HomePHP Roll Your Own Repository in PHP: Working with Collections of Entities
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:
/** * 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:
/** * 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.