Iterator for collections in PHP

In this article, I’ll try to explain how to use PHP’s Iterator and IteratorAggregate interfaces to create objects that can act as array in your code, simplify loading and protect your data. We’ll also create an example on how to create “loadable” collection – a collection or array that loads only when it’s needed. But first, we have to create our base classes that will provide the base functions for our Collection class.

Creating the Collection class

First we need to create the iterator for our collection. We have to use Iterator interface provided by PHP, to make sure we define all methods required. Here’s an interface in general:</>

1Iterator extends Traversable {
2  /* Methods */
3  abstract public mixed current ( void )
4  abstract public scalar key ( void )
5  abstract public void next ( void )
6  abstract public void rewind ( void )
7  abstract public boolean valid ( void )
8}

When we follow this interface, we create our working CollectionIterator class.

1class CollectionIterator implements Iterator {
2  /**
3   * This is our collection class, defined later in article.
4   */
5  private $Collection = null;
6  /**
7   * Current index
8   */
9  private $currentIndex = 0;
10  /**
11   * Keys in collection
12   */
13  private $keys = null;
14 
15  /**
16   * Collection iterator constructor
17   *
18   */
19  public function __construct(Collection $Collection){
20    // assign collection
21    $this->Collection = $Collection;
22    // assign keys from collection
23    $this->keys = $Collection->keys();
24  }
25 
26  /**
27   * Implementation of method current
28   *
29   * This method returns current item in collection based on currentIndex.
30   */
31  public function current(){
32    return $this->Collection->get($this->key());
33  }
34 
35  /**
36   * Get current key
37   *
38   * This method returns current items' key in collection based on currentIndex.
39   */
40  public function key(){
41    return $this->keys[$this->currentIndex];
42  }
43 
44  /**
45   * Move to next idex
46   *
47   * This method increases currentIndex by one.
48   */
49  public function next(){
50    ++$this->currentIndex;
51  }
52 
53  /**
54   * Rewind
55   *
56   * This method resets currentIndex by setting it to 0
57   */
58  public function rewind(){
59    $this->currentIndex = 0;
60  }
61 
62  /**
63   * Check if current index is valid
64   *
65   * This method checks if current index is valid by checking the keys array.
66   */
67  public function valid(){
68    return isset($this->keys[$this->currentIndex]);
69  }
70}

Iterator is now defined. As you see in the code, there are few methods that have to be defined in our Collection class for this to be working. Let’s create our Collection class and start play with it. Based on interface IteratorAggregate we need to define the method getIterator to satisfy interface requirements.
In following code we’ll also define two exceptions that you might use. First is ECollectionKeyInUse, which will be thrown, when you try to insert an item in collection with key that already exists and the second one is ECollectionKeyInvalid, which will be thrown, when key you search for cannot be found in collection.

1/**
2 * Our ECollectionKeyInUse exception
3 *
4 * Thrown when you try to insert new item with key that already exists in collection.
5 */
6class ECollectionKeyInUse extends Exception {
7 
8  public function __construct($key){
9    parent::__construct('Key ' . $key . ' already exists in collection');
10  }
11}
12 
13/**
14 * Our ECollectionKeyInvalid exception
15 *
16 * Exception that will be thrown when you try to get an item with the key
17 * that does not exist in collection.
18 */
19class ECollectionKeyInvalid extends Exception {
20 
21  public function __construct($key){
22    parent::__construct('Key ' . $key . ' does not exist in collection');
23  }
24}
25 
26/**
27 * Our Collection class implementation
28 */
29class Collection implements IteratorAggregate {
30 
31  /**
32   * This is our array with data (collection)
33   */
34  private $data = array();
35 
36  /**
37   * Get iterator for this collection
38   *
39   * This method will return <b>CollectionIterator</b> object we wrote before.
40   */
41  public function getIterator(){
42    return new CollectionIterator($this);
43  }
44 
45  /**
46   * Add item to collection
47   *
48   * This method will add item to collection.
49   *
50   * If you do now provide the key, key will be next available.
51   */
52  public function add($item, $key = null){
53    if ($key === null){
54      // key is null, simply insert new data
55      $this->data[] = $item;
56    }
57    else {
58      // key was specified, check if key exists
59      if (isset($this->data[$key]))
60        throw new ECollectionKeyInUse($key);
61      else
62        $this->data[$key] = $item;
63    }
64  }
65 
66  /**
67   * Get item from collection by key
68   *
69   */
70  public function get($key){
71    if (isset($this->data[$key]))
72      return $this->data[$key];
73    else
74      throw new ECollectionKeyInvalid($key);
75  }
76 
77  /**
78   * Remove item from collection
79   *
80   * This method will remove item from collection.
81   */
82  public function remove($key){
83    // check if key exists
84    if (!isset($this->data[$key]))
85      throw new ECollectionKeyInvalid($key);
86    else
87      unset($this->data[$key]);
88  }
89 
90  /**
91   * Get all items in collection
92   */
93  public function getAll(){
94    return $this->data;
95  }
96 
97  /**
98   * Get all the keys in collection
99   *
100   */
101  public function keys(){
102    return array_keys($this->data);
103  }
104 
105  /**
106   * Get number of entries in collection
107   */
108  public function length(){
109    return count($this->data);
110  }
111 
112  /**
113   * Clear the collection
114   *
115   * This method removes all the item from the collection
116   */
117  public function clear(){
118    $this->data = array();
119  }
120 
121  /**
122   * Check if key exists in collection
123   */
124  public function exists($key){
125    return isset($this->data[$key]);
126  }
127}

Now we have a working Collection class with methods like addremoveclear(), etc.
Simple example script for this Collection class:

1/**
2 * Example code
3 *
4 * Creates new collection with three items,display collection
5 * remove one and display collection again.
6 */
7 
8$Collection = new Collection;
9 
10$Collection->add('Circle'); // 0
11$Collection->add('Square'); // 1
12$Collection->add('Line');   // 2
13 
14foreach($Collection as $key => $item){
15  echo "Item($key): $itemn";
16}
17 
18// remove square
19$Collection->remove(1);
20 
21foreach($Collection as $item){
22  echo "Item($key): $itemn";
23}

When you run this example, you should see the output like this:

1# ./collection.php
2Item(0): Circle
3Item(1): Square
4Item(2): Line
5Item(0): Circle
6Item(1): Line

Creating the loadable Collection class

You might have a scenario, where you have a lot of objects flying around and you don’t really know, which you’ll use and which to load, etc. One way of dealing with that is to simply load all, which might be very memory consuming. A much better solution is to have loadable collection class. Basically that means, when you request an item from collection, or any collection property, the collection will first load it self. Here’s a simple extension to Collection class, which enables this functionality.

1/**
2 * Simple loadable Collection class.
3 */
4/**
5 * Requires Collection class
6 */
7require('Collection.class.php');
8 
9class LoadableCollection extends Collection {
10 
11  /**
12   * Callback for loading memebers
13   * @var callback
14   */
15  private $onLoad = null;
16  /**
17   * Is collection already loaded
18   * @var bool
19   */
20  private $isLoaded = false;
21 
22  /**
23   * Set load items callback
24   *
25   * This method is called, if collection was not yet loaded.
26   * If you don't set the callback, default load method
27   * of object will be used.
28   *
29   * @param callback $callback
30   * @throws BECollectionCallbackInvalid if provided function/method cannot be called
31   */
32  public function setLoadCallback($callback){
33    if (!is_callable($callback, false, $callableName))
34      throw new ECollectionCallbackInvalid($callableName . ' is not callable as a parameter to onLoad');
35    }
36    else {
37      $this->onLoad = $callback;
38    }
39  }
40 
41  /**
42   * Check load callback
43   *
44   * This method is called by each method in this class, to see
45   * if it needs to call the load callback set by {@link LoadableCollection:setLoadCallback}.
46   */
47  protected function checkCallback(){
48    if (!$this->isLoaded){
49      $this->isLoaded = true;
50      if ($this->onLoad === NULL){
51        /**
52         * If no callback was set, set it to $object->load method.
53         */
54        if (method_exists($this, 'load')){
55          $this->onLoad = array($this, 'load');
56        }
57        else {
58          throw new ECollectionCallbackInvalid('No valid callback set and no load() method found');
59        }
60      }
61      call_user_func($this->onLoad, $this);
62    }
63  }
64 
65  /**
66   * Override all methods and add callback check.
67   */
68  /**
69   * Add item to collection
70   *
71   * @param mixed $item
72   * @param mixed $key
73   */
74  public function addItem($item, $key = null){
75    $this->checkCallback();
76    parent::addItem($item, $key);
77  }
78 
79  /**
80   * Get item
81   *
82   * @param mixed $key
83   */
84  public function getItem($key){
85    $this->checkCallback();
86    return parent::getItem($key);
87  }
88 
89  /**
90   * Get all items
91   *
92   * @return array
93   */
94  public function getItems(){
95    $this->checkCallback();
96    return parent::getItems();
97  }
98 
99  /**
100   * Remove item
101   *
102   * @param mixed $key
103   */
104  public function removeItem($key){
105    $this->checkCallback();
106    parent::removeItem($key);
107  }
108 
109  /**
110   * Check if key exists
111   *
112   * @param mixed $key
113   */
114  public function exists($key){
115    $this->checkCallback();
116    return parent::exists($key);
117  }
118 
119  /**
120   * Get keys
121   */
122  public function keys(){
123    $this->checkCallback();
124    return parent::keys();
125  }
126 
127  /**
128   * Get items count
129   */
130  public function length(){
131    $this->checkCallback();
132    return parent::length();
133  }
134  /**
135   * Clear the collection
136   */
137  public function clear(){
138    $this->checkCallback();
139    return parent::clear();
140  }
141 
142  /**
143   * Unload collection
144   */
145  public function unload(){
146    $this->clear();
147    $this->isLoaded = false;
148    $this->onLoad = null;
149  }
150}

Here’s an example class and script to test the functionality.

1require('LoadableCollection.class.php');
2 
3class MyCars extends LoadableCollection {
4 
5  public function load(){
6    // load the entire collection
7    echo "Loading collection ...n";
8    $this->addItem('Audi');
9    $this->addItem('BMW');
10    $this->addItem('Mercedes');
11    echo "Collection loadedn";
12  }
13 
14}
15 
16echo "Creating MyCars objectn";
17$c = new MyCars;
18echo "MyCars object createdn";
19echo "Note that object was not loaded yet.n";
20 
21foreach($c as $key => $value){
22  echo "$key: $valuen";
23}
24echo "Done.n";

When you run the script, you should see something like this:

1# ./example.php
2Creating MyCars object
3MyCars object created
4Note that the object was not loaded yet
5Loading collection ...
6Collection loaded ...
70: Audi
81: BMW
92: Mercedes
10Done.

That’s it for iterator for collections in PHP. Hope it will help you.


Tags: , , , , ,

 
 
 
Content not available.
Please allow cookies by clicking Accept on the banner

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close