Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure we are using __invoke on property and not __call on main object?

Tags:

oop

php

Let's assume we have code like:

<?php
class Worker {
  public function __invoke() {
    echo "invoked\n";
  }
}

class Caller {
  public $worker;

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

  public function __call($name, $arguments) {
    echo "called\n";
  }
}

$c = new Caller(new Worker());
echo $c->worker();
?>

The result is called. What to do to get invoked?

like image 405
Mołot Avatar asked May 28 '13 14:05

Mołot


3 Answers

This problem exists also with anonymous functions which work the same. You have a few ways to work around:

1) modify your __call to check is it's a method, if not, invoke the property:

if (property_exists($this, $name) && is_object($this->$name) && method_exists($this->$name, "__invoke")) {
    call_user_func_array($this->name, $args);
}

2) call __invoke directly: $c->worker->__invoke();

3) Save the property into a temporary variable:

$tempVar = $c->worker;
$tempVar();

4) (nearly the same as 3) (source: http://marc.info/?l=php-internals&m=136336319809565&w=4)

${'_'.!$_=$c->worker}();

5) Use call_user_func or call_user_func_array:

call_user_func($c->worker);
like image 100
bwoebi Avatar answered Nov 10 '22 05:11

bwoebi


I experienced the same problem and it comes that (as you see) it's difficult to tell which object you mean.

The only way (that is "almost" clear) is to use an auxiliary variable

$temp = $c->worker;
$temp(); // should be now invoked
like image 2
Voitcus Avatar answered Nov 10 '22 06:11

Voitcus


Update:

In case you don't want to implement the __call() Method on all your Callables I'd just extend the Callable like this:

<?php
class Worker {
  public function __invoke() {
    echo "invoked\n";
  }
}

class InvokeCaller{

    public function __call($name, $arguments) {
        if(property_exists($this, $name) && is_object($this->{$name}))
            $this->{$name}->__invoke($arguments);
        else
            echo "called\n";
    }

}

class Caller extends InvokeCaller{
  public $worker;

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

}

class AnotherCaller extends InvokeCaller{

    public $anotherWorker;

    public function __construct($worker) {
        $this->anotherWorker = $worker;
    }

}

$c = new Caller(new Worker());
$c2 = new AnotherCaller(new Worker());
echo $c->worker();
echo $c2->anotherWorker();
?>

Old

I came up with this one

<?php
class Worker {
  public function __invoke() {
    echo "invoked\n";
  }
}

class Caller {
  public $worker;

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

  public function __call($name, $arguments) {
    if(property_exists($this, $name) && is_object($this->{$name}))
        $this->{$name}->__invoke($arguments);
    else
        echo "called\n";
  }
}

$c = new Caller(new Worker());
echo $c->worker();
?>

We're just modifying our call.

like image 2
thpl Avatar answered Nov 10 '22 07:11

thpl