PHP Services Layers: Data Mappers

In this second part of a series on PHP services layers, I add a couple of data mappers to our sample application. These data mappers will allow us to bridge the domain model with the data access layer. With this addition, we’re one step closer to implementing a fully-functional user service.

If you need to develop flexible, highly-extendable PHP applications that can be interfaced easily with different client layers, you will want to consider a service layer. As a fundamental pillar of Domain Driven Design (DDD), a service is an enterprise-level pattern that will let you encapsulate application logic behind a single interface — which can be consumed by distinct clients (yes, including your carefully-crafted action controllers!) while keeping code duplication to a minimum.

There’s little sense to highlighting the benefits of a service layer without providing proof. To partially address this issue, in the introductory part of this series I started building a sample application. It will use the functionality offered by a service to manipulate some user entities.

Admittedly, in its current state the application looks somewhat skeletal. I only managed to create its domain model, which is pretty simplistic. In this second installment, however, I’m going to turn it into a slightly more functional structure. And how will I do that? Well, we need to build an additional layer that permits you to bridge the domain model with the underlying storage mechanism (not yet implemented). Not surprisingly, this new layer will be made up of some easily customizable data mappers.

With that said, are you ready to continue this educational journey of building a service in PHP? Then jump in and start reading!

Recap time: a brief look at the application’s domain model

Just in case you missed the preceding part of this series, where I developed the sample domain model, below I included its source classes.

The first class is an abstract parent, responsible for modeling the common structure and behavior of generic entities. Here it is:

(MyApplication/Entity/AbstractEntity.php)

<?php

namespace MyApplicationEntity;

abstract class AbstractEntity
{
    protected $_values = array();
    protected $_allowedFields = array();
   
    /**
     * Class constructor
     */
    public function __construct(array $data)
    {
        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;   
        }
        if (isset($this->_values[$name])) {
            return $this->_values[$name];  
        }
        throw new EntityException(‘The field ‘ . $name . ‘ has not been set for this entity yet.’);
    }

    /**
     * Check if the specified field has been assigned to the entity
     */
    public function __isset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new EntityException(‘The field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        return isset($this->_values[$name]);
    }

    /**
     * Unset the specified field from the entity
     */
    public function __unset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new EntityException(‘The field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
        }
    }

    /**
     * Get an associative array with the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }             
}

 

(MyApplication/Entity/EntityException.php)

<?php

namespace MyApplicationEntity;

class EntityException extends Exception{}

While at first glance the implementation of the above “AbstractEntity” class seems intimidating, this is a mistaken impression, trust me. In fact, the class simply uses some PHP magic methods to get and fetch the values assigned to the fields of an entity. This process can be performed either via the corresponding mutators/getters (if they exist) or by using the protected $_values array. Got it? Great.

And now that you understand the logic of the previous class, it’s time to look at the next one. This is a derivative of the abstract parent, tasked with modeling plain user objects. Check it out:

(MyApplication/Entity/User.php)

<?php

namespace MyApplicationEntity;

class User extends AbstractEntity
{  
    protected $_allowedFields = array(‘id’, ‘fname’, ‘lname’, ‘email’);
   
    /**
     * Set the user’s ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array(‘options’ => array(‘min_range’ => 1, ‘max_range’ => 999999)))) {
            throw new EntityException(‘The specified ID is invalid.’);
        }
        $this->_values['id'] = $id;
    }
   
    /**
     * Set the user’s first name
     */ 
    public function setFname($fname)
    {
        if (strlen($fname) < 2 || strlen($fname) > 32) {
            throw new EntityException(‘The specified first name is invalid.’);
        }
        $this->_values['fname'] = $fname;
    }
       
    /**
     * Set the user’s last name
     */
    public function setLname($lname)
    {
        if (strlen($lname) < 2 || strlen($lname) > 32) {
            throw new EntityException(‘The specified last name is invalid.’);
        }
        $this->_values['lname'] = $lname;
    }
   
    /**
     * Set the user’s email address
     */
    public function setEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new EntityException(‘The specified email address is invalid.’);
        }
        $this->_values['email'] = $email;
    }                   
}

There you have it. With minor effort, I managed to set up a basic, yet functional domain model,  composed of only a few simple user entities. So, what’s the next step? Well, considering that the model must be persisted in some form, it’s necessary to build a new layer that acts like a mediator with the persistence mechanism.

Usually, this layer is implemented through some mapping classes, and I’ll be using this approach. So, in the following section I’m going to create a couple of data mappers. They will be responsible for performing CRUD operations with the user entities present in the domain model.      

To see how these mappers will be developed, click on the link below and keep reading.

{mospagebreak title=Building an abstract data mapper}

To save myself from the hassle of reinventing the wheel, the data mappers (http://martinfowler.com/eaaCatalog/dataMapper.html) that I plan to use will be pretty similar to the ones shown here (http://www.devshed.com/c/a/PHP/Roll-Your-Own-Repository-in-PHP-Data-Mapper-Finishing-Touches/). They implement a segregated interface, whose contract looks like this:

(MyApplication/Mapper/DataMapperInterface.php)

<?php

namespace MyApplicationMapper;

interface DataMapperInterface
{
    public function findById($id);

    public function findAll();

    public function search($criteria);

    public function insert($entity);

    public function update($entity);

    public function delete($entity);
}

The above interface doesn’t require any further analysis, so let’s move on and look at the class below. It implements an abstract data mapper:

(MyApplication/Mapper/AbstractDataMapper.php)

<?php

namespace MyApplicationMapper;
use MyApplicationDatabase,
    MyApplicationCollection;

abstract class AbstractDataMapper implements DataMapperInterface
{
    protected $_adapter;
    protected $_collection;
    protected $_entityTable;
    protected $_entityClass;

    /**
     * Constructor
     */
    public function  __construct(DatabaseDatabaseAdapterInterface $adapter, CollectionAbstractCollection $collection, array $entityOptions = array())
    {
        $this->_adapter = $adapter;
        $this->_collection = $collection;
        if (isset($entityOptions['entityTable'])) {
            $this->setEntityTable($entityOptions['entityTable']);
        }
        if (isset($entityOptions['entityClass'])) {
            $this->setEntityClass($entityOptions['entityClass']);
        }
    }

    /**
     * Get the database adapter
     */
    public function getAdapter()
    {
        return $this->_adapter;
    }

    /**
     * Get the collection
     */
    public function getCollection()
    {
        return $this->_collection;
    }

    /**
     * Set the entity table
     */
    public function setEntityTable($entityTable)
    {
        if (!is_string($table) || empty ($entityTable)) {
            throw new DataMapperException(‘The specified entity table is invalid.’);
        }
        $this->_entityTable = $entityTable;
    }

    /**
     * Get the entity table
     */
    public function getEntityTable()
    {
        return $this->_entityTable;
    }

    /**
     * Set the entity class
     */
    public function setEntityClass($entityClass)
    {
        if (!class_exists($entityClass)) {
            throw new DataMapperException(‘The specified entity class is invalid.’);
        }
        $this->_entityClass = $entityClass;
    }

    /**
     * Get the entity class
     */
    public function getEntityClass()
    {
        return $this->_entityClass;
    }

    /**
     * Find an entity by its ID
     */
    public function findById($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->_adapter->select($this->_entityTable);
        while ($data = $this->_adapter->fetch($this->_entityTable)) {
            $this->_collection[] = new $this->_entityClass($data);
        }
        return $this->_collection;
    }

    /**
     * Find all the entities that match the specified criteria
     */
    public function search($criteria)
    {
        $this->_adapter->select($this->_entityTable, $criteria);
        while ($data = $this->_adapter->fetch()) {
            $this->_collection[] = new $this->_entityClass($data);
        }
        return $this->_collection;
    }
   
    /**
     * Insert a new row in the table corresponding to the specified entity
     */
    public function insert($entity)
    {
        if ($entity instanceof $this->_entityClass) {
            return $this->_adapter->insert($this->_entityTable, $entity->toArray());
        }
        throw new DataMapperException(‘The specified entity is not allowed for this mapper.’);
    }

    /**
     * Update the row in the table corresponding to the specified entity
     */
    public function update($entity)
    {
        if ($entity instanceof $this->_entityClass) {
            $data = $entity->toArray();
            $id = $entity->id;
            unset($data['id']);
            return $this->_adapter->update($this->_entityTable, $data, "id = $id");
        }
        throw new DataMapperException(‘The specified entity is not allowed for this mapper.’);
    }

    /**
     * Delete the row in the table corresponding to the specified entity or ID
     */
    public function delete($id)
    {
        if ($id instanceof $this->_entityClass) {
            $id = $id->id;
        }
        return $this->_adapter->delete($this->_entityTable, "id = $id");
    }
}

 

(MyApplication/Mapper/DataMapperException.php)

<?php

namespace MyApplicationMapper;

class DataMapperException extends Exception{}

At the risk of being repetitive, what I said about the previous “AbstractEntity” class applies to the earlier mapper as well: despite its apparent complexity, the logic that stands behind it is fairly straightforward. In short, its functionality is reduced to performing CRUD operations on generic entities, achieved via a database adapter, which is injected in the constructor.

Of course, the lonely existence of an abstract mapper doesn’t buy us too much as far as moving user entities between the domain model and the persistence layer. To tackle this issue, it’s necessary to spawn a concrete mapper that works exclusively with user objects.

This is exactly what I’ll be doing in the upcoming segment. 

Mapping user entities: implementing a user mapper

If you ever thought that building a data mapper that works specifically with user entities was a challenging and difficult task, then take a look at the class below. It is called “UserMapper,” and its implementation is as follows: 

(MyApplication/Mapper/UserMapper.php)

<?php

namespace MyApplicationMapper;
use MyApplicationDatabase,
    MyApplicationCollection;

class UserMapper extends AbstractDataMapper
{
    protected $_entityClass = ‘MyApplicationEntityUser’;
    protected $_entityTable = ‘users’; 
   
    /**
     * Constructor
     */
     public function __construct(DatabaseDatabaseAdapterInterface $adapter, CollectionUserCollection $collection)
     {
         parent::__construct($adapter, $collection);
     }  
}

That was really quick to code and read, wasn’t it? This user mapper simply overrides its parent’s constructor to make sure that only collections of user entities will be injected into its internals. Besides, the $_entityClass and $_entityTable properties assure that the mapper will move data between the mentioned entities and a “users” database table residing in the persistence layer. The latter will be discussed in depth in upcoming installments of the series.

With this user mapper ready to go, we’ve made significant progress toward the development of a service layer. However, there are still some pieces that need to be added to the current structure of this sample application. These include the construction of the collaborators taken by the pertinent mappers. 

Final Thoughts

In this second chapter of the series, things got a little more interesting as I added a couple of data mappers to this sample application. These data mappers allowed us to bridge its domain model with the data access layer. Even though it may sound like a marketing phrase, we’re closer to implementing a fully-functional user service. For the moment, be patient and everything will work like a charm, believe me.

On the other hand, you may have noticed that the previous data mappers accept two dependencies to work as expected: the first one is a database adapter, while the second one is an instance of a collection handling class. Well, there’s no need to be a rocket scientist to realize that these collaborators must be properly implemented as well.

In the next tutorial I’ll be showing you the full source code of the former (the latter will be covered in detail in a future installment).

Don’t miss the next part!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan