/unclosure

Closure unwrapper especially suited for Laravel PDO.

Primary LanguagePHPMIT LicenseMIT

Unclosure Build Status Coverage Status Scrutinizer Code Quality

Closure unwrapper especially suited for Laravel PDO.

Requirements

Package Version Mandatory
PHP ^7.4 || ^8.0
PHPStan >=1.1

Installing

composer require mpyw/unclosure

Examples

Call PDO::setAttribute() after database connection has been established

<?php

use Mpyw\Unclosure\Value;
use PDO;

function withEmulation(PDO $pdo, bool $enabled): PDO
{
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $enabled);
    return $pdo;
}

$connector = function (string $dsn) {
    return new PDO($dsn);
};

// Eager Evaluation
$pdo = Value::withCallback($connector('sqlite::memory:'), 'withEmulation', true);

// Lazy Evaluation
$pdo = Value::withCallback($connector, 'withEmulation', true);
$pdo = $pdo('sqlite::memory:');

Temporarily change PDO attributes

<?php

use Mpyw\Unclosure\Value;
use PDO;

function switchingEmulationTo(bool $enabled, &$pdo, callable $callback, ...$args)
{
    return Value::withEffect(
        $pdo,
        function (PDO $pdo) use ($enabled) {
            $original = $pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES);
            $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $enabled);
            
            return function (PDO $pdo) use ($original) {
                $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $original);
            };
        },
        $callback,
        ...$args
    );
}

$connector = function (string $dsn) {
    return new PDO($dsn);
};

// Eager Evaluation
$pdo = $connector('sqlite::memory:');
switchingEmulationTo(true, $pdo, function () use ($pdo) {
    // Run queries that require prepared statement emulation
});

// Lazy Evaluation
$pdo = $connector;
switchingEmulationTo(true, $pdo, function () use (&$pdo) {
    $pdo = $pdo('sqlite::memory:');
    
    // Run queries that require prepared statement emulation
});

API

Value::withCallback()

static mixed|Closure withCallback(mixed|Closure $value, callable $callback, mixed ...$args)

Call $callback($value, ...$args) or wrap its call into Closure.

  • When you pass $value as Closure:
    • Return wrapped Closure which returns $callback($value(), ...$args).
  • When you pass $value as a raw value:
    • Return $callback($value, ...$args).

Arguments and Return Value

Name Type Description
$value mixed
(A) Closure
First argument for $callback which is unwrappable
$callback (B) callable A callback which takes unwrapped $value as the first argument
...$args mixed Second, third, ... arguments for $callback
<Return Value> mixed
(C) Closure
Decorated Closure or an unwrapped value

(A) $value

$value(mixed ...$initialArgs): mixed
Name Type Description
...$initialArgs mixed Arguments for unwrapping Closure
<Return Value> mixed An unwrapped value

(B) $callback

$callback(mixed $value, ...$args): mixed
Name Type Description
$value mixed An unwrapped value
...$args mixed Arguments from Value::withCallback()
<Return Value> mixed A decorated unwrapped value

(C) Return Value

*(mixed ...$initialArgs): mixed
Name Type Description
...$initialArgs mixed Arguments for unwrapping $value
<Return Value> mixed An unwrapped value which is propagated from $value(...$initialArgs)

Value::withEffect()
Value::withEffectForEach()

public static withEffect(mixed|Closure &$value, callable $configurator, callable $callback, mixed ...$args): mixed
public static withEffectForEach((mixed|Closure)[] &$values, callable $configurator, callable $callback, mixed ...$args): mixed

Call $callback(...$args), watching new assignment on &$value. You can conditionally unwrap $value in your $callback by yourself.

  • When you pass $value as Closure:
    • If $value has been unwrapped, configurations via $configurator are applied.
    • If $value still remains as Closure, all configurations are canceled.
  • When you pass $value as a raw value:
    • Configurations via $conigurator are immediately applied.

Arguments and Return Value

Name Type Description
&$value mixed
(A) Closure
An argument for $configurator which is unwrappable
$configurator (B) callable A configurator callback which takes unwrapped $value as the first argument
$callback (D) callable Any callback function
...$args mixed Arguments for $callback
<Return Value> mixed Return value from $callback(...$args)

(A) &$value

*(): mixed
Name Type Description
<Return Value> mixed An unwrapped value

(B) $configurator

$configurator(mixed $value): ?callable
Name Type Description
mixed mixed An unwrapped value
<Return Value> null
(C) callable
An optional disposer function corresponding to the configurator

(C) $configurator Return Value

*(mixed $value): void
Name Type Description
mixed mixed An unwrapped value

(D) $callback

$callback(...$args): mixed
Name Type Description
...$args mixed Arguments from Value::withEffect()
<Return Value> mixed