Search code examples
phpcallback

PHP - Argument #3 ($modifier) must be of type callable, string given


I am trying to store a callable/closure as a class field/property.

My initial attempt was this:

class MyClass
{
    protected callable $modifier;

    public function __construct(callable $modifier) {
        $this->modifier = $modifier;
    }
}

class SomeOtherClass
{
    public static function func()
    {
        return ['a' => 'b'];
    }
}

$test = new MyClass('SomeOtherClass::func');

This returned an error of:

$modifier cannot have type callable

Then, after reading this online, I've tried this with no success:

class MyClass
{
    protected Closure $modifier;

    public function __construct(callable $modifier) {
        $this->modifier = Closure::fromCallable($modifier);
    }
}

class SomeOtherClass
{
    public static function func()
    {
        return ['a' => 'b'];
    }
}

$test = new MyClass('SomeOtherClass::func');

This last one returns this error:

Argument #1 ($modifier) must be of type callable, string given

Then I've tried this:

class MyClass
{
    protected Closure $modifier;

    public function __construct(callable $modifier) {
        $this->modifier = Closure::fromCallable($modifier);
    }
}

class SomeOtherClass
{
    public static function func()
    {
        return ['a' => 'b'];
    }
}

$test = new MyClass(['SomeOtherClass', 'func']); // <----- changed string to an array

Which returns this error:

Argument #1 ($modifier) must be of type callable, array given

Is this even possible to do in PHP?


Solution

  • Assuming you're using PHP 8.1 or above (which at the time of writing are the only supported versions of PHP anyway), then you can use first-class callable syntax, as introduced in 8.1.

    To do this, you use the name of a function and then (...) as the argument to pass in.

    In your case, you could therefore do this:

    class MyClass
    {
        protected Closure $modifier;
    
        public function __construct(callable $modifier) {
            $this->modifier = Closure::fromCallable($modifier);
        }
        
        public function runClosure()
        {
            var_dump(($this->modifier)());
        }
    }
    
    class SomeOtherClass
    {
        public static function func()
        {
            return ['a' => 'b'];
        }
    }
    
    $test = new MyClass(SomeOtherClass::func(...));
    $test->runClosure();
    

    Live demo: https://3v4l.org/nMmvq


    N.B. The code above assumes that you want to use func() as a static method (as defined in your sample code). If anyone is reading and wants to do something similar but with an instance method, you'd need a small adjustment as follows:

    class MyClass
    {
        protected Closure $modifier;
    
        public function __construct(callable $modifier) {
            $this->modifier = Closure::fromCallable($modifier);
        }
    
        public function runClosure()
        {
            var_dump(($this->modifier)());
        }
    }
    
    class SomeOtherClass
    {
        public function func()
        {
            return ['a' => 'b'];
        }
    }
    
    $soc = new SomeOtherClass();
    $test = new MyClass($soc->func(...));
    $test->runClosure();
    

    Live demo: https://3v4l.org/iEAiM