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:</>
1 | Iterator extends Traversable { |
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 ) |
When we follow this interface, we create our working CollectionIterator class.
1 | class CollectionIterator implements Iterator { |
5 | private $Collection = null; |
9 | private $currentIndex = 0; |
19 | public function __construct(Collection $Collection ){ |
21 | $this ->Collection = $Collection ; |
23 | $this ->keys = $Collection ->keys(); |
31 | public function current(){ |
32 | return $this ->Collection->get( $this ->key()); |
40 | public function key(){ |
41 | return $this ->keys[ $this ->currentIndex]; |
49 | public function next(){ |
50 | ++ $this ->currentIndex; |
58 | public function rewind (){ |
59 | $this ->currentIndex = 0; |
67 | public function valid(){ |
68 | return isset( $this ->keys[ $this ->currentIndex]); |
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.
6 | class ECollectionKeyInUse extends Exception { |
8 | public function __construct( $key ){ |
9 | parent::__construct( 'Key ' . $key . ' already exists in collection' ); |
19 | class ECollectionKeyInvalid extends Exception { |
21 | public function __construct( $key ){ |
22 | parent::__construct( 'Key ' . $key . ' does not exist in collection' ); |
29 | class Collection implements IteratorAggregate { |
34 | private $data = array (); |
41 | public function getIterator(){ |
42 | return new CollectionIterator( $this ); |
52 | public function add( $item , $key = null){ |
55 | $this ->data[] = $item ; |
59 | if (isset( $this ->data[ $key ])) |
60 | throw new ECollectionKeyInUse( $key ); |
62 | $this ->data[ $key ] = $item ; |
70 | public function get( $key ){ |
71 | if (isset( $this ->data[ $key ])) |
72 | return $this ->data[ $key ]; |
74 | throw new ECollectionKeyInvalid( $key ); |
82 | public function remove( $key ){ |
84 | if (!isset( $this ->data[ $key ])) |
85 | throw new ECollectionKeyInvalid( $key ); |
87 | unset( $this ->data[ $key ]); |
93 | public function getAll(){ |
101 | public function keys(){ |
102 | return array_keys ( $this ->data); |
108 | public function length(){ |
109 | return count ( $this ->data); |
117 | public function clear(){ |
118 | $this ->data = array (); |
124 | public function exists( $key ){ |
125 | return isset( $this ->data[ $key ]); |
Now we have a working Collection class with methods like add, remove, clear(), etc.
Simple example script for this Collection class:
8 | $Collection = new Collection; |
10 | $Collection ->add( 'Circle' ); |
11 | $Collection ->add( 'Square' ); |
12 | $Collection ->add( 'Line' ); |
14 | foreach ( $Collection as $key => $item ){ |
15 | echo "Item($key): $itemn" ; |
19 | $Collection ->remove(1); |
21 | foreach ( $Collection as $item ){ |
22 | echo "Item($key): $itemn" ; |
When you run this example, you should see the output like this:
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.
7 | require ( 'Collection.class.php' ); |
9 | class LoadableCollection extends Collection { |
15 | private $onLoad = null; |
20 | private $isLoaded = false; |
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' ); |
37 | $this ->onLoad = $callback ; |
47 | protected function checkCallback(){ |
48 | if (! $this ->isLoaded){ |
49 | $this ->isLoaded = true; |
50 | if ( $this ->onLoad === NULL){ |
54 | if (method_exists( $this , 'load' )){ |
55 | $this ->onLoad = array ( $this , 'load' ); |
58 | throw new ECollectionCallbackInvalid( 'No valid callback set and no load() method found' ); |
61 | call_user_func( $this ->onLoad, $this ); |
74 | public function addItem( $item , $key = null){ |
75 | $this ->checkCallback(); |
76 | parent::addItem( $item , $key ); |
84 | public function getItem( $key ){ |
85 | $this ->checkCallback(); |
86 | return parent::getItem( $key ); |
94 | public function getItems(){ |
95 | $this ->checkCallback(); |
96 | return parent::getItems(); |
104 | public function removeItem( $key ){ |
105 | $this ->checkCallback(); |
106 | parent::removeItem( $key ); |
114 | public function exists( $key ){ |
115 | $this ->checkCallback(); |
116 | return parent::exists( $key ); |
122 | public function keys(){ |
123 | $this ->checkCallback(); |
124 | return parent::keys(); |
130 | public function length(){ |
131 | $this ->checkCallback(); |
132 | return parent::length(); |
137 | public function clear(){ |
138 | $this ->checkCallback(); |
139 | return parent::clear(); |
145 | public function unload(){ |
147 | $this ->isLoaded = false; |
148 | $this ->onLoad = null; |
Here’s an example class and script to test the functionality.
1 | require ( 'LoadableCollection.class.php' ); |
3 | class MyCars extends LoadableCollection { |
5 | public function load(){ |
7 | echo "Loading collection ...n" ; |
8 | $this ->addItem( 'Audi' ); |
10 | $this ->addItem( 'Mercedes' ); |
11 | echo "Collection loadedn" ; |
16 | echo "Creating MyCars objectn" ; |
18 | echo "MyCars object createdn" ; |
19 | echo "Note that object was not loaded yet.n" ; |
21 | foreach ( $c as $key => $value ){ |
When you run the script, you should see something like this:
4 | Note that the object was not loaded yet |
That’s it for iterator for collections in PHP. Hope it will help you.
Tags: array, Collection, Iterator, object, OOP, PHP
This entry was posted on September 14th, 2011
and is filed under PHP.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response or Trackback from your own site.