PHP Delayed Invocation Object

By virtue of PHP’s many Magic functions it is possible and indeed quite easy to create an object that can be used to call a function or object method now, but have it invoked later.

The reason I wrote this function was for lazy loading of data. I had the id’s and information required to acquire the data available in the constructor so I could have loaded it then. I could have saved all those data values and made the DB call later. That would have meant that I would have needed another private function that new how to use the data to pass it off to my model objects. So, why not pretend to call it in the constructor and have it invoked later.

Say that you have a class Blog_Feed and you want to call a method getBlogPostData(id) on it.

[php]
$obj = new Blog_Feed();
$data = $obj->getBlogPostData(42);
[/php]

Using the Invocation class below

[php]
$obj = new Blog_Feed();
$inv = new Invocation($obj);
$inv->getBlogPostData(42);
// $data = $inv();
[/php]

Those two examples are functionally equivalent. The advantage is that if your application has differing code paths where some paths don’t need the blog post data it won’t make that call yet.

[php]
class Some_Class {
$data = null;
$dataInv = null;

function __construct(id) {
$obj = new Blog_Feed();
$this->dataInv = new Invocation($obj);
$this->dataInv->getBlogPostData(id);
}

function functionThatNeedsData() {
if (!$this->data) { $inv = $this->dataInv; $this->data = $inv(); }
// use the data
}

.
.
.
}
[/php]

Therefore, if the functionThatNeedsData is never called then the $data is never populated by the getBlogPostData() function.

A quick side note about the line
[php]if (!$this->data) { $inv = $this->dataInv; $this->data = $inv(); }[/php]
The instance variable $dataInv is saved out into a local variable before it is invoked because PHP can’t tell the difference syntactically between calling a variable as a function or calling a member function. PHP will assume it’s a function, not be able to find it, and then throw an exception.
[php]
$obj->localvar();
$obj->method();
[/php]

This is the code for the Invocation class.

[php]
<?php
class Invocation {
protected $obj;
protected $func;
protected $args;

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

public function __invoke() {
if ($this->func) {
if ($this->obj) {
return call_user_func_array(
array($this->obj, $this->func), $this->args
);
}
else {
return call_user_func_array($this->func, $this->args);
}
}
}

public function __call($name, $args) {
$this->func = $name;
$this->args = $args;
}
}
[/php]

As you can see in the code. The object to have the method invoked on is taken by the constructor. No object needs to be given though, this can be used to call a global free function as well. The __call function saves the function that was called and the arguments passed to that function. The __invoke function uses the data it has to call the function.

This can be easily extended to call multiple functions in succession.

[php]
<?php
class Invocation {
protected $obj;
protected $func = array();
protected $args = array();
protected $multi;

public function __construct($obj = null, $multi = false) {
$this->obj = $obj;
$this->multi = $multi;
}

public function __invoke() {
while (count($this->func)) {
$func = array_shift($this->func);
$args = array_shift($this->args);

if ($func) {
if ($this->obj) {
return call_user_func_array(
array($this->obj, $func), $args
);
}
else {
return call_user_func_array($func, $args);
}
}
}
}

public function __call($name, $args) {
if ($this->multi || count($this->func) == 0) {
$this->func[] = $name;
$this->args[] = $args;
}
}
}
[/php]

A quick example:

[php]
$obj = new Some_Type();
$inv = new Invocation($obj);
$inv->function1(‘hello’, ‘world’);
$inv->function2(42, PI);
$res = $inv(); // result from function1;
$res = $inv(); // result from function1 again;
[/php]

[php]
$obj = new Some_Type();
$inv = new Invocation($obj, true);
$inv->function1(‘hello’, ‘world’);
$inv->function2(42, PI);
$res = $inv(); // result from function1;
$res = $inv(); // result from function2;
[/php]