Roll Your Own Repository in PHP: Building the Domain Layer

In this introductory part of a series, I give you an overview of what a repository is and how it can be used for handling collections of domain objects in PHP. Since my goal here is to address the subject from a practical point of view, after I explain some theoretical concepts, I will develop a simple domain layer comprised of two classes. The first one will be an abstract parent that defines the structure and behavior of generic entities, while the second class will be responsible for modeling simple user objects, according to a number of predefined constraints.

Often, during the development of complex PHP applications that expose a rich domain layer, it’s mandatory to decouple the participant domain objects from the underlying persistence layer. In most cases, this underlying layer is a relational database (although it could be a web service or another form of persisting mechanism). In a situation like this, the implementation of data mappers can be of great help to solve the so-called impedance mismatch; they can be used to transparently resolve the internal incompatibilities that exist between both layers, while keeping them isolated from each other.

While data mappers can be thought of as two-way bridges, capable of talking to the persistence layer (AKA data access layer or simply DAL) and of reconstituting domain objects through CRUD operations, they suffer from a well-know drawback. In most of their implementations, those operations tend to be somewhat generic at some point. In relatively simple applications, this shouldn’t be an issue. In more complex scenarios, though, where lots of different domain objects need to be retrieved (beyond the functionality offered by generic finders like “findById()” or “findAll()”), inserted or deleted according to a specific criteria, it’s necessary to build an additional abstraction layer upon the mappers that permits you to execute these operations at a much more narrow level.

That’s exactly where a repository comes into play. It presents an API to client code that allows you to work with collections of related domain objects that match a more refined criteria. Consider, for instance, a user repository where collections of user objects can be fetched from the DAL according to a given role, or a specified name or email address. Put in a simpler way, a repository behaves like a mediator between the existing mappers and the domain layer. It encapsulates, behind a set of discrete methods, all the functionality required to perform specific SQL queries and manipulate collections of domain objects.

Needless to say, the best way to understand the benefits provided by a repository is by example. In line with this concept, in this article series I’m going to recreate the scenario mentioned above by building from scratch a user repository. This repository will be capable of handling collections of user objects that match a given criteria.

Keep in mind that this will be a work in progress — meaning that before you see the repository finally up and running, you’ll be guided through the creation of the corresponding domain, data access and mapping layers. So, get ready to see a bunch of code samples from this point onward. You’ve been warned.

Now, it’s time to leave the boring theory behind and start rolling a basic repository in PHP. Let’s get started!

Taking the first step: lazy-loading source classes with a basic autoloader

As I noted in the introduction, the progressive development of the user repository will demand that I write a considerable number of source classes, ranging from the ones that will make up the data access and mapping layers, to the ones responsible for creating domain objects (simple users, in this particular case). Since I want to save myself from the hassle of dealing with multiple PHP requires, the first thing that I plan to do will consist of creating a basic autoloader, which will lazy-load the classes though the native autoloading mechanism provided by PHP.

Having clarified that, here’s how this autoloader will be implemented:

(Autoloader.php)

<?php

class Autoloader
{
    private static $_instance;
   
    /**
     * Get the Singleton instance of the autoloader
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self;
        }
        return self::$_instance;
    } 
   
    /**
     * Reset the instance of the autoloader
     */
    public static function resetInstance()
    {
        self::$_instance = null;
    }
   
    /**
     * Class constructor
     */
    private function __construct()
    {
        spl_autoload_register(array(__CLASS__, ‘load’));
    }
   
    /**
     * Prevent cloning the instance of the autoloader
     */
    private function __clone(){}

    /**
     * Load a given class or interface
     */
    public static function load($class)
    {
        $file = $class . ‘.php';
        if (!file_exists($file)) {
            throw new ClassNotFoundException(‘The file containing the requested class ‘ . $class . ‘or interface was not found.’);
        }
        require $file;
        if (!class_exists($class, false) && !interface_exists($class, false)) {
            throw new ClassNotFoundException(‘The requested class or interface ‘ . $class . ‘ was not found.’);
        }
    }  
}

 

(ClassNotFoundException.php)

<?php

class ClassNotFoundException extends Exception{}

As shown in the above code fragment, the autoloader is nothing but a simple Singleton, which loads classes on demand by using the SPL stack, and also throws a couple of custom exceptions when the process fails for some typical reasons. To keep things pretty clear and understandable, this class doesn’t take advantage of native or user-defined namespaces, but if you need to implement this feature (or others), feel free to do so.  

So far, so good. Having already built a basic autoloader that lazy-loads resources, it’s time to start creating the classes that will comprise the domain layer of this example. The first of these classes will be an abstract parent. It will be tasked with modeling generic entities via the complementary “__set()” and “__get()” PHP magic methods.

To see how this abstract class will be defined, click on the link below and read the following segment.

{mospagebreak title=Building the domain layer: modeling generic entities}

While I have to admit that I’m not a big fan of creating classes that rely too much on the magic methods bundled with PHP, in this case I’m going to implement an approach similar to the one proposed by Ben Scholzen and use the complementary “__set()”/“__get()” pair to construct the abstract parent mentioned in the previous segment. This class will be responsible for defining the structure and behavior of generic entities (like users, blog posts and so forth), and its source code is shown below: 

(EntityAbstract.php)

<?php

abstract class EntityAbstract
{
    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 (array_key_exists($name, $this->_values)) {
            return $this->_values[$name];
        }
        throw new EntityException(‘The field ‘ . $name . ‘ has not been set for this entity yet.’);
    }
       
    /**
     * Unset the specified property from the entity
     */  
    public function __unset($name)
    {
        if (array_key_exists($name, $this->_values)) {
            unset($this->_values[$name]);
        }
    }
      
    /**
     * Get the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }             
}

 

(EntityException.php)

<?php

class EntityException extends Exception {}

As you can see above, the “EntityAbstract” class accepts, through its constructor, an associative array of data. This data is used to assign, all at once, a set of values to its undeclared fields. This process is carried out via the “__set()” method, which first attempts to perform the assignment process via the corresponding mutator; if this method doesn’t exist, the value is assigned directly through the protected "$_values” array.

A similar logic is also implemented within the counterpart “__get()”, even though in this case the method checks to see if the value assigned to the specified field can be retrieved by means of a getter. In addition, the class declares a property called $_allowedFields, which is used to restrict the fields that can be assigned to the entity. Now, do you understand the logic of this generic parent? Good.

Having already defined an abstract class that encapsulates the logic shared by generic entities, the next step in the development of the sample user repository is to derive a subclass capable of modeling user objects, so that they can be properly handled afterward by their associated mapper and, at an upper level, by the repository.

This new child class will be built in the next section below, so keep reading. 
 
Creating a refined implementation of the previous abstract class: modeling user objects

To be frank, building a class that models user objects is a straightforward process, since most of its logic has already been implemented by its abstract parent. In this specific case, and for the sake of brevity, any user object spawned from this class will be allowed to have only four fields: an ID, then a first and a last name, and finally an email address.

With those constraints properly outlined, here’s the definition of this concrete class, not surprisingly called “User”:

(User.php)

<?php

class User extends EntityAbstract
{  
    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’ => 99999)))) {
            throw new EntityException(‘The specified ID ‘ . $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 ‘ . $fname . ‘ 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 ‘ . $lname . ‘ 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 ‘ . $email . ‘ is invalid.’);
        }
        $this->_values['email'] = $email;
    }                   
}

I’m sure you’ll agree that the “User” class shown above is a breeze to follow. All it does is implement a bunch of mutators corresponding to each of its allowed fields. These methods impose some basic restrictions to the values assigned to the pertinent properties; it’s possible, however, to perform a more strict validation, either by using procedural code or through a set of injected validation objects.

Finally, to keep things short and simple I decided not to define any getters. This means that the values assigned to the fields of the class will be directly retrieved from the internal $_values array. Nevertheless, if you want to tackle this extra work on your own and implement these complementary methods, feel free to do so.

And with the inclusion of this concrete user-modeling class, the first step toward the construction of the user repository is now complete. Bear in mind that this is only the beginning of a long and hopefully instructive journey, so for the moment be patient.   

Final thoughts

In this introductory part of the series, I provided you with an overview of what a repository is and how it can be used for handling collections of domain objects in PHP. Since my goal here is to address the subject from a practical point of view, after explaining some theoretical concepts, I went through the development of a simple domain layer comprised of two classes. The first one was an abstract parent that defined the structure and behavior of generic entities, while the second class was responsible for modeling simple user objects, according to a number of predefined constraints.

Considering that a repository usually resides between the domain and mapping layers of an application, and the latter also accesses in turn the data access layer, in the next tutorial I’m going to create this last layer, which in this case will be tasked with talking to MySQL.

Don’t miss the upcoming part! 

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