Give the Traversable Interface Some Love

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 paragraph 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() { ... }

    // ...