Navigation

Every once in a while, I get into an argument about extending PHP’s Traversable interface instead of the Iterator interface in custom interfaces. The confusion seems to result from the official documentation of Traversable. Let me clarify why you should give Traversable much more love than you do right now.

Let’s start with a simple use case. Let’s create an interface ArrayInterface that demarcates objects that behave like PHP arrays. The interface should allow for counting, iterating and array access. See this short example for a code using this interface:

$arrayObject = /* some object implementing ArrayInterface */;
 
echo count($arrayObject) . "\n";
echo $arrayObject[count($arrayObject) - 1] . "\n";
 
foreach ($arrayObject as $key => $value) {
    echo $key . ': ' . $value . "\n";
}
 
$arrayCopy = iterator_to_array($arrayObject);

How would you implement ArrayInterface? The common solution is to choose among the following two options:

  1. Extending Iterator
    interface ArrayInterface extends \Countable, \ArrayAccess, \Iterator;
  2. Extending IteratorAggregate
    interface ArrayInterface extends \Countable, \ArrayAccess, \IteratorAggregate;

In the first case, implementations of ArrayInterface must provide the methods current(), key(), next(), rewind() and valid(). In the second case, they must implement a simple getIterator() method returning an iterator. All of these methods are required by the PHP engine to know how to do the actual iteration. But you, the user ArrayInterface, will never use them. You simply want to iterate (at least in 99% of the cases; for the other 1% this post does not apply).

Let the Traversable interface come to rescue. From its documentation:

Interface to detect if a class is traversable using foreach.

Sounds perfect. Let’s read on:

This is an internal engine interface which cannot be implemented in PHP scripts. Either IteratorAggregate or Iterator must be used instead. When implementing an interface which extends Traversable, make sure to list IteratorAggregate or Iterator before its name in the implements clause.

And this seems to be the source of confusion. Don’t misread this pragraph as “this interface is internal, please don’t use it”. It is not! What the paragraph states is that classes cannot implement this interface without also implementing IteratorAggregate or Iterator at the same time. As stated in the last sentence, it is very valid to extend the interface in your own interfaces to leave the choice of the concrete iterator implementation to the implementing class.

With all this knowledge in mind, let’s finally implement  ArrayInterface:

interface ArrayInterface extends \Countable, \ArrayAccess, \Traversable;

Implementing classes can now freely choose to implement either Iterator or IteratorAggregate:

class ArrayImpl1 implements \Iterator, ArrayInterface
{
    public function current() { ... }
    public function key() { ... }
    public function next() { ... }
    public function rewind() { ... }
    public function valid() { ... }
 
    // ...
}
 
class ArrayImpl2 implements \IteratorAggregate, ArrayInterface
{
    public function getIterator() { ... }
 
    // ...
}

Posted Sunday, October 7th, 2012 at 13:15
Written by: | Filed Under Category: Best Practices
You can leave a response, or trackback from your own site.

6

Responses to “Give the Traversable Interface Some Love”

Stof

There is a typo in your last example. you should implement the interfaces, not extend them.

Bernhard

Stoffed… Thanks, fixed!

puedes dar un ejemplo del codigo fuente de symfony2 por ejemplo para que casos se extiende con impl1 y para que casos se extiende con impl2 y cuales fueron las ventajas de darle libertad al desarrollador para que implemente o unos metodos u otros todo esto sobre como la manera en que se hacia en previas versiones?

Bernhard

In Symfony2, there are only a handful of interfaces extending Traversable (FormInterface, FormBuilderInterface, PropertyPathInterface, ResultStatement) and none that extend Iterator or IteratorAggregate. The benefit is quite clearly that, for example when implementing ResultStatement, it is up to you whether you preload all row of that result and return a standard ArrayIterator over the rows (IteratorAggregate), or whether you want more fine-grained control over what happens in each step of the iteration (Iterator).

bas

interfacing an abstract or base class with \Traversable allows for ducktyping on iteratorable arguments right? instead on checking on type we check on functional requirement (in my opinion a interface should only include these)

Bernhard

@bas: Exactly, you summed it up pretty well.

Leave a Reply

 

Additional Resources