views:

38

answers:

3

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);
+3  A: 

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();
Gordon
We posted at the same time... Yes maybe that could help... If I don't find any other better alternative I will mark your answer as good.
lepe
+4  A: 

PHP thinks you want to call a method k on instance $t when you do:

$t->k(4, 5)

which is perfectly reasonable. You can use an intermediate variable to call the object:

$b = $t->k;
$b(4, 5);

See also bug #50029, which describes your issue.

Sjoerd
+1 for finding the bug
Gordon
(+1). Now it is getting clear for me. Thank you for the link.
lepe
+1  A: 

You can not use method syntax (like $foo->bar() ) to call closures or objects with __invoke, since the engine always thinks this is a method call. You could simulate it through __call:

function __call($name, $params) {
  if(is_callable($this->$name)) {
    call_user_func_array($this->$name, $params);
  }
}

but it would not work as-is.

StasM