Замыкания (closures) и first-class callables в константных выражениях PHP 8.5
Введение
PHP 8.5 принёс одну из самых долгожданных возможностей для атрибутов,
дефолтных значений и констант — статические замыкания (closures) и first-class callables (...(...)) теперь
разрешены в constant expressions.
Это не просто синтаксический сахар — это дверь в совершенно другой стиль написания конфигураций, валидаций, обработчиков событий и маппингов прямо в коде, без лишних классов и фабрик.
Что именно стало возможным
| Место использования | До PHP 8.5 | PHP 8.5+ (новый синтаксис) |
|---|---|---|
| Дефолтное значение параметра | ?Closure $cb = null + if внутри |
Closure $cb = static fn($v) => $v * 2 |
| Дефолтное значение свойства | Только скаляры / массивы / null | private Closure $normalizer = static fn(string $s) => trim($s); |
| Параметр атрибута | Строка, массив, константа, callable-массив | #[Rule(callback: static fn($v) => $v > 0)] |
| Константа класса / глобальная | Только immutable значения | const PROCESSOR = self::normalizeData(...); |
| Массивы в атрибутах / константах | Только простые значения | const FILTERS = [static fn($v)=>filter_var($v,FILTER_VALIDATE_EMAIL), ...] |
Самые важные правила и ограничения
- Замыкание обязательно должно быть
static function() {}илиstatic fn() =>
В контексте константных выражений замыкание создаётся на этапе компиляции, когда ещё нет
никакого $this. Поэтому PHP требует явного указания static, чтобы подчеркнуть: «это замыкание
без привязки к объекту».
- Запрещено захватывать переменные через
use ($var)
Нет окружающего скоупа на этапе компиляции.
- Нельзя использовать
$thisвнутри
Замыкание создаётся на уровне компиляции, объекта ещё нет.
Разрешены:
- статические методы классов через
self::method(...) - глобальные функции через
strlen(...) - first-class callables вида
SomeClass::method(...) - стрелочные функции
static fn($x) => $x * 2 - обычные анонимные функции
static function($x) { return $x * 2; }
Примеры использования
Замыкание как значение по умолчанию свойства
class User
{
public ?Closure $normalizer =
static fn (string $s) => trim(strtolower($s));
}
В атрибутах (очень мощно для валидаторов, прав доступа и т.д.)
#[Assert\Callback(
callback: static fn ($value) => $value !== '' && strlen($value) >= 3
)]
class Product
{
public string $name;
}
First-class callable (статические методы и обычные функции)
class Formatter
{
public const NORMALIZE = 'strtolower(...)'; // работает
public const TRIM_AND_LOWER = self::trimLower(...); // тоже работает
private static function trimLower(string $s): string
{
return trim(strtolower($s));
}
}
В параметрах функций
function process(
array $data,
Closure $callback = static fn ($v) => $v * 2
): array {
return array_map($callback, $data);
}
Массивы замыканий / callables в константах
const FILTERS = [
'email' => static fn ($v) => filter_var($v, FILTER_VALIDATE_EMAIL),
'length' => 'strlen(...)',
'slug' => [self::class, 'makeSlug(...)'],
];