First-class callable syntax in PHP (introduced in PHP 8.1) allows us to create an anonymous function (Closure) using the expression CallableExpr(...). It is safer and more refactor-friendly than using string or array callables. Since PHP 8.5, first-class callable syntax is allowed in constant expressions. This includes default values of parameters and properties, constants, and attribute parameters.
Assume we have a function that accepts an optional callback for filtering array elements. Before PHP 8.5, we had to default the closure to null and assign a fallback closure when none was provided by the user.
<?php
function arrayFilter(array $data, ?Closure $callback = null): array
{
$callback ??= is_int(...);
$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]=>int(0)}
Since PHP 8.5, first-class callable syntax can be used directly as a default parameter value, eliminating the need to set the closure to null and assign a fallback.
<?php
function arrayFilter(array $data, Closure $callback = is_int(...)): 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]=>int(0)}
Other cases where first-class callable syntax is permitted in constant expressions include:
- Default values of properties
ArrayFilter.php
<?php
class ArrayFilter
{
private Closure $callback = is_int(...);
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]=>int(0)}
- Constants
FilterConst.php
<?php
class FilterConst
{
public const Closure DEFAULT = is_int(...);
}
<?php
require_once __DIR__.'/FilterConst.php';
$data = ['', 1, 0, false, '1'];
var_dump(array_filter($data, FilterConst::DEFAULT)); // array(2) {[0]=>int(1) [1]=>int(0)}
- 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(ucfirst(...))] 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