My implementation of PHP Chainable
Posted: March 2nd, 2010 | Author: shesek | Filed under: PHP | Tags: chainable, method chainability, php chainable | No Comments »I needed a way to use method chainability on objects without having to modify the actual class (e.g. extending some Chainable class or implementing one) and without having to “return $this” on every method.
I came up with, what I believe to be, an elegant simple solution – a generic approach that can be used on any class/object, which makes chaining possible on 3rd-party classes and enables you to get the return values from those methods.
For the lazy ones among us, it also allows to have the benefits of method chaining without having to think about it while coding, but I won’t recommended that because of the performance hit. Its more useful for 3rd-party classes, if you’re the one writing the class just make sure to support chaining.
So, first of all, this is the Chainable class:
class Chainable {
private $obj;
private $result;
public function __construct($obj){
$this->obj = $obj;
}
public function __call($method, $args) {
if (substr($method, 0, 1) == '_') {
array_unshift($args, $this->result);
$method = substr($method, 1);
}
$this->result = call_user_func_array(array($this->obj, $method), $args);
return $this;
}
// __set & __get were added later on and mentiond at the very end of the post
public function __set($key, $value){$this->obj->$key = $value; return $this;}
public function __get($key){return $this->obj->$key;}
public function result(){
return $this->result;
}
// new Chainable($obj)->foo() isn't possible, Chainable::get($obj)->foo() is
public static function get($obj){
$chainable = new Chainable($obj);
return $chainable;
}
}
(You can download a raw version from here)
Basically, you use instances of Chainable instead of working directly with the actual class. You get a new instance by using new Chainable($object), or the more convient way which allows to call methods of it right away – Chainable::get($object).
After getting a Chainable object that’s connected to your object, you simply use $chainableObj->method1()->method2().
If you want to pass the result of the last method call to the next method, you prefix the method that should receive the result with a “_”, and than the first argument passed to it would be the last return value.
Also, you have a result() method for getting the last result from “outside”. this does create problems when you actually have a result() method on the object you’re passing to Chainable, and I will probably change it to something less likely to be used.
Lets take an example class, Foo:
class Foo {
public function bar($str){
return 'bar::' . $str;
}
public function taz($passed, $str) {
return 'taz::' . $str . ' -- passed::' . $passed;
}
}
Than, to use the Chainable interface you:
$foo = new Foo;
echo Chainable::get($foo)->bar('shesek')->_taz('test')->result();
</pre>
Which prints "taz::test -- passed::bar::shesek". note the "<strong style="color: #ff0000;">_</strong>taz" which allows taz() to get the return value of bar() as the first argument, $passed.
Another approach could be adding a "chain" method inside the Foo class (probably much easier to make a base class that has this and extend):
1
public function chain(){
return Chainable::get($this);
}
Which enables you to use $foo->chain()->bar('shesek')->_taz('test')->result();. It makes the code prettier, with the price of having to modify the class itself – but a very minor modification.
If you have any remarks or thoughts, I’ll be happy to hear about it!
Update: Added __set & __GET
__set allows you to set variables in the object while chaining (example with PHPMailer):
$mail=new PHPMailer();
Chainable::get($mail)->__set('Subject','Hello there')->AddReplyTo('my@email.info')->SetFrom('my@email.info')->AddAddress('your@email.info')->msgHTML('<h1>Hey!</h1>')->send()->result()
This is equal to $mail->Subject=’…’;. This can be used as a magic setter too, but doesn’t make much sense.
__get allows you to read public properties of the object:
$http = new HTTP;
echo Chainable::get($http)->__set('url', 'http://www.google.com/')->request()->html;
The last ->html> is equal to $http->html.
Recent Comments