Home
New in PHP 8.5: Closures as Constant Expressions
Liked 7 times

Another exciting PHP 8.5 feature: Closures can now be used as constant expressions, allowing them to appear as default parameters or attribute values.

Ever wanted to set a closure as a default parameter value in PHP, only having to come up with workarounds? In PHP 8.5, that frustration is gone. Closures can now be constant expressions — meaning they work anywhere you’d use a literal value.

I’ve been bitten by this limitation before. Many times. Now, you can use closures in places where you could previously only use values like integers or strings:

  • Default parameter values
  • Constant values
  • Property default values
  • Attribute parameter values
  • And more

Default values

In the past, I’ve written code like this:

function someFunction(mixed $someValue, ?callable $callback = null): bool
{
    $callback ??= fn () => true;
    return $callback($someValue);
}

Or this:

final class SomeClass
{
    private Closure $someCallable;

    public function __construct()
    {
        $this->someCallable = function (mixed $value): bool {
            // todo
            return true;
        };
    }
}

With closures now being constant expressions, both examples can be simplified to:

function someFunction(
    mixed $someValue,
    callable $callback = static function () { return true; },
): bool {
    return $callback($someValue);
}

final class SomeClass
{
    private Closure $someCallable = static function (mixed $value): bool {
        // todo
        return true;
    };
}

No more $callback ??= gymnastics. Using closures directly as default parameter values is something I do fairly often, so being able to tighten the public interface by avoiding nonsense values like null is a great improvement.

Attributes

This is another great change — you can now define functions directly within attributes. For example:

#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class TruthyValidator
{
    public function __construct(
        public Closure $truthyValidator = static function(mixed $value): bool {
            return (bool) $value;
        }
    ) {
    }
}

Here’s a simple validator attribute that checks whether the value is truthy, with the default implementation just casting it to a boolean and letting PHP handle the conversion. But say you want to consider the string '0' as truthy:

    #[TruthyValidator(truthyValidator: static function(string|int|null $value): bool {
        return $value === '0' || $value;
    })]
    public string|int|null $someProperty = null;

First-Class Callables

This is technically a separate RFC, but it was split for voting reasons rather than technical ones, so I’m covering both in the same article.

In addition to standard closures where you define the function body inline, you can now also use first-class callables as constant expressions. This means all of the above examples also work with them.

<?php

// define a default validator
function defaultValidatorFunction(mixed $value): bool
{
    return (bool) $value;
}

// define the validator class
#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class TruthyValidator
{
    public function __construct(
        // and assign the default validator using the first-class callable syntax
        public Closure $truthyValidator = defaultValidatorFunction(...),
    ) {
    }
}

// define our custom validation function
function truthyValidatorWithoutZeroString(string|int|null $value): bool
{
    return $value === '0' || $value;
}

class SomeClassToBeValidated
{
    // and use it as a first-class callable
    #[TruthyValidator(truthyValidator: truthyValidatorWithoutZeroString(...))]
    public string|int|null $someProperty = null;
}

Conclusion

I really like this addition because it — like many other recent improvements — moves PHP toward a cleaner, more consistent language with fewer hacks and a saner syntax.

Where will you use this first? Drop your examples in the comments — I’m curious what creative cases you come up with.


More from New in PHP 8.5:


Tip: To comment on or like this post, open it on your favourite Fediverse instance, such as Mastodon or Lemmy.
© 2024 Dominik Chrástecký. All rights reserved.