Basic Iterator interfaces

Iterator interface

The Iterator interface allows an object to be used in foreach loops, where the foreach loop implicitly calls the Iterator methods.

interface Iterator { // conceptual definitions
    public function mixed current ( void );
    public function scalar key ( void );
    public function void next ( void );
    public function void rewind ( void );
    public function boolean valid ( void );
}

class MyContainer implements Iterator {
   //...snip
}

The foreach loop below

$x = new MyClass(); // implements Iterator
//...add some elements
// iterate using ``foreach``
foreach($x as $current) {

    echo $current;
}

is equivalent to these explicit Iterator calls and for loop:

<?php
 $x->rewind();

 for( $x->rewind(); $x->valid(); $x->next()) {  // called each time

    $echo $x->current();

 }

Example implementations

This code shows how to implement an iterator for an array.

<?php
 class MyArrayIterator implements Iterator {

     array private $a;

     public function __construct(&$a)
     {
        $this->a = $a;
     }

     public function valid()
     {
        return current($this->a) !== false;
     }

     public function next()
     {
        next($this->a);
        return;
     }

     public function current()
     {
        return current($this->a);
     }

     public function rewind()
     {
        reset($this->a);
     }

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

And we would use it like so:

<?php
 $a = array('a' => "apple", 'b' => "bear", 'c' => "camel");

 $iter = new MyArrayIterator($a);
 foreach($iter as $key => $value) {

     echo "$key = $value\n";
 }

Since php provides a built-in array iterator, we could have simply used it:

<?php
 $a = array('a' => "apple", 'b' => "bear", 'c' => "camel");

 $iter = new ArrayIterator($a);
 foreach($iter as $key => $value) {

     echo "$key = $value\n";
 }

The code below shows how to implement a text file iterator:

<?php
class TextFileReadIterator implements Iterator {

    private $fh;
    private $line_no;
    private $text;

    public function __construct(string $filename)
    {
        $rc = fopen($filename, "r");

        if ($rc === FALSE) {
             throw new Exception($filename . "file could not be opened");
        }
        $this->fh = $rc;
        $this->rewind();
    }

    public function valid()
    {
        $rc = feof($this->fh);
        return ($rc === true) ? false : true;
    }

    public function next()
    {
        $this->text = fgets($this->fh);

        if ($this->text !== FALSE) {
            $this->line_no++;
        }
    }

    public function current()
    {
        return $this->text;
    }

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

    public function rewind()
    {
        fseek($this->fh, 0);
        $this->line_no = 0;
        $this->next();
    }
}

Which would be used like this:

<?php
include "TextFileIterator.php";

$fileiter = new TextFileReadIterator("/home/kurt/www/kurttest/spl/notes/spl-iterators.rst");

foreach($fileiter as $key => $value) {

    echo "Line number is " . $key . " text is \n$value\n";
}

The built–PHP SplFileObject class already provides this functionality. There are other PHP classes that support iteration. Examples include SimpleXMLElement, DomNodeList and PDOstatement.

IteratorAggregate interface

IteratorAggregate lets you implement your iterator externally. It has only one method, getIterator() that returns an Iterator.

IteratorAggregate extends Traversable {
    /* Methods */
    abstract public Traversable getIterator ( void )
}
class MyContainer implements IteratorAggregate {
   public function getIterator() { return new MyExternalIterator($this); }
}

class MyExternalIterator implements Iterator {
 //...snip
    public function __construct(MyContainer $member)
    {
      // Store $member locally for iteration
    }

    // Implementations for: current(), key(), next(), rewind() and valid() to iterate over data in $member
}

foreach also implicitly calls getIterator()

<?php
 $x = new SomeClass(); // implements IteratorAggregate
 //...add some elements
 // iterate using ``foreach``
 foreach($x as $element) { // calls SomeClass::getIterator()

     echo $element . "\n";
 }

Example implementation

The Composite Pattern can be implemented using IteratorAggregate. Below class Menu is the composite class. It implements IteratorAggregate to return an external iterator. The base class class of the containment hierarchy is MenuComponent. It too implements IteratorAggregate and returns an instance of EmptyIterator.

In the code below The Menu::__toString() method invokes foreach on its components—either submenus or menu items.

<?php
abstract class MenuComponent implements IteratorAggregate {

     public function add(MenuComponent  $m)
     {
         throw new \Exception("method MenuComponent::add not implemented");
     }
     public function remove(MenuComponent $m)
     {
         throw new \Exception("method MenuComponent::remove not implemented");
     }

     public function getName() : string
     {
         throw new \Exception("method MenuComponent::getName not implemented");
     }
     public function getDescription() : string
     {
         throw new \Exception("method MenuComponent::getDescription  not implemented");
     }
     public function getPrice() : string
     {
         throw new \Exception("method MenuComponent::getPrice not implemented");
     }
     public function isVegetarian() : bool
     {
         throw new \Exception("method MenuComponent::isVegetarian not implemented");
     }
     public function getIterator() : \Iterator
     {
          return new EmptyIterator();
     }
}

The MenuItem class looks like this

<?php
class MenuItem extends MenuComponent {

    private $name;
    private $description;
    private $vegetarian;
    private $price;

    public function __construct(string $name, string $description, bool $vegetarian, float $price)
    {
          $this->name = $name;
          $this->description = $description;
          $this->vegetarian = $vegetarian;
          $this->price = $price;
    }

    public function getName() : string
    {
          return $this->name;
    }

    public function getDescription() : string
    {
          return $this->description;
    }

    public function getPrice() : float
    {
          return $this->price;
    }

    public function isVegetarian() : bool
    {
          return $this->vegetarian;
    }

    public function __toString() : string
    {
          $str = "\n  " . $this->getName();

          if ($this->isVegetarian()) {

              $str .= "(v)";
          }

          $str .=  ", " . $this->getPrice();
          $str .= "     -- " . $this->getDescription();

          return $str;
    }
}

Finally, Menu looks like this

<?php

class Menu extends MenuComponent implements IteratorAggregate {

   private $menuComponents;
   private $name;
   private $description;

   public function __construct(string $name, string $description)
   {
       $this->description = $description;
       $this->name = $name;
       $this->menuComponents = array();
   }

   public function add(MenuComponent $menuComponent)
   {
       $this->menuComponents[spl_object_hash($menuComponent)] = $menuComponent;
   }

   public function remove(MenuComponent $menuComponent)
   {
       unset($this->menuComponents[\spl_object_hash($menuComponent)]);
   }

   public function getIterator() : \Iterator
   {
       echo "Menu::getIterator() just called\n";
       return new ArrayIterator($this->menuComponents);
   }

   public function getName() : string { return $this->name; }

   public function getDescription() : string { return $this->description; }

   public function __toString() : string
   {
       $str ="\n\n" . $this->getName();
       $str .= ", " . $this->getDescription();
       $str .= "\n\n---------------------";

       foreach ($this->menuComponents as $component) {

        $str .= $component; // calls component's __toString()
       }
       $str .= "\n\n";
       return $str;
  }
}

Finally, The test code looks like this

<?php
include "SplClassLoader.php";

$loader = new SplClassLoader();
$loader->register();

try {

    $pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast");

    $dinerMenu = new Menu("DINER MENU", "Lunch");

    $cafeMenu = new Menu("CAFE MENU", "Dinner");

    $dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");

    $coffeeMenu = new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee");

    $allMenus = new Menu("ALL MENUS", "All menus combined");

    $allMenus->add($pancakeHouseMenu);
    $allMenus->add($dinerMenu);
    $allMenus->add($cafeMenu);

    $pancakeItem = new MenuItem(
            "K&B's Pancake Breakfast",
            "Pancakes with scrambled eggs, and toast",
            true,
            2.99);

    $pancakeHouseMenu->add($pancakeItem);

    $pancakeHouseMenu->add(new MenuItem(
            "Regular Pancake Breakfast",
            "Pancakes with fried eggs, sausage",
            false,
            2.99));

    $pancakeHouseMenu->add(new MenuItem(
            "Blueberry Pancakes",
            "Pancakes made with fresh blueberries, and blueberry syrup",
            true,
            3.49));

    $pancakeHouseMenu->add(new MenuItem(
            "Waffles",
            "Waffles, with your choice of blueberries or strawberries",
            true,
            3.59));

    $dinerMenu->add(new MenuItem(
            "Vegetarian BLT",
            "(Fakin') Bacon with lettuce & tomato on whole wheat",
            true,
            2.99));
    $dinerMenu->add(new MenuItem(
            "BLT",
            "Bacon with lettuce & tomato on whole wheat",
            false,
            2.99));

    $dinerMenu->add(new MenuItem(
            "Soup of the day",
            "A bowl of the soup of the day, with a side of potato salad",
            false,
            3.29));

    $dinerMenu->add(new MenuItem(
            "Hotdog",
            "A hot dog, with saurkraut, relish, onions, topped with cheese",
            false,
            3.05));
    $dinerMenu->add(new MenuItem(
            "Steamed Veggies and Brown Rice",
            "Steamed vegetables over brown rice",
            true,
            3.99));

    $dinerMenu->add(new MenuItem(
            "Pasta",
            "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
            true,
            3.89));

    $dinerMenu->add($dessertMenu);

    $dessertMenu->add(new MenuItem(
            "Apple Pie",
            "Apple pie with a flakey crust, topped with vanilla icecream",
            true,
            1.59));

    $dessertMenu->add(new MenuItem(
            "Cheesecake",
            "Creamy New York cheesecake, with a chocolate graham crust",
            true,
            1.99));

    $dessertMenu->add(new MenuItem(
            "Sorbet",
            "A scoop of raspberry and a scoop of lime",
            true,
            1.89));

    $cafeMenu->add(new MenuItem(
            "Veggie Burger and Air Fries",
            "Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
            true,
            3.99));

    $cafeMenu->add(new MenuItem(
            "Soup of the day",
            "A cup of the soup of the day, with a side salad",
            false,
            3.69));

    $cafeMenu->add(new MenuItem(
            "Burrito",
            "A large burrito, with whole pinto beans, salsa, guacamole",
            true,
            4.29));

    $cafeMenu->add($coffeeMenu);

    $coffeeMenu->add(new MenuItem(
            "Coffee Cake",
            "Crumbly cake topped with cinnamon and walnuts",
            true,
        1.59));

    $coffeeMenu->add(new MenuItem(
            "Bagel",
            "Flavors include sesame, poppyseed, cinnamon raisin, pumpkin",
            false,
            0.69));

    $coffeeMenu->add(new MenuItem(
            "Biscotti",
            "Three almond or hazelnut biscotti cookies",
            true,
            0.89));

    echo "\n===================\n";

    foreach ($allMenus as $current) {

           echo $current;
    }

    $d = 10;

} catch (Exception $ex) {

   echo "Exception on line " . $ex->getLine() . " of file " . $ex->getFile() ."\n";
   echo $ex->getMessage();
}

Will return this output:

Menu::getIterator() just called

PANCAKE HOUSE MENU, Breakfast
---------------------
  K &amp; B's Pancake Breakfast(v), 2.99     -- Pancakes with scrambled eggs, and toast
  Regular Pancake Breakfast, 2.99     -- Pancakes with fried eggs, sausage
  Blueberry Pancakes(v), 3.49     -- Pancakes made with fresh blueberries, and blueberry syrup
  Waffles(v), 3.59     -- Waffles, with your choice of blueberries or strawberries


DINER MENU, Lunch
---------------------
  Vegetarian BLT(v), 2.99     -- (Fakin') Bacon with lettuce & tomato on whole wheat
  BLT, 2.99     -- Bacon with lettuce & tomato on whole wheat
  Soup of the day, 3.29     -- A bowl of the soup of the day, with a side of potato salad
  Hotdog, 3.05     -- A hot dog, with saurkraut, relish, onions, topped with cheese
  Steamed Veggies and Brown Rice(v), 3.99     -- Steamed vegetables over brown rice
  Pasta(v), 3.89     -- Spaghetti with Marinara Sauce, and a slice of sourdough bread
DESSERT MENU, Dessert of course!
---------------------
  Apple Pie(v), 1.59     -- Apple pie with a flakey crust, topped with vanilla icecream
  Cheesecake(v), 1.99     -- Creamy New York cheesecake, with a chocolate graham crust
  Sorbet(v), 1.89     -- A scoop of raspberry and a scoop of lime




CAFE MENU, Dinner
---------------------
  Veggie Burger and Air Fries(v), 3.99     -- Veggie burger on a whole wheat bun, lettuce, tomato, and fries
  Soup of the day, 3.69     -- A cup of the soup of the day, with a side salad
  Burrito(v), 4.29     -- A large burrito, with whole pinto beans, salsa, guacamole
COFFEE MENU, Stuff to go with your afternoon coffee
---------------------
  Coffee Cake(v), 1.59     -- Crumbly cake topped with cinnamon and walnuts
  Bagel, 0.69     -- Flavors include sesame, poppyseed, cinnamon raisin, pumpkin
  Biscotti(v), 0.89     -- Three almond or hazelnut biscotti cookies