Roll Your Own Repository in PHP: the Data Access Layer

In this second installment of the series, I create a simple data access layer. It will be comprised of a single interface, and a basic MySQL abstraction class.

A repository is an abstraction layer that usually sits between the domain and the mapping layers of a given application. It provides client code with a set of methods that can be used to manipulate collections of domain objects that match a specific criteria. Superbly described by Martin Fowler in his already classic book Patterns of Enterprise Application Architecture, a repository can be really helpful for encapsulating queries on related domain objects behind an intuitive API. Doing this produces an illusion of having all of those object collections residing in memory at the same time.

As with other software design patterns, a repository can be implemented in multiple programming languages. This includes PHP – though PHP-based repositories aren’t very popular right now, except in cases where this level of abstraction is indispensable. Regardless, building a repository from scratch in PHP is an extremely productive process. It permits you to put other principles and patterns of modern application development to work together, such as data mappers, factories and entities, beyond the typical implementation of Active Record. Note that a typical implementation of Active Record couples domain objects “too dangerously” with the underlying persistence mechanism (usually a relational database).     

With that premise in mind, in the first part of this series I took the first step toward the construction of a simple user repository, namely the development of the domain layer. To keep the process easy to follow, the layer was comprised of two sample classes. The first one was an abstract parent, tasked with defining the structure and behavior of generic entities, while the second class was a refined implementation of the parent, responsible for modeling basic user objects.

Needless to say, before I manage to create a functional user repository, there are some additional steps that must be taken. These include (among others) building the data access and mapping layers. Therefore, in this second tutorial of the series, I’m going to create the former; for the sake of simplicity, it will be made up of a simple MySQL abstraction class.

Ready to see how this sample data access layer will be built in a few easy steps? Then jump in and begin reading!

Reviewing the sample classes defined so far

Before I start defining the data access layer mentioned in the introduction, let’s quickly review the classes created in the preceding part of the series. As you’ll recall, these classes comprise the domain layer of this example. Their responsibility is to create simple entities, like the user objects that will be handled later on by the repository.

With that said, first, here’s a basic autoloader. It lazy-loads source classes via the SPL stack. Check it out:

(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 you’ll surely agree with me, the logic implemented by the above “Autoloader” class is very easy to grasp, so I’m not going to waste your valuable time explaining how it works again. Instead, look at the following two classes. The abstract one, “EntityAbstract,” is tasked with defining the structure and behavior of generic entities, while the concrete “User” class is charged with modeling user objects. Here’s the former:

(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 {}

Done. With the above abstract parent encapsulating most of the functionality required to create generic entities, deriving a subclass that models user objects according to a number of predefined constraints is this easy:

(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;
    }                   
}

Mission accomplished, at least for now. Having shown the pair of classes that make up the domain layer of this example (plus the autoloader, which normally should reside in some kind of bootstrap module or class), the next step is to define the corresponding data access layer. As mentioned before, this layer will be integrated by a basic MySQL abstraction class.

Don’t be concerned for the moment if these layers seem to be disconnected from each other. When I put them to work side by side, you’ll see how nicely they’ll fit in the whole schema imposed by a repository.

So, if you want to see the definition of the aforementioned data access layer, read the following segment. It’s only one click away.      

{mospagebreak title=Building the data access layer}

In reality, before creating a concrete class tasked with talking directly to MySQL, we need to define a simple contract. This contract must be implemented by this class, and by other classes responsible for interacting with different RDBMS. In doing so, it’ll be possible to create easily “pluggable” database adapters that aren’t tied to a specific implementation.

For obvious reasons, the contract is nothing but an interface, whose definition looks like this: 

(DatabaseAdapterInterface.php)

<?php

interface DatabaseAdapterInterface
{
    public function connect();
   
    public function disconnect(); 
   
    public function query($query);
   
    public function fetch(); 
   
    public function select($table, $where, $fields, $order, $limit, $offset);
   
    public function insert($table, array $data);
   
    public function update($table, array $data, $where);
   
    public function delete($table, $where);
   
    public function getInsertId();
   
    public function countRows();
   
    public function getAffectedRows();
}

As you can see above, the “DatabaseAdapterInterface” interface defines a set of methods which must be implemented later by any adapter class that works with a specific database server. In many popular frameworks, such as the Zend Framework and Symfony, common functionality shared by different database adapters is usually encapsulated within an abstract class. Since in this case I plan to use only a single MySQL adapter, I decided to define a single interface, which is open to further implementations.

So far, so good. Now that you understand the purpose of coding the previous interface, it’s time to  create the MySQL abstraction class mentioned before.

The source code of this class will be shown in the next section. Thus, to get there, just keep reading. 

Creating an implementer of the previous interface: building a basic MySQL abstraction class

For demonstration purposes, the underlying persistence mechanism that will be used for storing and retrieving all of the user objects spawned from the ”User” class will be a MySQL database. Therefore, it’s mandatory to build a class capable of interacting with that database server through a simple API.

This class is naturally an implementer of the previous “DatabaseAdapterInterface” interface. Its source code is shown below:    

(MySQLAdapter.php)

<?php

class MySQLAdapter implements DatabaseAdapterInterface
{
    protected $_config = array();
    protected $_link;
    protected $_result;
    protected static $_instance;
   
    /**
     * Get the Singleton instance of the class
     */
    public static function getInstance(array $config = array())
    {
        if (self::$_instance === null) {
            self::$_instance = new self($config);
        }
        return self::$_instance;
    }
   
    /**
     * Class constructor
     */
    protected function __construct(array $config)
    {
        if (count($config) !== 4) {
            throw new MySQLAdapterException(‘Invalid number of connection parameters.’);  
        }
        $this->_config = $config;
    }
   
    /**
     * Prevent cloning the instance of the class
     */
    protected function __clone(){}
   
    /**
     * Connect to MySQL
     */
    public function connect()
    {
        // connect only once
        if ($this->_link === null) {
            list($host, $user, $password, $database) = $this->_config;
            if ((!$this->_link = @mysqli_connect($host, $user, $password, $database))) {
                throw new MySQLAdapterException(‘Error connecting to MySQL : ‘ . mysqli_connect_error());
            }
            unset($host, $user, $password, $database);      
        }
    }

    /**
     * Execute the specified query
     */
    public function query($query)
    {
        if (!is_string($query) || empty($query)) {
            throw new MySQLAdapterException(‘The specified query is not valid.’);  
        }
        // lazy connect to MySQL
        $this->connect();
        if (!$this->_result = mysqli_query($this->_link, $query)) {
            throw new MySQLAdapterException(‘Error executing the specified query ‘ . $query . mysqli_error($this->_link));
        }
    }
   
    /**
     * Perform a SELECT statement
     */
    public function select($table, $where = ”, $fields = ‘*’, $order = ”, $limit = null, $offset = null)
    {
        $query = ‘SELECT ‘ . $fields . ‘ FROM ‘ . $table
               . (($where) ? ‘ WHERE ‘ . $where : ”)
               . (($limit) ? ‘ LIMIT ‘ . $limit : ”)
               . (($offset && $limit) ? ‘ OFFSET ‘ . $offset : ”)
               . (($order) ? ‘ ORDER BY ‘ . $order : ”);
        $this->query($query);
        return $this->countRows();
    }
   
    /**
     * Perform an INSERT statement
     */ 
    public function insert($table, array $data)
    {
        $fields = implode(‘,’, array_keys($data));
        $values = implode(‘,’, array_map(array($this, ‘quoteValue’), array_values($data)));
        $query = ‘INSERT INTO ‘ . $table . ‘(‘ . $fields . ‘)’ . ‘ VALUES (‘ . $values . ‘)’;
        $this->query($query);
        return $this->getInsertId();
    }
   
    /**
     * Perform an UPDATE statement
     */
    public function update($table, array $data, $where = ”)
    {
        $set = array();
        foreach ($data as $field => $value) {
            $set[] = $field . ‘=’ . $this->quoteValue($value);
        }
        $set = implode(‘,’, $set);
        $query = ‘UPDATE ‘ . $table . ‘ SET ‘ . $set
               . (($where) ? ‘ WHERE ‘ . $where : ”);
        $this->query($query);
        return $this->getAffectedRows(); 
    }
   
    /**
     * Perform a DELETE statement
     */
    public function delete($table, $where = ”)
    {
        $query = ‘DELETE FROM ‘ . $table
               . (($where) ? ‘ WHERE ‘ . $where : ”);
        $this->query($query);
        return $this->getAffectedRows();
    }
   
    /**
     * Single quote the specified value
     */
    public function quoteValue($value)
    {
        if ($value === null) {
            $value = ‘NULL’;
        }
        else if (!is_numeric($value)) {
            $value = "’" . mysqli_real_escape_string($this->_link, $value) . "’";
        }
        return $value;
    }
   
    /**
     * Fetch a single row from the current result set (as an associative array)
     */
    public function fetch()
    {
        if ($this->_result !== null) {
            if ((!$row = mysqli_fetch_array($this->_result, MYSQLI_ASSOC))) {
                $this->freeResult();
                return false;
            }
            return $row;
        }
    }

    /**
     * Get the insertion ID
     */
    public function getInsertId()
    {
        return $this->_link !== null ?
               mysqli_insert_id($this->_link) :
               null; 
    }
   
    /**
     * Get the number of rows returned by the current result set
     */ 
    public function countRows()
    {
        return $this->_result !== null ?
               mysqli_num_rows($this->_result) :
               0;
    }
   
    /**
     * Get the number of affected rows
     */
    public function getAffectedRows()
    {
        return $this->_link !== null ?
               mysqli_affected_rows($this->_link) :
               0;
    }
   
    /**
     * Free up the current result set
     */
    public function freeResult()
    {
        if ($this->_result !== null) {
            mysqli_free_result($this->_result);  
        }
    }
   
    /**
     * Close explicitly the database connection
     */
    public function disconnect()
    {
        if ($this->_link !== null) {
            mysqli_close($this->_link);
            $this->_link = null;
        }
    }
   
    /**
     * Close automatically the database connection when the instance of the class is destroyed
     */
    public function __destruct()
    {
        $this->disconnect();
    }
}

 

(MySQLAdapterException.php)

<?php

class MySQLAdapterException extends Exception{}

Don’t feel overwhelmed by the rather lengthy definition of the above “MySQLAdapter” class. The logic that drives it is fairly easy to follow, trust me. Basically, the class is a simple wrapper for the “mysqli” PHP extension, which exposes a set of discrete methods that perform some common operations on the database server. These operations include fetching table rows, executing selects, insertions/updates and deletions. Naturally, it’s possible to swap this database class with the one included with the framework of your choice (if you use one), so feel free to skip over this explanation.

Anyway, with the inclusion of the “MySQLAdapter” class, the data access layer of this example is now complete. Even though it’s valid to note that the pair of sample layers defined so far can’t work together yet, their respective implementations have brought us closer to the construction of a user repository. At the risk of being repetitive, bear in mind that this is a step-by-step development process, so be patient for now. In the end, the wait will be really worthwhile.

Final thoughts

In this second installment of the series, I created a simple data access layer. It was comprised of a single interface and a basic MySQL abstraction class whose underlying logic was hopefully easy to catch.

Having already created two independent sample layers, the next logical step is to define one more, responsible for talking to both while keeping them isolated from each other. As you may have guessed, this extra layer will be composed of the data mappers, and in the course of the coming tutorial I’ll show you how to implement them in a pretty straightforward fashion.

Don’t miss the next part! 

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