Handling Collections of Domain Objects in PHP

In this fourth part of the series, I will build a simple countable iterator. It will be able to easily manipulate collections of entities by using an array-like notation. What’s more, if you frequently implement different kinds of inner iterators, then you’ll quickly understand the logic behind this one.

Although in many cases the pragmatic nature of PHP coupled to the functionality provided by well-known design patterns permit you to tackle a certain number of problems in a fairly efficient and elegant way, sometimes it’s necessary to appeal to more complex and abstract solutions. These are often used at the enterprise level, where large applications are the rule and not the exception.

That’s exactly the case with a Unit of Work (UoW). This powerful paradigm allows you to optimize operations performed on domain objects via a transactional model that resembles the one found in many RDBMS. Simply put, domain objects are queued in memory and scheduled for further insertion, updating or removal according to the requirements of client code. Finally, all of these operations are executed in one go, therefore decreasing accesses to the underlying storage mechanism.

As with any other design pattern, it’s feasible to implement a UoW using different programming languages, including PHP. In keeping with this concept, in earlier installments of this series I went through the development of a few loosely-coupled classes to demonstrate the functionality of the pattern. So far, I’ve created an abstract UoW, along with some additional dependencies, such as an abstract data mapper, an entity-modeling class and a simple MySQL adapter.

It’s valid to note, though, that aside from performing insertions, updates and deletions, the UoW must also be capable of pulling collections of entities out of the persistence layer. Since the simplest way to achieve this is by creating a countable iterator, in this fourth tutorial of the series I’m going to create one whose driving logic will be quite simple to grasp.

Ready to continue learning how to implement a Unit of Work in PHP? Then begin reading!

Review: building a basic data access layer

Prior to showing the iterator that will be responsible for manipulating collections of domain objects, I’d like to spend a few moments reintroducing the source code corresponding to the interface and the MySQL abstraction class developed in the preceding installment of this series. When used in conjunction, they implement a simple database adapter. 

Having said that, here’s the interface, whose signature looks as follows:

(DatabaseAdapterInterface.php)

<?php

interface DatabaseAdapterInterface
{
    function connect();
   
    function disconnect(); 
   
    function query($query);
   
    function fetch(); 
   
    function select($table, $where, $fields, $order, $limit, $offset);
   
    function insert($table, array $data);
   
    function update($table, array $data, $where);
   
    function delete($table, $where);
   
    function getInsertId();
   
    function countRows();
   
    function getAffectedRows();
}

As you’ll surely agree with me, understanding the structure of the above "DatabaseAdapterInterface" interface is a straightforward process. Since the interface establishes a contract that must be fulfilled by all of the database adapters created from this point onward, here’s the definition of the one that works specifically with MySQL. Check it out:

(MySQLAdapter.php)

<?php

class MySQLAdapter implements DataBaseAdapterInterface
{
    protected $_config = array();
    protected $_link;
    protected $_result;
    protected static $_instance;
   
    /**
     * Get the Singleton instance of the class
     */
    public static function getInstance(array $config = array())
    {
        if (self::$_instance === null) {
            self::$_instance = new self($config);
        }
        return self::$_instance;
    }
   
    /**
     * Class constructor
     */
    protected function __construct(array $config)
    {
        if (count($config) !== 4) {
            throw new MySQLAdapterException(‘Invalid number of connection parameters.’);  
        }
        $this->_config = $config;
    }
   
    /**
     * Prevent cloning the instance of the class
     */
    protected function __clone(){}
   
    /**
     * Connect to MySQL
     */
    public function connect()
    {
        // connect only once
        if ($this->_link === null) {
            list($host, $user, $password, $database) = $this->_config;
            if ((!$this->_link = @mysqli_connect($host, $user, $password, $database))) {
                throw new MySQLAdapterException(‘Error connecting to MySQL : ‘ . mysqli_connect_error());
            }
            unset($host, $user, $password, $database);      
        }
    }

    /**
     * Execute the specified query
     */
    public function query($query)
    {
        if (!is_string($query) || empty($query)) {
            throw new MySQLAdapterException(‘The specified query is not valid.’);  
        }
        // lazy connect to MySQL
        $this->connect();
        if (!$this->_result = mysqli_query($this->_link, $query)) {
            throw new MySQLAdapterException(‘Error executing the specified query ‘ . $query . mysqli_error($this->_link));
        }
    }
   
    /**
     * Perform a SELECT statement
     */
    public function select($table, $where = ”, $fields = ‘*’, $order = ”, $limit = null, $offset = null)
    {
        $query = ‘SELECT ‘ . $fields . ‘ FROM ‘ . $table
               . (($where) ? ‘ WHERE ‘ . $where : ”)
               . (($limit) ? ‘ LIMIT ‘ . $limit : ”)
               . (($offset && $limit) ? ‘ OFFSET ‘ . $offset : ”)
               . (($order) ? ‘ ORDER BY ‘ . $order : ”);
        $this->query($query);
        return $this->countRows();
    }
   
    /**
     * Perform an INSERT statement
     */ 
    public function insert($table, array $data)
    {
        $fields = implode(‘,’, array_keys($data));
        $values = implode(‘,’, array_map(array($this, ‘quoteValue’), array_values($data)));
        $query = ‘INSERT INTO ‘ . $table . ‘(‘ . $fields . ‘)’ . ‘ VALUES (‘ . $values . ‘)';
        $this->query($query);
        return $this->getInsertId();
    }
   
    /**
     * Perform an UPDATE statement
     */
    public function update($table, array $data, $where = ”)
    {
        $set = array();
        foreach ($data as $field => $value) {
            $set[] = $field . ‘=’ . $this->quoteValue($value);
        }
        $set = implode(‘,’, $set);
        $query = ‘UPDATE ‘ . $table . ‘ SET ‘ . $set
               . (($where) ? ‘ WHERE ‘ . $where : ”);
        $this->query($query);
        return $this->getAffectedRows(); 
    }
   
    /**
     * Perform a DELETE statement
     */
    public function delete($table, $where = ”)
    {
        $query = ‘DELETE FROM ‘ . $table
               . (($where) ? ‘ WHERE ‘ . $where : ”);
        $this->query($query);
        return $this->getAffectedRows();
    }
   
    /**
     * Single quote the specified value
     */
    public function quoteValue($value)
    {
        if ($value === null) {
            $value = ‘NULL';
        }
        else if (!is_numeric($value)) {
            $value = "’" . mysqli_real_escape_string($this->_link, $value) . "’";
        }
        return $value;
    }
   
    /**
     * Fetch a single row from the current result set (as an associative array)
     */
    public function fetch()
    {
        if ($this->_result !== null) {
            if ((!$row = mysqli_fetch_array($this->_result, MYSQLI_ASSOC))) {
                $this->freeResult();
                return false;
            }
            return $row;
        }
    }

    /**
     * Get the insertion ID
     */
    public function getInsertId()
    {
        return $this->_link !== null ?
               mysqli_insert_id($this->_link) :
               null; 
    }
   
    /**
     * Get the number of rows returned by the current result set
     */ 
    public function countRows()
    {
        return $this->_result !== null ?
               mysqli_num_rows($this->_result) :
               0;
    }
   
    /**
     * Get the number of affected rows
     */
    public function getAffectedRows()
    {
        return $this->_link !== null ?
               mysqli_affected_rows($this->_link) :
               0;
    }
   
    /**
     * Free up the current result set
     */
    public function freeResult()
    {
        if ($this->_result !== null) {
            mysqli_free_result($this->_result);  
        }
    }
   
    /**
     * Close explicitly the database connection
     */
    public function disconnect()
    {
        if ($this->_link !== null) {
            mysqli_close($this->_link);
            $this->_link = null;
        }
    }
   
    /**
     * Close automatically the database connection when the instance of the class is destroyed
     */
    public function __destruct()
    {
        $this->disconnect();
    }
}

 

(MySQLAdapterException.php)

<?php

class MySQLAdapterException extends Exception{}

Since the above MySQL handling class was discussed in depth in a previous tutorial of the series, I’m not going to waste your valuable time (and mine, of course) explaining how it works one more time. The only detail worth stressing here is that the class is an implementer of the earlier interface, which means that it defines some discrete methods that allow it to perform CRUD operations on a selected table.

If you’re asking yourself what the point is in coding a class like this, let me remind you that it’ll be used by the sample UoW to insert, update and delete domain objects from a specific MySQL database. But before you have the chance to see this process in action, it’s necessary to create a few additional classes, including one responsible for handling collections of entities.

In the next segment I’m going to define such a class, so if you want to learn more on this topic, just jump ahead and read the lines to come.

{mospagebreak title=Building a class to handle collections of entities}

Actually, there are several approaches that can be used for handling collections of entities in a simple way. In this case, however, I will rely on building a countable iterator, which will also be able to  access stored entities such as plain array elements.

In fact, I developed a similar iterator when I discussed how to implement a repository in PHP here (http://www.devshed.com/c/a/PHP/Roll-Your-Own-Repository-in-PHP-Working-with-Collections-of-Entities/1/), so if you’ve already read that tutorial, you’ll find the logic of the class below quite familiar. Here it is:

(CollectionAbstract.php)

<?php

abstract class CollectionAbstract implements Iterator, Countable, ArrayAccess
{
    protected $_entities = array();
   
    /**
     * Get the entities stored in the collection
     */
    public function getEntities()
    {
        return $this->_entities;
    }
   
    /**
     * Clear the collection of entities
     */
    public function clear()
    {
        $this->_entities = array();
    }
   
    /**
     * Reset the collection of entities (implementation required by Iterator Interface)
     */
    public function rewind()
    {
        reset($this->_entities);
    }
   
    /**
     * Get the current entity in the collection (implementation required by Iterator Interface)
     */ 
    public function current()
    {
        return current($this->_entities);
    }
   
    /**
     * Move to the next entity in the collection (implementation required by Iterator Interface)
     */
    public function next()
    {
        next($this->_entities);
    }
   
    /**
     * Get the key of the current entity in the collection (implementation required by Iterator Interface)
     */
    public function key()
    {
        return key($this->_entities);
    }
   
    /**
     * Check if there are more entities in the collection (implementation required by Iterator Interface)
     */
    public function valid()
    {
        return (boolean) $this->current();
    }
   
    /**
     * Count the number of entities in the collection (implementation required by Countable Interface)
     */
    public function count()
    {
        return count($this->_entities);
    }
   
    /**
     * Add an entity to the collection (implementation required by ArrayAccess interface)
     */
}

The above "CollectionAbstract" class is an implementer of the Iterator, Countable and ArrayAccess native PHP interfaces, which makes it possible to build an API that permits you to traverse collections of entities using a "foreach" construct, and even count the number of entities present in a specific collection.

Of course, if you try the class in its current state, you’ll get an ugly fatal error from the PHP engine. It still doesn’t implement the methods declared by the ArrayAccess interface. It’s time to add this functionality to the class, so you can see how it looks when finished.

This will be accomplished in the lines to come, so keep reading.

Implementing the ArrayAccess PHP interface

If you want to see how the earlier "CollectionAbstract" class looks after implementing the set of methods defined by the ArrayAccess PHP interface, below I included the class’ full source code. This way you can analyze it in a more detailed manner. Here it is:

(CollectionAbstract.php)

<?php

abstract class CollectionAbstract implements Iterator, Countable, ArrayAccess
{
    protected $_entities = array();
   
    /**
     * Get the entities stored in the collection
     */
    public function getEntities()
    {
        return $this->_entities;
    }
   
    /**
     * Clear the collection of entities
     */
    public function clear()
    {
        $this->_entities = array();
    }
   
    /**
     * Reset the collection of entities (implementation required by Iterator Interface)
     */
    public function rewind()
    {
        reset($this->_entities);
    }
   
    /**
     * Get the current entity in the collection (implementation required by Iterator Interface)
     */ 
    public function current()
    {
        return current($this->_entities);
    }
   
    /**
     * Move to the next entity in the collection (implementation required by Iterator Interface)
     */
    public function next()
    {
        next($this->_entities);
    }
   
    /**
     * Get the key of the current entity in the collection (implementation required by Iterator Interface)
     */
    public function key()
    {
        return key($this->_entities);
    }
   
    /**
     * Check if there are more entities in the collection (implementation required by Iterator Interface)
     */
    public function valid()
    {
        return (boolean) $this->current();
    }
   
    /**
     * Count the number of entities in the collection (implementation required by Countable Interface)
     */
    public function count()
    {
        return count($this->_entities);
    }
   
    /**
     * Add an entity to the collection (implementation required by ArrayAccess interface)
     */
    public function offsetSet($key, $entity)
    {
        if ($key === null) {
            if (!in_array($key, $this->_entities, true)) {
                $this->_entities[] = $entity;
                return;
            }
        }
        else if (!array_key_exists($key, $this->_entities)) {
            $this->_entities[$key] = $entity;
        }
    }
   
    /**
     * Remove an entity from the collection (implementation required by ArrayAccess interface)
     */
    public function offsetUnset($key)
    {
        if ($key instanceof EntityAbstract) {
            $entities = array();
            foreach ($this->_entities as $_entity) {
                if ($_entity !== $key) {
                    $entities[] = $_entity;
                }  
            }
            $this->_entities = $entities;
            return;
        }
        if (array_key_exists($key, $this->_entities)) {
            unset($this->_entities[$key]);
        }
    }
   
    /**
     * Get the specified entity from the collection (implementation required by ArrayAccess interface)
     */
    public function offsetGet($key)
    {
        return array_key_exists($key, $this->_entities) ?
               $this->_entities[$key] :
               null;
    } 
   
    /**
     * Check if the specified entity exists in the collection (implementation required by ArrayAccess interface)
     */    
    public function offsetExists($key)
    {
        return array_key_exists($key, $this->_entities);
    }
}

Mission accomplished, at least for now. Although the definition of this class seems to be somewhat irrelevant to the construction of a fully-functional Unit of Work, this is simply a misleading impression, trust me. When I show you how to put this class and others to work together, you’ll see how neatly each piece fits into the schema dictated by the pattern.

Of course, this shouldn’t stop you from playing with all of the code samples shown in this tutorial and tweaking them at will. So go ahead. You’ll have a pretty good time, guaranteed! 

Final thoughts

In this fourth episode of the series, I built a simple countable iterator which could manipulate collections of entities by using an array-like notation. What’s more, if you frequently implement different kinds of inner iterators, then the driving logic of the one coded in this tutorial should be fairly easy to grasp for you.

While it could be said that the scenario is finally set to start using the sample UoW developed in the first installment of the series (remember that one, right?), there’s one more step that we still must take.  Since in most cases the domain layer of an application will be comprised of different kinds of entities, namely blog posts, comments, users and so forth, it’d be really helpful to create an abstraction layer that permits us to swap multiple UoWs at runtime.

To fit this requirement, in the upcoming tutorial I’m going to create an entity manager class (basically a very slimmed down version of the one included with Doctrine 2.0). It will be capable of handling entities by switching over distinct UoWs.

Want to see how this will be done? Then don’t miss the next part! 

[gp-comments width="770" linklove="off" ]

chat