On Tue, Nov 28, 2023 at 12:59 AM Sergii Shymko <ser...@shymko.net> wrote:
>
> Hi,
>
> Wanted to bring up an inconsistent behavior of callable arguments compared to 
> arguments of other types.
> Callable argument cannot have a default value (tested string or array types - 
> both are not permitted).
> The same exact value works perfectly fine when passed dynamically, it just 
> cannot be specified as a default.
> The workaround is to remove the type annotation which is obviously 
> undesirable.
>
> Here’s an example:
> declare(strict_types=1);
> function test(callable $idGenerator = 'session_create_id') {
>     $id = $idGenerator();
>     // ...
> }
>
> The function/method declaration above produces the following error on all PHP 
> versions:
> Fatal error: Cannot use string as default value for parameter $idGenerator of 
> type callable in /tmp/preview on line 4
>
> Note that the exact same string argument can be passed without any issue:
> function test(callable $idGenerator) {…}
> test('session_create_id’);
>
> Is there a specific architectural limitation causing this that's 
> hard/impossible to overcome?
>
> I’m aware that class properties cannot be annotated with callable - another 
> unfortunate limitation.
> Callable is not a real type like other primitive types which causes all these 
> inconsistencies, correct?
> Callable properties (separate topic) may be a challenge, but can at least 
> argument defaults be supported?
>
> Regards,
> Sergii Shymko

I stopped using "callable" a long time ago. These days I use \Closure
and it works in all the same places (including properties).

If you want to accept a callable string, you need to change the type
to \Closure|string and verify it with `is_callable()`.

> Is there a specific architectural limitation causing this that's 
> hard/impossible to overcome?

IIRC, default arguments must be compile-time constant, and this isn't,
apparently:

session_create_id(...)

You can also do something like this:

function hello() {
    echo "hi\n";
}

class wrapper {
    public function __construct(public \Closure|string $closure) {
        is_callable($closure) ?: throw new
InvalidArgumentException('closure must be callable');
    }
    public function __invoke() {
        return ($this->closure)();
    }
}

function test(wrapper|Closure $closure = new wrapper('hello')) {
    ($closure)();
}

test();

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to