Junior Spellweaver
- Joined
- Nov 26, 2008
- Messages
- 196
- Reaction score
- 62
Class chaining method is a useful technique when writing classes that have lots of methods that store data within their class. Builders such as the
The following code demonstrates the Chainable Interface, which can be used to contract objects who implement it to build methods consistent with the chaining philosophy.
First, we start with the Interface, which instructs the implementor to use the PHP magic method __call(). Place the following code in chainable.php.
Next, we build our Chain class and implement Chainable. Let's build our __call() method to prefix calls to nonexistent functions with 'chain_'. This allows us to create chain methods that the user can call without even knowing the difference. In this way, we can encapsulate the functionality and the user is blind and happy!
Save this file as chain.php.
Create the following Test Class in test.php.
Notice that we have one method using the 'chain_' prefix, chain_hello(). When we call hello(), the __call() magic method will kick in and call chain_hello(), which is what we really want. After that, it will return the $this reference to allow us to continue chaining additional method calls. Also note that we have left the make_array( method intact. We can call this method normally, although, it cannot be chained.
Let's test our code with sample_1.php:
Let's suppose we wanted to make our make_array() method chainable as well. The only problem we'll encounter, is that this method returns an array. By returning $this we will kill its functionality entirely! We need a way to store the original return values from methods and still allow them to be chained.
Let's put together another contract with the ReturnStackable Interface:
Now, if we extend our Chain class to implement ReturnStackable in addition to Chainable, we end up with the following:
The return_stack() method returns either the specified array value by index, or the entire array of returns as they were stored by the __call() magic method.
If we modify our Test class, we can turn the make_array() method into a chainable method. While we're at it, let's go ahead and add some functionality to store a supplied array in the return stack.
Another bit of test code:
Now, we've got a chainable class that remembers the original function returns! The beauty of this approach, is that you can implement method chaining in your existing classes with a few simple modifications to the method names, and still have access to their return values via the return stack. Additionally, you could flesh out the Chain class to optionally purge the return stack when it was retrieved. This would allow instant access to the stack, while preventing lingering values from getting in the way.
You must be registered to see links
You must be registered to see links
make great use of this technique. If you are unfamiliar with Chaining, check out the
You must be registered to see links
.The following code demonstrates the Chainable Interface, which can be used to contract objects who implement it to build methods consistent with the chaining philosophy.
First, we start with the Interface, which instructs the implementor to use the PHP magic method __call(). Place the following code in chainable.php.
PHP:
<?php
interface Chainable
{
/**
* Overload method calls.
*/
public function __call($name, array $arguments);
}
Next, we build our Chain class and implement Chainable. Let's build our __call() method to prefix calls to nonexistent functions with 'chain_'. This allows us to create chain methods that the user can call without even knowing the difference. In this way, we can encapsulate the functionality and the user is blind and happy!
PHP:
<?php
class Chain implements Chainable
{
/**
* Descendants of this class must use `chain_` prefix for all
* methods they wish to make chainable. The method can then be
* called by its base name.
*
* Return values from called methods are lost.
*
* @param string $name name of the method called
* @param array $arguments array of arguments supplied to method
* @return PlainChain $this instance of this class
*/
public function __call($name, array $arguments)
{
$method = "chain_{$name}";
call_user_func_array(array($this, $method), $arguments);
return $this;
}
}
Save this file as chain.php.
Create the following Test Class in test.php.
PHP:
<?php
class Test extends Chain
{
/**
* Name functions according to Parent __call requirements to have
* them automagically return $this.
*/
public function chain_hello()
{
print 'Hello';
return TRUE;
}
public function make_array()
{
return array('foo' => 'bar', 'baz' => 'faz');
}
}
Notice that we have one method using the 'chain_' prefix, chain_hello(). When we call hello(), the __call() magic method will kick in and call chain_hello(), which is what we really want. After that, it will return the $this reference to allow us to continue chaining additional method calls. Also note that we have left the make_array( method intact. We can call this method normally, although, it cannot be chained.
Let's test our code with sample_1.php:
PHP:
<?php
//get dependencies
require('chainable.php');
require('chain.php');
require('test.php');
$test = new Test;
//execute a chain
$test
->hello()
->hello()
->make_array();
Let's suppose we wanted to make our make_array() method chainable as well. The only problem we'll encounter, is that this method returns an array. By returning $this we will kill its functionality entirely! We need a way to store the original return values from methods and still allow them to be chained.
Let's put together another contract with the ReturnStackable Interface:
PHP:
<?php
interface ReturnStackable
{
/**
* A way to get the return stack values
*/
public function return_stack($key = NULL);
}
Now, if we extend our Chain class to implement ReturnStackable in addition to Chainable, we end up with the following:
PHP:
<?php
class Chain implements Chainable, ReturnStackable
{
/**
* Keeps track of original return values that would otherwise be
* lost when returning $this
*/
private $return_stack = array();
/**
* Descendants of this class must use `chain_` prefix for all
* methods they wish to make chainable. The method can then be
* called by its base name.
*
* Stores returns from methods for later retrieval by contract of
* ReturnStackable.
*
* @param string $name name of the method called
* @param array $arguments array of arguments supplied to method
* @return Chain $this instance of this class
*/
public function __call($name, array $arguments)
{
$method = "chain_{$name}";
$this->return_stack[$name][] = call_user_func_array
(
array($this, $method),
$arguments
);
return $this;
}
/**
* Return array stack of original return values generated by
* methods that were lost due to $this being returned instead.
*
* For a particular key, the return values for all calls to that
* method will be returned in array format in ascending
* chronological order.
*/
public function return_stack($key = NULL)
{
if (isset($key))
{
return $this->return_stack[$key];
}
return $this->return_stack;
}
}
The return_stack() method returns either the specified array value by index, or the entire array of returns as they were stored by the __call() magic method.
If we modify our Test class, we can turn the make_array() method into a chainable method. While we're at it, let's go ahead and add some functionality to store a supplied array in the return stack.
PHP:
<?php
class Test extends Chain
{
/**
* Name functions according to Parent __call requirements to have
* them automagically return $this.
*/
public function chain_hello()
{
print 'Hello';
return TRUE;
}
public function chain_make_array(array $array = NULL)
{
if (isset($array))
{
return $array;
}
else
{
return array('foo' => 'bar', 'baz' => 'faz');
}
}
}
Another bit of test code:
PHP:
<?php
//get dependencies
require('chainable.php');
require('return_stackable.php');
require('chain_stackable.php');
require('test.php');
$test = new Test;
//execute a chain
$test->hello()
->hello()
->make_array()
->make_array(array('food' => 'tacos'))
->make_array(array('food' => 'burritos'));
//fetch the return stack from functions
$stack = $test->return_stack('make_array');
var_dump($stack);
Now, we've got a chainable class that remembers the original function returns! The beauty of this approach, is that you can implement method chaining in your existing classes with a few simple modifications to the method names, and still have access to their return values via the return stack. Additionally, you could flesh out the Chain class to optionally purge the return stack when it was retrieved. This would allow instant access to the stack, while preventing lingering values from getting in the way.
Last edited: