The Travails of Traversal

13 07 2016

Flickr: By JacobDavis

The venerated foreach-loop may seem to have formed a part of PHP from the outset, but it actually entered the language starting with PHP 4, apparently appropriated from Perl. While nowadays, this control structure provides a convenient way to iterate over an array or object without the complication of conditional looping inherent in for, while, and do-while loops, it was originally intended to provide a way to easily traverse arrays (see PHP Manual published 3-6-2003).

The Manual describes the PHP 4 foreach as operating “… on a copy of the specified array, not the array itself, therefore the array pointer is not modified …” (see Manual ). So, in PHP 4, if you access and alter an array element in such a loop, the change is temporary, not permanent. So, in effect PHP4 induces a block scope for the array argument of foreach, a design which PHP5 abandons.

The PHP 4 foreach relies on an internal array pointer to reset an array to its first element. For PHP 5, the Manual cautions about the inadvisability of resetting the internal array pointer while the loop executes, mentioning that this practice could lead to unexpected behavior, certainly an undesirable prospect. The Manual declares that as of PHP 7, the improved foreach no longer utilizes the internal array pointer. In PHP4 as now, the internal array pointer only requires a manual reset when used with a conditional looping structure, such as while in combination with each(), as follows:

<?php
    $a = array("alpha", "beta", "gamma");
    while (list($v) = each($a)) {
        echo $v,"\n";
    }

// negelecting to reset
    echo "\n\nNo data available without reset\n\n";
    while (list($v) = each($a)) {
        echo $v,"\n";
    }
    
// with reset:
    reset($a);
    echo "\n\nData available after reset\n\n";
    while (list($v) = each($a)) {
        echo $v,"\n";
    }
?>

See live code.

The foreach garnered widespread affection for providing an easy, pleasurable way to iterate over an array or an object. One might have presumed that iteration in PHP had attained a pinnacle of perfection. Subsequently, a core contributor realized that adding a Traversable interface would improve matters more, if one were to question the suitability of employing this control structure with an object. More recently, another contributor sought to further improve PHP by suggesting an iterable keyword that would function like callable, and thereby eliminate the necessity for a Traverable type-hint for function parameters, while accepting various data entities. Such convenience comes at a price, one that may obscure the truth.

The Benefit and Limits of Foreach

The Manual explains that Traversable serves to inform the developer whether a foreach loop may iterate a class. PHP contributor AJF comments that the interface really applies to determining whether an object’s design incorporates an iterator. To refresh, an iterator as described by Wikipedia, denotes an object that iterates and accesses the values within a data container. The Standard PHP Library provides a basic interface which allows one to code a customized iterator. A developer must implement its abstract methods which will then automatically execute during the span of a foreach loop. Stephen Froelich penned an excellent article for Site Point detailing how to use this interface as well as the more conciseIteratorAggregate with its solitary getIterator() abstract method.

As an aside, you may wish to try a built-in customized iterator, such as theArrayIterator when traversing an ArrayObject. This iterator automatically executes in a loop structure as follows:

<?php

class test {
    public $a = ["a"=>"red","b"=>"white","c"=>"blue"];
    private $b = ["d"=>"turquoise","e","magenta"];


  public function getIterator(){
    return (new ArrayObject( $this->a + $this->b ))->getIterator();
  }
}
$it = (new test())->getIterator();
while ($it->valid()){
    echo $it->key(),".",$it->current(),"\n";
    echo $it->next();
} 

See live code.

The getIterator() method of the class returns an ArrayIterator which one can use to access the value of every element of the newly instantiated ArrayObject as long as you initialized the object appropriately to account for non-public properties. (You may wish to browse a blogpost I’ve previously written concerning the ArrayObject.) As a user points out at StackOverflow, the ArrayIterator by default ignores private and protected properties. Parenthetically-speaking, the code could have benefitted from using a foreach loop instead of the while structure, as follows:

<?php

class test {
    public $a = ["a"=>"red","b"=>"white","c"=>"blue"];
    private $b = ["d"=>"turquoise","e"=>"magenta"];


  public function getIterator(){
    return (new ArrayObject( $this->a + $this->b ))->getIterator();
  }
}
$it = (new test())->getIterator();
foreach ($it as $k => $v){
    echo $k, ".",$v, "\n";
} 

See live code.

In the foreach loop, the methods of the iterator automatically execute while it traverses the data.

A question may occur to one as to why PHP should need anything more than a simple foreach to access an object’s properties. Froelich answers that question in a previous article for Site Point, explaining that the main advantage consists in permitting a large data set to be handled one element at a time, far more efficient than if the entire data set were duplicated in memory as may happen under certain conditions with a foreach which Nikita Popov delineates. In sum, the chief advantage of employing an iterator results in greater granular control over iteration. The following trivial example illustrate the difference between the iteration power of a simple foreach and one combined with an iterator:

<?php

$a = [0,1,2,null,3];
foreach($a as $k => $v){
    echo $v;	// 0123
}
echo "\n";

class Test implements Iterator {
    private $p = 0;
    private $a = [0,1,2,null,3];
  
    public function __construct() {
        array_unshift( $this->a, 8 );
    }

    function rewind() {
        $this->p = 0;
    }

    function current() {
        return $this->a[$this->p];
    }

    function key() {
        return $this->p;
    }

    function next() {
        ++$this->p;
    }

    function valid() {
        return isset( $this->a[$this->p] );
    }
}

$it = new Test;

foreach($it as $k => $v) {
   echo $v;		// 8012
}
?>

See live code.

An object ranking as non-traversable signifies that its sourcecode lacks support for the Traversable interface, not necessarily that it is non-traversable. For example, when the Traversable type-hint checks a stdClass object, the object evaluates as “non-traversable. ” Yet, a foreach-loop possesses the ability to traverse this object, as the following example indicates:

 <?php  
function ForEachAble($obj){     
  foreach ($obj as $k => $v) {
      if ($v == null) return false;
      echo $v . "\n";
  }
  return true;
}
$x = new stdClass;
$x->bla  = 'blee';
$x->blue = 'blu';
$x->ble = 'eh';
echo “\n”,ForEachAble($x)? 'true' : 'false', "\n";

See live code.

The stdClass never implemented Traversable since the source code for the class predates the inception of this PHP 5 interface.

Inspecting Traversable

One might wonder why the PHP Internals List voting members hesitate to revise the stdClass so that it may support Traversable.. Such a task should require scant effort considering that the interface literally nothing to it, exemplifying an abstract base interface per the Manual, as you may note from perusing the code in the Standard PHP Library (SPL):

interface Traversable { }

While Traversable ensures that an object possesses an iterator, you may expand its original purpose by extending the interface according to the webmozart. The following rudimentary example illustrates that point:

<?php interface bla extends Traversable {
  /* returns and iterator and can greet, too */ 	
    public function hello(); 
}

class blee implements IteratorAggregate, bla {
     public $a = 1;
     public $b = 2;
     public $c = 3;
    
     function getIterator(){
       return new ArrayIterator( $this );
     }
     
     function hello(){
         echo 'hi!', "\n";
     }
}

($b = new blee)->hello();
foreach ($b as $k => $v){
    echo $v;
}

See live code.

Forcing Objects to Become Traversable

Apparently, the reluctance to modify the design of the stdClass stems from uncertainty about whether the purpose of the stdClass included iteration (see Nikita Popov’s comment and that of Aaron Piotrowski ). Until the stdClass should experience a redesign, users may force a stdClass object to behave as if it supported Transversable by decorating a stdClass object with an ArrayObject, as follows:

<?php  
function f(Traversable $x) {
  foreach ($x as $k =-> $v){
   echo $v;
  }
}
$a = json_decode('{"a": 1, "b": 2, "c": 3}');
$arrayobject = new ArrayObject($a);
f($arrayobject);

See live code.

A related challenge for developers involves manipulating customized classes so that they may adopt a Traversable quality, even though, unlike an internal class, they cannot directly implement this interface. User-defined classes must implement the Iterator or IteratorAggregate, since each of these interfaces in turn implement Traversable as the following script demonstrates:

<?php  
function f( Traversable $obj ){
   foreach ($obj as $k => $v){
    echo $v . "\n";
 }
}
class test2 implements IteratorAggregate {
  public $bla  = 'a';
  public $blee = 'b';
  public $blu  = 'c';
   
  public function getIterator() {
     return new ArrayIterator( $this );
  }
}

$t2 = new test2;
f($t2); 

See live code.

The Iterable and Potentially Irritable

Of note, a new RFC recently past muster with the PHP project and its implementation should appear in PHP 7.1. The new feature allows for developers to employ the keyword “iterable” instead of using the more restrictive Traversable type-hint in connection with determining the nature of a parameter. The net result affords more flexibility since parameters may consist of arrays, objects with iterators or generators. An object that fails to meet these criteria, such as one based on the stdClass, will be rejected despite the fact that one may iterate the object with a simple foreach-loop.

Those of a pragmatic mindset might propose amending stdClass so that it implements Traversable in order to avoid the oddity of subjecting a stdClass object to outright rejection for failing to qualify as iterable. A different solution which avoids tinkering with the internal class involves casting such an object to an array since the RFC deems arrays as iterable entities. However, one may criticize such a recommendation as offloading to Userland that for which PHPland ought to assume responsibility. The Closure class, another internal class, has experienced modification since it first appeared, so the precedent exists for enhancing an internal class in order to engender greater efficiency when coding.

This work is licensed under a Creative Commons License

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: