Building a Unit of Work in PHP: Creating a Data Mapper and Handling Entities

In this second tutorial of the series, I add to the previous UoW class a pair of collaborators that it needs to function properly. These are an abstract data mapper tasked with interacting with the persistence layer, and an additional abstract class responsible for modeling generic entities.

A Unit of Work (UoW) is an enterprise-level design pattern that permits you to apply a transactional model to the domain objects that integrate an application, to synchronize and optimize the operations performed on them. Even though this definition may sound hard to grasp, and like it concerns the most esoteric facets of the OOP paradigm, the truth is that understanding the logic that drives this pattern is a fairly straightforward process, especially when using an easy-going language like PHP.

Of course, highlighting the benefits of a UoW from a theoretical standpoint would be pretty pointless if the process isn’t backed up with some functional code samples. In the introductory installment of this series, I went through the development of an abstract UoW class. According to the model imposed by the pattern, it was provided with the ability to “mark” entities for further insertion, update and removal via a set of discrete methods. Lastly, a call to the class’s “commit()” method allowed it to perform all these queued operation in the persistence layer in just one go.

Although the current implementation of this sample UoW class is clear enough to grasp very quickly, it’s valid to point out that the class takes a couple of collaborators that make it work as expected. The first one is a data mapper, which in turn executes the queued operations in the underlying persistence mechanism, while the second one is an instance of a class responsible for modeling generic entities. For obvious reasons, before you see the UoW up and running, it’s necessary to define the originating classes of these dependencies.

Given that, in this second chapter of the series I’m going to show you these brand new classes, so you can see how they fit in the schema dictated by the UoW. Now, jump forward and start reading!

Review: performing domain objects operations in a transactional manner

As usual, before I create the classes that model the two dependencies just mentioned, it’d be useful to recall the definition of the abstract UoW built in the preceding chapter. This sample UoW is capable, among other things, of setting entities for further insertion, updating and deletion. Its full source code is shown below. Pay close attention to it, please:

(UnitOfWorkAbstract.php)

<?php

abstract class UnitOfWorkAbstract
{
    protected $_newEntities = array();
    protected $_cleanEntities = array();
    protected $_dirtyEntities = array();
    protected $_removedEntities = array();
    protected $_dataMapper;
   
    /**
     * Class constructor
     */
    public function __construct(DataMapperAbstract $dataMapper)
    {
        $this->_dataMapper = $dataMapper;
    }
   
    /**
     * Get the data mapper the Unit of Work uses
     */         
    public function getDataMapper()
    {
        return $this->_dataMapper;
    }
   
    /**
     * Mark an entity ‘new’
     */
    public function markNew(EntityAbstract $entity)
    {
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
        }
        if (!in_array($entity, $this->_newEntities, true)) {
            $this->_newEntities[] = $entity;
        }
    }
   
    /**
     * Remove an entity previously marked ‘new’
     */
    protected function _removeNew(EntityAbstract $entity)
    {
        if (in_array($entity, $this->_newEntities, true)) {
            $newEntities = array();
            foreach ($this->_newEntities as $_newEntity) {
                if ($entity !== $_newEntity) {
                    $newEntities[] = $_newEntity;
                }
            }
            $this->_newEntities = $newEntities;
        }
    }
   
    /**
     * Get all the ‘new’ entities
     */
    public function getNewEntities()
    {
        return $this->_newEntities;
    }
   
    /**
     * Mark an entity ‘clean’
     */
    public function markClean(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
            if (!array_key_exists($id, $this->_cleanEntities)) {
                $this->_cleanEntities[$id] = $entity;
            }
        }
    }
   
    /**
     * Get all the ‘clean’ entities
     */
    public function getCleanEntities()
    {
        return $this->_cleanEntities;
    }
   
    /**
     * Mark an entity ‘dirty’
     */
    public function markDirty(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
            if (!array_key_exists($id, $this->_dirtyEntities)) {
                $this->_dirtyEntities[$id] = $entity;
            }
        }
    }
   
    /**
     * Get all the ‘dirty’ entities
     */
    public function getDirtyEntities()
    {
        return $this->_dirtyEntities;
    }
   
    /**
     * Mark an entity ‘removed’
     */
    public function markRemoved(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (!array_key_exists($id, $this->_removedEntities)) {
                $this->_removedEntities[$id] = $entity; 
            }
        }
    }
   
    /**
     * Get all the ‘removed’ entities
     */
    public function getRemovedEntities()
    {
        return $this->_removedEntities;
    }
   
    /**
     * Clear all the ‘new’ entities
     */ 
    public function clearNew()
    {
        $this->_newEntities = array();
        return $this;
    }
   
    /**
     * Clear all the ‘clean’ entities
     */
    public function clearClean()
    {
        $this->_cleanEntities = array();
        return $this;
    }
   
    /**
     * Clear all the ‘dirty’ entities
     */ 
    public function clearDirty()
    {
        $this->_dirtyEntities = array();
        return $this;
    }
   
    /**
     * Clear all the ‘removed’ entities
     */ 
    public function clearRemoved()
    {
        $this->_removedEntities = array();
        return $this;
    }
   
    /**
     * Clear all the entities stored in the Unit Of Work
     */
    public function clearAll()
    {
        $this->clearNew()
             ->clearClean()
             ->clearDirty()
             ->clearRemoved();
    }
   
    /**
     * Find an entity by its ID (implements an identity map)
     */
    public function findById($id)
    {
        if (array_key_exists($id, $this->_cleanEntities)) {
            return $this->_cleanEntities[$id];
        }
        if ($entity = $this->_dataMapper->findById($id)) {
            $this->markClean($entity);
            return $entity;
        }
        return null;  
    }
   
    /**
     * Find all the entities
     */
    public function findAll()
    {
        $collection = $this->_dataMapper->findAll();
        if ($collection !== null) {
            foreach ($collection as $entity) {
                $this->markClean($entity);
            }
            return $collection;
        }
        return null;
    }
   
    /**
     * Commit all the pending entity operations in one go (insert, update, delete)
     */
    public function commit()
    {  
        // save all the ‘new’ entities
        if (!empty($this->_newEntities)) {
            foreach ($this->_newEntities as $_newEntity) {
                $this->_dataMapper->insert($_newEntity);           
            }
        }
        // update all the ‘dirty’ entities
        if (!empty($this->_dirtyEntities)) {
            foreach ($this->_dirtyEntities as $_dirtyEntity){
                $this->_dataMapper->update($_dirtyEntity);
            }
        }
        // delete all the ‘removed’ ‘entities
        if (!empty($this->_removedEntities)) {
            foreach ($this->_removedEntities as $_removedEntity) {
                $this->_dataMapper->delete($_removedEntity);
            }
        }
    }  
}

From the above code sample, it’s clear to see that the actual workhorses of the UoW are the “markNew(),” “markClean(),” “markDirty()” and “markRemoved()” methods. Along with the aforementioned “commit()” method, allow you to queue entities in memory and execute the pending operations in one go.

Even though the implementation of each method seems to be complex, at least at a glance, if you analyze them more closely you’ll realize that they do their business by handling only plain array elements. Of course, while it’s fairly easy to understand how the abstract UoW does its thing, at this point there’s not much that can be done with it, because we need to create the dependencies that it uses internally.

To fix this issue, in the following section I’m going to show you the definition of the abstract data mapper injected through the UoW’s constructor. Therefore, to learn more on this topic, go ahead and read the lines to come.

{mospagebreak title=Adding a collaborator to the UoW: defining a simple abstract data mapper}

To keep the entire sample code understandable and uncluttered, the implementation of the abstract data mapper injected into the internals of the previous UoW will be really short. In this case, it will act like a mediator between the persistence layer and the UoW itself, and will be capable of handling collections of entities as well.

With that said, here’s how this abstract data mapper looks. Check it out:

(DataMapperAbstract.php)

<?php

abstract class DataMapperAbstract
{
    protected $_adapter;
    protected $_collection;
        
    /**
     * Class constructor
     */
    public function __construct(DatabaseAdapterInterface $adapter, CollectionAbstract $collection)
    {
        $this->_adapter = $adapter;
        $this->_collection = $collection;
        $this->init();
    }
   
    /**
     * Initialize the data mapper here
     */
    public function init(){}
   
    /**
     * Get the instance of the database adapter
     */ 
    public function getAdapter()
    {
        return $this->_adapter;
    }
   
    /**
     * Get the collection
     */
    public function getCollection()
    {
        return $this->_collection;
    } 
}

As you can see above, the functionality encapsulated by the “DataMapperAbstract” class is rather limited. All it does is take two dependencies through its constructor, which are stored as protected properties. The first one is the database adapter, used to talk to the persistence layer, while the last one is an object tasked with handling collections of entities. As I’ve stated before, don’t be concerned about the definitions of these dependencies, since they’ll be shown in forthcoming parts of this series.

Of course, if you’re anything like me, you may be wondering why I didn’t make this abstract mapper a bit more functional, right? In fact I could have, but since my objective here is to build a sample web application that will use the previous UoW to synchronize and handle user entities, any form of refined functionality will be delegated to a concrete user mapper, rather than implemented within the abstract one.

All in all, now that you’ve grasped how this simple data mapper does its business, the next step is to create a class capable of modeling generic entities, so they can be properly manipulated by the UoW.
     
The definition of this whole new class will be shown below, so just keep reading.

Modeling generic entities: building an abstract entity class

As you may have realized, there are many approaches that can be taken to model generic entities. In this case, though, I will use an abstract class which acts like a simple proxy for the “__get()” and “__set()” PHP magic methods and permits you to assign, on the fly, a number of fields to an entity. I used a similar class in a series that I wrote previously (http://www.devshed.com/c/a/PHP/Roll-Your-Own-Repository-in-PHP-Building-the-Domain-Layer/1/), so feel free to skip over the following code sample if you’re already familiar with the topic.

Having clarified that, here’s the definition of this entity-modeling class:  

(EntityAbstract.php)

<?php

abstract class EntityAbstract
{
    protected $_values = array();
    protected $_allowedFields = array();
   
    /**
     * Class constructor
     */
    public function __construct(array $data = array())
    {
        foreach ($data as $name => $value) {
            $this->$name = $value;
        }
    }
   
    /**
     * Assign a value to the specified field via the corresponding mutator (if it exists);
     * otherwise, assign the value directly to the ‘$_values’ protected array
     */
    public function __set($name, $value)
    {  
        if (!in_array($name, $this->_allowedFields)) {
            throw new EntityException(‘The field ‘ . $name . ‘ is not allowed for this entity.’); 
        }
        $mutator = ‘set’ . ucfirst($name);
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);          
        }
        else {
            $this->_values[$name] = $value;
        }   
    }
   
    /**
     * Get the value assigned to the specified field via the corresponding getter (if it exists);
    otherwise, get the value directly from the ‘$_values’ protected array
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new EntityException(‘The field ‘ . $name . ‘ is not allowed for this entity.’);   
        }
        $accessor = ‘get’ . ucfirst($name);
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;   
        }
        return array_key_exists($name, $this->_values) ?
               $this->_values[$name] :
               null;
    }
   
    /**
     * Get an associative array with the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }             
}

 

(EntityException.php)

<?php

class EntityException extends Exception {}

As I just explained, the above “EntityAbstract” class takes advantage of property overloading to assign pairs of fields/values to a given entity. The assignment and retrieval processes can be performed either through the corresponding mutators and getters (if they have been implemented) or by direct manipulation of the protected $_values array. That was pretty easy to code and read, wasn’t it?

Although the creation of this class seems to be somewhat irrelevant, at least when it comes to demonstrating how to use the previous UoW, this is only a misconception, trust me. When I show you how to put all of these classes (and others that remain undefined) to work together, all of the pieces of this puzzle will fit nicely. In the interim, just be patient.  

Final thoughts

In this second tutorial of the series, I added to the previous UoW class a pair of collaborators that it needs to function properly, namely an abstract data mapper tasked with interacting with the persistence layer, and an additional abstract class responsible for modeling generic entities. Since similar implementations of these classes were shown and discussed in other articles published here at the Developer Shed network, you shouldn’t have major trouble understanding what they do.

And speaking of collaborators, you may have already noticed that the earlier mapper has its own collaborate, which turns out to be an instance of an abstract database adapter. Obviously, before you’ll see this sample Unit of Work in action, it’s necessary to define the adapter in question, right?

This is exactly the subject that I plan to cover in the next tutorial, so you don’t have any excuses to miss it! 

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

chat