Search code examples
phpmethodsinvoke

PHP5.3: "Call to undefined method" error when calling invoke from class variable


I have been doing some tests (to replace old code) with the __invoke magic method and I'm not sure this is a bug or not:

Lets suppose we have a class:

class Calc {
    function __invoke($a,$b){
        return $a*$b;
    }
}

The following is possible and works without any problem:

$c = new Calc;
$k = $c;
echo $k(4,5); //outputs 20

However if I want to have another class to store an instance of that object, this doesn't work:

class Test {
    public $k;
    function __construct() {
        $c = new Calc;
        $this->k = $c; //Just to show a similar situation than before
        // $this-k = new Calc; produces the same error.
    }
}

The error occurs when we try to call it like:

$t = new Test;
echo $t->k(4,5); //Error: Call to undefined method Test::k()

I know that a "solution" could be to have a function inside the class Test (named k) to "forward" the call using call_user_func_array but that is not elegant.

I need to keep that instance inside a common class (for design purposes) and be able to call it as function from other classes... any suggestion?

Update:

I found something interesting (at least for my purposes):

If we assign the "class variable" into a local variable it works:

$t = new Test;
$m = $t->k;
echo $m(4,5);

Solution

  • When you do $test->k(), PHP thinks you are calling a method on the $test instance. Since there is no method named k(), PHP throws an exception. What you are trying to do is make PHP return the public property k and invoke that, but to do so you have to assign k to a variable first. It's a matter of dereferencing.

    You could add the magic __call method to your Test class to check if there is a property with the called method name and invoke that instead though:

    public function __call($method, $args) {
        if(property_exists($this, $method)) {
            $prop = $this->$method;
            return $prop();
        }
    }
    

    I leave adding the arguments to the invocation to you. You might also want to check if the property is_callable.

    But anyway, then you can do

    $test->k();