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