7

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);
lepe
  • 24,677
  • 9
  • 99
  • 108
  • Please provide a complete code example which shows your problem. The code sample you say works does not work and the code sample you say gives a problem works. – Sjoerd Jun 24 '10 at 07:54
  • Yes, sorry, my bad... I update it. Sjoerd: Did that example worked for you? strange :S – lepe Jun 24 '10 at 07:56

4 Answers4

7

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
  • 74,049
  • 16
  • 131
  • 175
6

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
  • 312,688
  • 75
  • 539
  • 559
  • 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 Jun 24 '10 at 08:00
2

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
  • 10,593
  • 6
  • 56
  • 103
0

If you call $test->k() PHP will search for a method called "k" on the $test instance and obviously it will throws an Exception.

To resolve this problem you can create a getter of the property "k"

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.
    }

    public function getK() {
        return $this->k;
    }
}

So now you can use the functor in this way:

$t = new Test();
echo $t->getK()(4,5);
David Ginanni
  • 1,617
  • 15
  • 9