Introduction
The purpose of a collection is to store objects in an organized manner with specific access rules. We are going to build a collection class using the Standard PHP Library (SPL). Our final product will be capable of iterating, counting and access to objects via array. If you are not familiar with SPL you can find some additional information on the PHP SPL manual site. Unfortunately the manual is somewhat lacking. Here is a more detailed list of SPL classes.
We will call our class “Collection” (original, I know). This class will eventually implement the SPL classes: ArrayAccess, Countable and Iterator. If you are unfamiliar with SPL your first reaction may be that you don’t have any of these classes in your project. The beauty of SPL is that the classes are built into PHP (compiled by default). Once you implement a class you are exposed to a series of abstract function which you must implement. Once implemented the result is the ability to manipulate objects and create functionality you wouldn’t normally be able to code.
First we are going to do a quick introduction to the interfaces we will be using. After that we will jump right into code and examples. Please keep in mind that the collection class we are going to build is more about demonstrating the capabilities of these particular SPL interfaces then actual practical use, although you could easily turn this into a useful class.
ArrayAccess
ArrayAccess is an interface that lets you override array access of objects. Basically it lets you use an object as an array and use keys to access values stored within the class.
Abstract Functions
offsetExists($offset)
offsetGet($offset)
offsetSet($offset, $value)
offsetUnset($offset)
Countable
Countable lets you hook into the global count function and override it. We are going to use it to count the number of objects in our collection.
Abstract Functions
count()
Iterator
Iterator allows you to iterator over an object. In our example you will be able to directly loop over a collection and return the objects that are stored.
Abstract Functions
current()
key()
next()
rewind()
valid()
Implementation
The first thing we need to do is define our class. It should look something like this:
<?php
class collection {
}
?>
Next we will define a constructor and a private variable called $object that will store the objects we pass into this collection. The constructor will be one way to populate the collection.
class collection {
private $objects = Array();
public function __construct($objects) {
if(!empty($objects)) {
$this->objects = $objects;
}
}
}
At this point our class doesn’t do much but take an array of objects and store then in the class. Next we will implement the SPL classes we discussed earlier.
Implement ArrayAccess
class collection implements ArrayAccess {
private $objects = Array();
public function __construct($objects) { ... }
/** ABSTRACT SPL ArrayAcess **/
public final function offsetExists($offset) {
if(isset($this->objects[$offset])){
return true;
} else {
return false;
}
}
public final function offsetGet($offset) {
return $this->objects[$offset];
}
public final function offsetSet($offset, $value) {
if($offset) {
$this->objects[$offset] = $value;
} else {
$this->objects[] = $value;
}
}
public final function offsetUnset($offset) {
unset($this->objects[$offset]);
}
// End ArrayAccess Abstraction
}
Implement Countable
Countable is really simple to implement and only requires one function. There is an interesting note in the code comment that you should make note of.
class collection implements ArrayAccess, Countable {
private $objects = Array();
public function __construct($objects) { ... }
/** ABSTRACT SPL ArrayAcess **/
...
// End ArrayAccess Abstraction
/** ABSTRACT SPL Countable **/
public function count() {
// Note from PHP manual: If var is not an array or an object with
// implemented Countable interface, 1 will be returned.
// Not sure why it returns one, but we need to make sure our
// count is right and returns zero when there are no objects
if(empty($this->objects)) {}
return 0;
} else {
return count($this->objects);
}
}
// End Countable Abstraction
Implement Iterator
class collection implements ArrayAccess, Countable, Iterator {
private $objects = Array();
public function __construct($objects) { ... }
/** ABSTRACT SPL ArrayAcess **/
...
// End ArrayAccess Abstraction
/** ABSTRACT SPL Countable **/
...
// End Countable Abstraction
/** ABSTRACT SPL Iterator **/
public function current() {
return current($this->objects);
}
public function key() {
return key($this->objects);
}
public function next() {
return next($this->objects);
}
public function rewind() {
if(empty($this->objects)) {
return false;
}
return reset($this->objects);
}
public function valid() {
if(empty($this->objects)) {
return false;
} else if(current($this->objects) === false) {
return false;
} else {
return true;
}
}
// END Iterator Abstraction
}
Final Collection
Our completed class isnt all that complicated. But its extremely powerful if used the right way. Hopefully you were able to follow along and understand what we have built. The final code is posted below as well as a test case in the next section. I recommend running the test to really see what the class is capable of doing.
<?php
class collection implements ArrayAccess, Countable, Iterator {
private $objects = Array();
public function __construct($objects) {
if(!empty($objects)) {
$this->objects = $objects;
}
}
/** ABSTRACT SPL ArrayAcess **/
public final function offsetExists($offset) {
if(isset($this->objects[$offset])){
return true;
} else {
return false;
}
}
public final function offsetGet($offset) {
return $this->objects[$offset];
}
public final function offsetSet($offset, $value) {
if($offset) {
$this->objects[$offset] = $value;
} else {
$this->objects[] = $value;
}
}
public final function offsetUnset($offset) {
unset($this->objects[$offset]);
}
// End ArrayAccess Abstraction
/** ABSTRACT SPL Countable **/
public function count() {
// Note from PHP manual: If var is not an array or an object with
// implemented Countable interface, 1 will be returned.
// Not sure why it returns one, but we need to make sure our
// count is right and returns zero when there are no objects
if(empty($this->objects)) {
return 0;
} else {
return count($this->objects);
}
}
// End Countable Abstraction
/** ABSTRACT SPL Iterator **/
public function current() {
return current($this->objects);
}
public function key() {
return key($this->objects);
}
public function next() {
return next($this->objects);
}
public function rewind() {
if(empty($this->objects)) {
return false;
}
return reset($this->objects);
}
public function valid() {
if(empty($this->objects)) {
return false;
} else if(current($this->objects) === false) {
return false;
} else {
return true;
}
}
// END Iterator Abstraction
}
?>
Testing and Demonstration
Test Code
<?php
/** FOO CLASS USED TO POPULATE OUR TEST COLLECTION */
class Foo {
public $value;
public function __construct($value) {
$this->value = $value;
}
}
/** DEMONSTRATION **/
$objects = Array();
for($x = 0; $x < 5; $x ++) {
$objects[] = new Foo($x);
}
$collection = new Collection($objects);
// offsetExists
if(isset($collection[3])) {
echo "Offset 3 exists\n";
} else {
echo "Offset 3 doesnt exist\n\n";
}
// offsetGet
echo "Value of offset 1: " . $collection[1]->value . "\n";
// offsetSet
$collection[3] = new Foo('A');
echo "Offset 3 has been changed, the object value is now: " . $collection[3]->value . "\n";
// offsetUnset
unset($collection[2]);
echo "Offset 2 has been removed. ";
if(isset($collection[2])) {
echo "Offset 2 exists\n";
} else {
echo "Offset 2 doesnt exist\n\n";
}
// Countable
echo "This collection has " . count($collection) . " objects\n\n";
// Iterating
echo "Looping through our collection:\n";
foreach($collection as $key => $object) {
echo "Index " . $key . " = " . $object->value . "\n";
}
?>
Test Output
Offset 3 exists
Value of offset 1: 1
Offset 3 has been changed, the object value is now: A
Offset 2 has been removed. Offset 2 doesnt exist
This collection has 4 objects
Looping through our collection:
Index 0 = 0
Index 1 = 1
Index 3 = A
Index 4 = 4

Posts
Since you’re using an internal structure to store objects, it would be simple to implement IteratorAggregate instead of Iterator. The external effects are identical :)
| October 25, 2009 @ 5:20 pm
I believe when using IteratorAggregate you create the abstract getIterator function used to initialize the iterator object. You could obviously do this transparently in the class and get access to a lot of the duplicate functionality I created. But the IteratorAggregate will create a duplicate of the internal data structure and wont automatically reflect changes.
| October 25, 2009 @ 10:32 pm
Mhh,
I think PHP already has a nice feature for object collections, it´s called SPLObjectStorage.
It´s already really powerfull in the late 5.2.x versions, but with the implementation of the ArrayAccess interface, since version 5.3, its all you need to work with object collections.
I also think, without a concrete benchmark in mind, it´s faster then the example shown above…
Feel free to correct me ;)
| November 2, 2009 @ 1:57 pm
SPLObjectStorage is definitely a useful class and serves its purpose well. My class offers a bit more flexibility, while SPLObjectStorage offers speed. SPLObjectStorage is FIFO (First In, First Out) and I also dont think it can be implemented? This limits class expansion and functionality. For example, in a future article I was going to add a seeking iterator to my object manager. This would not be possible with SPLObjectStorage. If you know the order of your objects and never need to sort or seek and expect things to return in the same predictable order SPLObjectStorage will be the faster and better option. Like for an active record pattern. If you want flexibility my class is a good alternative :)
| November 3, 2009 @ 1:16 am
Hi Adam, re. “Unfortunately the manual is somewhat lacking.” There is still a vast amount of work to do to fill out the SPL docs but the work is ongoing. Not too long ago we completed at least having some skeleton documentation (classes, methods, etc. listed) added to the manual for all of the available classes but that still leaves a mountain (or mole hill) to climb. More volunteers are always welcome, drop me an email if you are interested at all (whether writing some docs or just being helpful in pointing out what you would like).
| November 2, 2009 @ 6:17 pm