Collection Classes in PHP

    Alireza Rahmani Khalili
    Share

    A Collection class is an OOP replacement for the traditional array data structure. Much like an array, a collection contains member elements, although these tend to be objects rather than simpler types such as strings and integers.

    The general characteristics of a collection class are:

    • Establishes a wrapper around a array of objects.
    • Collections are mutable – new elements may be added and existing elements may be modified or removed.
    • Sorting algorithms are unstable (which means the order for equal elements is undefined).
    • Can use lazy instantiation to save system resources.

    Problem with Arrays

    Applications frequently have objects that contain a group of other objects, and this is a great place to make use of collections. For example, suppose we decide to create a book store system. Let’s say we’ve written a customer class which, among other things, holds a list of books that the customer would like to purchase:

    <?php
    class Customer 
    {
        public $items = array();
        // ...
    }
    
    $customer = new Customer(1234);
    foreach ($customer->items as $item) {
        echo $item->name;
    }

    If the most obvious approach (using an array) were the best possible approach, I wouldn’t write this article. The above example has these problems:

    • We’ve broken encapsulation – the array is exposed as a public member variable.
    • There is ambiguity in indexing and how to traverse the array to find a specific item.

    Additionally, to make sure the array is available for any code that might access it, we must populate the list of information from the database at the same time as the customer information. This means that even if we want to print just the customer’s name, we must fetch all of the item information, unnecessarily increasing load on the database and potentially bogging down the entire application.

    We can solve those problems by creating a collection class as an OOP wrapper around the array and use lazy instantiation. Lazy instantiation is the mechanism by which we create elements in the array only when we actually need it. It’s called “lazy” because the object determines on its own when to instantiate the component objects rather that blindly creating them when it is instantiated.

    A Basic Collection Class

    A collection class needs to expose methods that allow us to add, retrieve, and delete items, and it’s helpful to have a method that lets us know the size of the collection. So, a basic class would start like this:

    <?php
    class Collection 
    {
        private $items = array();
    
        public function addItem($obj, $key = null) {
        }
    
        public function deleteItem($key) {
        }
    
        public function getItem($key) {
        }
    }

    The $items array provides a location in which to store the objects that are members of the collection. addItem() lets us add a new object to the collection, deleteItem() removes an object, and getItem() returns an object.

    With addItem(), we add an object to the collection by putting it in the $items array at a specified location specified by $key (if no key is provided, we let PHP pick the next available index). If an attempt is made to add an object using a key that already exists, an exception should be thrown to prevent inadvertently overwriting existing information:

    public function addItem($obj, $key = null) {
        if ($key == null) {
            $this->items[] = $obj;
        }
        else {
            if (isset($this->items[$key])) {
                throw new KeyHasUseException("Key $key already in use.");
            }
            else {
                $this->items[$key] = $obj;
            }
        }
    }

    The deleteItem() and getItem() methods take the key as a parameter indicating which items are targeted for removal or retrieval. An exception should be thrown if an invalid key is supplied.

    public function deleteItem($key) {
        if (isset($this->items[$key])) {
            unset($this- >items[$key]);
        }
        else {
            throw new KeyInvalidException("Invalid key $key.");
        }
    }
    
    public function getItem($key) {
        if (isset($this->items[$key])) {
            return $this->items[$key];
        }
        else {
            throw new KeyInvalidException("Invalid key $key.");
        }
    }

    Because the $key parameter to the addItem() method is optional, we won’t necessarily know the key used for each item in the collection. Adding a method that can provide a list of keys to any external code that might need it is a good idea. The keys can be returned as an array:

    public function keys() {
        return array_keys($this->items);
    }

    It might also be helpful to know how many items are in the collection.

    public function length() {
        return count($this->items);
    }

    And because getItem() and deleteItem() can throw an exception if an invalid key is passed, a means of determining whether a given key exists in the collection is also a good idea.

    public function keyExists($key) {
        return isset($this->items[$key]);
    }

    To use the Collection class as it stands now, create the file Collection.php and save the code for the Collection class in it. Create files for the KeyInvalidException and KeyhasUseException classes, too (they can be simple sub-classes of the base Exception class). Be sure to add require statements if you’re not using an autoloader, and then try the following test code:

    <?php
    require_once "Collection.php";
    
    class Salut
    {
        private $name;
        private $number;
    
        public function __construct($name, $number) {
            $this->name = $name;
            $this->number = $number;
        }
    
        public function __toString() {
            return $this->name . " is number " . $this->number;
        }
    }
    
    $c = new Collection();
    $c->addItem(new Salut("Steve", 14), "steve");
    $c->addItem(new Salut("Ed", 37), "ed");
    $c->addItem(new Salut("Bob", 49), "bob");
    
    $c->deleteItem("steve");
    
    try {
        $c->getItem("steve");
    }
    catch (KeyInvalidException $e) {
        print "The collection doesn't contain Steve.";
    }

    This example may not be particularly interesting, but it should give you an idea of how the class is used.

    Conclusion

    Collections can be seen as more-specialized way of working with lists for which certain contracts are guaranteed. A Collection class is a very useful OO alternative to the traditional array, and one that can be implemented in virtually any application you might build. It provides careful management of its members and a consistent API that makes it easy to write code that uses the class.