Use Closure in Constant Expressions in PHP 8.5

Use Closure in Constant Expressions in PHP 8.5

Since PHP 8.5, static closures can be used in constant expressions. This includes default values of parameters and properties, constants, and attribute parameters.

Suppose we have a function that takes an optional callback to filter array elements. Before PHP 8.5, we had to define the closure as null by default and then assign a fallback closure when the user didn't provide one.

<?php

function arrayFilter(array $data, ?Closure $callback = null): array
{
    $callback ??= static fn (mixed $item) => !empty($item);
    $result = [];
    foreach ($data as $item) {
        if ($callback($item)) {
            $result[] = $item;
        }
    }

    return $result;
}

$data = ['', 1, 0, false, '1'];
var_dump(arrayFilter($data)); // array(2) {[0]=>int(1) [1]=>string(1) "1"}

Since PHP 8.5, a static closure can be used directly as a default parameter value, rather than setting the closure to null and assigning a fallback.

<?php

function arrayFilter(
    array $data,
    Closure $callback = static function (mixed $item) { return !empty($item); },
): array {
    $result = [];
    foreach ($data as $item) {
        if ($callback($item)) {
            $result[] = $item;
        }
    }

    return $result;
}

$data = ['', 1, 0, false, '1'];
var_dump(arrayFilter($data)); // array(2) {[0]=>int(1) [1]=>string(1) "1"}

Other cases where static closures are valid in constant expressions:

  • Default values of properties

ArrayFilter.php

<?php

class ArrayFilter
{
    private Closure $callback = static function (mixed $item) {
        return !empty($item);
    };

    public function setCallback(Closure $callback): void
    {
        $this->callback = $callback;
    }

    public function run(array $data): array
    {
        $result = [];
        foreach ($data as $item) {
            if (($this->callback)($item)) {
                $result[] = $item;
            }
        }

        return $result;
    }
}
<?php

require_once __DIR__.'/ArrayFilter.php';

$arrayFilter = new ArrayFilter();

$data = ['', 1, 0, false, '1'];
var_dump($arrayFilter->run($data)); // array(2) {[0]=>int(1) [1]=>string(1) "1"}
  • Constants

FilterConst.php

<?php

class FilterConst
{
    public const Closure DEFAULT = static function (mixed $item) {
        return !empty($item);
    };
}
<?php

require_once __DIR__.'/FilterConst.php';

$data = ['', 1, 0, false, '1'];
var_dump(array_filter($data, FilterConst::DEFAULT)); // array(2) {[0]=>int(1) [1]=>string(1) "1"}
  • Attribute parameters

Transform.php

<?php

#[Attribute(Attribute::TARGET_PROPERTY)]
class Transform
{
    public function __construct(public Closure $callback)
    {
    }
}

User.php

<?php

class User
{
    public function __construct(
        #[Transform(static function (string $value) { return ucfirst($value); })]
        private string $name,
    ) {
        $this->name = $this->applyTransform('name', $name);
    }

    public function getName(): string
    {
        return $this->name;
    }

    private function applyTransform(string $property, mixed $value): mixed
    {
        $reflection = new ReflectionProperty($this, $property);
        $attribute = $reflection->getAttributes(Transform::class)[0]->newInstance();

        return ($attribute->callback)($value);
    }
}
<?php

require_once __DIR__.'/Transform.php';
require_once __DIR__.'/User.php';

$user = new User('john');
echo $user->getName(); // John

Leave a Comment

Cancel reply

Your email address will not be published.