Roll Your Own Repository in PHP: Working with Collections of Entities

In this fifth part of the series, I add two additional classes to our sample PHP application. They will be responsible for handling collections of generic entities, and more specifically, collections of user objects. In truth, these new classes will be simple countable iterators, capable of accessing the entities as if they were array elements.

Being a fundamental pillar of Domain-driven Design (DDD), a repository is an abstraction layer placed between the domain and mapping layers of an application. It permits you to query collections of domain objects as if all of them were residing in memory at the same time. There are many benefits to having such a level of abstraction, especially for applications that expose a rich domain integrated by numerous objects; however, the implementation of a repository demands some extra work, which can be overkill to the eyes of the average PHP developer.

Contrary to this opinion, I must say that constructing a repository from scratch is instructive. It requires first that you define a few domain classes and mappers, then at least a simple access data layer, and finally that you put all of these pieces to work together. To demonstrate how useful this experience can be, in previous parts of this series I started creating a sample web program whose main objective is to handle collections of user objects through a basic repository.

So far, I’ve demonstrated how to construct the domain, mapping and data access layers of this sample program, and it has been a fairly straightforward process. As I stated above, though, a repository bases most of its functionality on working with collections of domain objects, a process that we have yet to discuss.

To tackle that issue, in this fifth tutorial of the series I’m going to define a couple of classes that will be responsible for treating collections of entities as if they were array elements. Needless to say, the creation of these classes will require the use of the Countable, Iterator and ArrayAccess native PHP interfaces. If you’re interested in learning the full details of this creation process, start reading right now!

The generic mapper previously developed

As usual, before I show you the definitions of the collection classes mentioned in the introduction, we’ll  take a quick look at the finished version of the data mappers developed in the last tutorial.

With that said, here’s the abstract mapper that permits you to work with generic entities. Its source code is as follows:

(DataMapperAbstract.php)

<?php

abstract class DataMapperAbstract implements DataMapperInterface
{
    protected $_adapter;
    protected $_collection;
    protected $_entityClass;
    protected $_entityTable; 
        
    /**
     * Class constructor
     */
    public function __construct(MySQLAdapter $adapter, CollectionAbstract $collection, array $entityOptions = array())
    {
        $this->_adapter = $adapter;
        $this->_collection = $collection;
        if (isset($entityOptions['entityClass'])) {
            $this->setEntityClass($entityOptions['entityClass']);
        }
        if (isset($entityOptions['entityTable'])) {
            $this->setEntityTable($entityOptions['entityTable']);
        }
        $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 the mapper uses
     */
    public function getCollection()
    {
        return $this->_collection;
    }
   
    /**
     * Set the class for reconstructing entities
     */
    public function setEntityClass($entityClass)
    {
        if (!class_exists($entityClass, false)) {
            throw new DataMapperException(‘The specified entity class ‘ . $entityClass . ‘ does not exist.’);
        }
        $this->_entityClass = $entityClass;
    }
   
    /**
     * Get the class for reconstructing entities
     */
    public function getEntityClass()
    {
        return $this->_entityClass;
    }
   
    /**
     * Set the entity database table the mapper works with
     */
    public function setEntityTable($entityTable)
    {
        if (!is_string($entityTable) || empty($entityTable)) {
            throw new DataMapperException(‘The specified entity table ‘ . $entityTable . ‘ is invalid.’);
        }
        $this->_entityTable = $entityTable;
    }
   
    /**
     * Get the entity database table the mapper works with
     */
    public function getEntityTable()
    {
        return $this->_entityTable;
    }
   
    /**
     * Find an entity by its ID
     */
    public function findById($id)
    {
        $id = (int) $id;
        $this->_adapter->select($this->_entityTable, "id = $id");
        if ($data = $this->_adapter->fetch()) {
            return new $this->_entityClass($data);
        }
        return null;
    }
   
    /**
     * Find all the entities
     */
    public function findAll()
    {
        $this->_collection->clear();
        $this->_adapter->select($this->_entityTable);
        while ($data = $this->_adapter->fetch()) {
            $this->_collection->add($data['id'], new $this->_entityClass($data));   
        }
        return $this->_collection->count() !== 0 ?
               $this->_collection :
               null;
    }
   
    /**
     * Find all the entities that match the specified criteria
     */
    public function search($criteria)
    {
        $this->_collection->clear();
        $this->_adapter->select($this->_entityTable, $criteria);
        while ($data = $this->_adapter->fetch()) {
            $this->_collection->add($data['id'], new $this->_entityClass($data));   
        }
        return $this->_collection->count() !== 0 ?
               $this->_collection :
               null;
    }
     
    /**
     * Insert a new row in the table corresponding to the specified entity
     */
    public function insert(EntityAbstract $entity)
    {
        $data = $entity->toArray();
        return $this->_adapter->insert($this->_entityTable, $data);
    }
   
    /**
     * update the row in the table corresponding to the specified entity
     */
    public function update(EntityAbstract $entity)
    {
        $id = (int) $entity->id;
        $data = $entity->toArray();
        unset($data['id']);
        return $this->_adapter->update($this->_entityTable, $data, "id = $id");
    }
    
    /**
     * Delete the row in the table corresponding to the specified entity or ID
     */
    public function delete($id)
    {
        if ($id instanceof EntityAbstract) {
            $id = (int) $id->id;
        }
        return $this->_adapter->delete($this->_entityTable, "id = $id");
    }               
}

While the logic implemented by the above generic mapper seems hard to grasp, it’s really quite simple to understand. All that this class does is perform CRUD operations using generic entities (please, notice the injection into the constructor of an object of type CollectionAbstract). With this abstract parent comfortably seated on top of the hierarchy, building a concrete mapper that handles user entities is ridiculously easy. What’s more, the following snippet shows how to accomplish this in a nutshell. Check it out: 

(UserMapper.php)

<?php

class UserMapper extends DataMapperAbstract
{
    protected $_entityClass = ‘User';
    protected $_entityTable = ‘users'; 
   
    /**
     * Class constructor
     */
     public function __construct(MySQLAdapter $adapter, UserCollection $collection, array $entityOptions = array())
     {
         parent::__construct($adapter, $collection, $entityOptions);
     }  
}

There you have it. Thanks to all the functionality encapsulated by the earlier abstract class, the implementation of a user mapper is indeed a breeze. But before you start playing with this subclass, take a closer look at the constructors of both classes. Yes, apart from injecting an instance of the already familiar MySQL adapter, they accept the two collaborators mentioned at the beginning, which are responsible for handling collections of entities.

For obvious reasons, these classes must be properly defined. That’s exactly what I plan to do in the following section. Therefore, to learn more about this process, click on the link below and read the lines to come.

{mospagebreak title=Manipulating collections of entities with an abstract collection class}

I don’t want to sound like a braggart, believe me. But creating a class capable of manipulating collections of entities is a straightforward process. Basically, the entire process of creation is reduced to building a countable iterator that can access those entities as if they were plain array elements. It’s that simple, really.

To clarify this concept a bit further, take a look at the class below, which performs all of these tasks with generic entities:  

(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
     */
    public function clear()
    {
        $this->_entities = array();
    }
    
    /**
     * Reset the collection (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 in the collection (implementation required by ArrayAccess interface)
     */
    public function offsetGet($key)
    {
        if (array_key_exists($key, $this->_entities)) {
            return $this->_entities[$key];
        }
    } 
   
    /**
     * 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);
    }
}

As the above code snippet shows, the previous “CollectionAbstract” class encapsulates most of the functionality required for handling collections of generic entities in a fairly simple way. In this case, I decided to make the class an implementer of the Countable, Iterator and ArrayAccess PHP native interfaces, but similar results can be obtained with other iteratable interfaces as well.

So far, so good. Having created an abstract parent capable of working with generic entities, the next step is to derive a subclass that deals specifically with collections of user objects. Again, I have to say this process is a breeze to accomplish. However, the best way to demonstrate this is by showing the full source code of this child class. 

This will be done below, so keep reading.

Working with a collection of user entities: a refined implementation of the earlier collection class

Considering that the primary goal of a user repository is to query collections of user entities, it’s necessary to build a class capable of performing that task in a simple manner. Since the abstract parent defined in the preceding segment does most of the hard work and handles collections of generic entities, creating a refined implementation that deals only with user objects is as easy as defining the following subclass: 

(UserCollection.php)

<?php

class UserCollection extends CollectionAbstract
{
    /**
     * Add a user to the collection
     */
    public function add($key, User $user)
    {
        $this->offsetSet($key, $user);
    }     
}

That was truly easy to code, right? As its name implies, the only area of responsibility of the previous “UserCollection” class is to manipulate collections of user entities, which has been achieved in this case by implementing an additional method that adds user objects to the internal collection. This is nothing but Inheritance 101, but the existence of these two collection classes now permits us to see in a clearer way how they can be put to work side by side with the other classes defined in earlier tutorials to create a functional user repository.

But I’m getting ahead of myself, as every detail of this process will be covered in the next installment.    

Final thoughts

In this fifth installment of the series, I added to this sample PHP application a couple of additional classes, responsible for handling collections of generic entities — and more specifically, collections of user objects. As you just saw, the logic implemented by these classes was pretty easy to catch, as they’re nothing but simple countable iterators, capable of accessing the mentioned entities as if they were array elements.

With these iterators up and running, the scenario is finally set to start building the desired user repository. Nevertheless, the inner workings of this process will be discussed in depth in the forthcoming tutorial. So don’t miss the next part!

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

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort