Creating a Chainable Interface in PHP

Class method chaining is a useful technique when writing classes that have lots of methods that store data within their class. Builders such as the Kohana Framework's Database Query Builder make great use of this technique. If you are unfamiliar with this practice, check out the Method Chaining Tutorial.

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.

toggle plain-text
  1. <?php
  2.  
  3. interface Chainable
  4. {
  5.     /**
  6.      * Overload method calls.
  7.      */
  8.     public function __call($name, array $arguments);
  9. }

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!

toggle plain-text
  1. <?php
  2.  
  3. class Chain implements Chainable
  4. {
  5.     /**
  6.      * Descendants of this class must use `chain_` prefix for all
  7.      * methods they wish to make chainable. The method can then be
  8.      * called by its base name.
  9.      *
  10.      * Return values from called methods are lost.
  11.      *
  12.      * @param string $name name of the method called
  13.      * @param array $arguments array of arguments supplied to method
  14.      * @return PlainChain $this instance of this class
  15.      */
  16.     public function __call($name, array $arguments)
  17.     {
  18.         $method = "chain_{$name}";     
  19.         call_user_func_array(array($this, $method), $arguments);
  20.        
  21.         return $this;
  22.     }
  23. }

Save this file as chain.php.

Create the following Test Class in test.php.

toggle plain-text
  1. <?php
  2.  
  3. class Test extends Chain
  4. {  
  5.     /**
  6.      * Name functions according to Parent __call requirements to have
  7.      * them automagically return $this.
  8.      */
  9.     public function chain_hello()
  10.     {
  11.         print 'Hello';
  12.        
  13.         return TRUE;
  14.     }
  15.    
  16.     public function make_array()
  17.     {
  18.         return array('foo' => 'bar', 'baz' => 'faz');      
  19.     }
  20. }

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:

toggle plain-text
  1. <?php
  2.  
  3. //get dependencies
  4. require('chainable.php');
  5. require('chain.php');
  6. require('test.php');
  7.  
  8. $test = new Test;
  9.  
  10. //execute a chain
  11. $test
  12. ->hello()
  13. ->hello()
  14. ->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

:
toggle plain-text
  1. <?php
  2.  
  3. interface ReturnStackable
  4. {  
  5.     /**
  6.      * A way to get the return stack values
  7.      */
  8.     public function return_stack($key = NULL);
  9. }
  10.  

Now, if we extend our Chain class to implement ReturnStackable in addition to Chainable, we end up with the following:

toggle plain-text
  1. <?php
  2.  
  3. class Chain implements Chainable, ReturnStackable
  4. {
  5.     /**
  6.      * Keeps track of original return values that would otherwise be
  7.      * lost when returning $this
  8.      */
  9.     private $return_stack = array();
  10.    
  11.     /**
  12.      * Descendants of this class must use `chain_` prefix for all
  13.      * methods they wish to make chainable. The method can then be
  14.      * called by its base name.
  15.      *
  16.      * Stores returns from methods for later retrieval by contract of
  17.      * ReturnStackable.
  18.      *
  19.      * @param string $name name of the method called
  20.      * @param array $arguments array of arguments supplied to method
  21.      * @return Chain $this instance of this class
  22.      */
  23.     public function __call($name, array $arguments)
  24.     {
  25.         $method = "chain_{$name}";     
  26.         $this->return_stack[$name][] = call_user_func_array
  27.         (
  28.             array($this, $method),
  29.             $arguments
  30.         );
  31.        
  32.         return $this;
  33.     }
  34.    
  35.     /**
  36.      * Return array stack of original return values generated by
  37.      * methods that were lost due to $this being returned instead.
  38.      *
  39.      * For a particular key, the return values for all calls to that
  40.      * method will be returned in array format in ascending
  41.      * chronological order.
  42.      */
  43.     public function return_stack($key = NULL)
  44.     {
  45.         if (isset($key))
  46.         {
  47.             return $this->return_stack[$key];
  48.         }
  49.        
  50.         return $this->return_stack;
  51.     }
  52. }

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.

toggle plain-text
  1. <?php
  2.  
  3. class Test extends Chain
  4. {  
  5.     /**
  6.      * Name functions according to Parent __call requirements to have
  7.      * them automagically return $this.
  8.      */
  9.     public function chain_hello()
  10.     {
  11.         print 'Hello';
  12.        
  13.         return TRUE;
  14.     }
  15.    
  16.     public function chain_make_array(array $array = NULL)
  17.     {
  18.         if (isset($array))
  19.         {
  20.             return $array;
  21.         }
  22.         else
  23.         {
  24.             return array('foo' => 'bar', 'baz' => 'faz');
  25.         }
  26.     }
  27. }

Another bit of test code:

toggle plain-text
  1. <?php
  2.  
  3. //get dependencies
  4. require('chainable.php');
  5. require('return_stackable.php');
  6. require('chain_stackable.php');
  7. require('test.php');
  8.  
  9. $test = new Test;
  10.  
  11. //execute a chain
  12. $test->hello()
  13. ->hello()
  14. ->make_array()
  15. ->make_array(array('food' => 'tacos'))
  16. ->make_array(array('food' => 'burritos'));
  17.  
  18. //fetch the return stack from functions
  19. $stack = $test->return_stack('make_array');
  20. 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. Try it out!

Back to Tutorials

};