Home arrow PHP arrow Roll Your Own Repository in PHP: Defining a Generic Data Mapper

Roll Your Own Repository in PHP: Defining a Generic Data Mapper

In this third part of the series, I add a simple mapping layer to the sample application. This layer will be composed of a single interface and a generic mapper class.

TABLE OF CONTENTS:
  1. Roll Your Own Repository in PHP: Defining a Generic Data Mapper
  2. The mapping layer
By: Alejandro Gervasio
Rating: starstarstarstarstar / 4
November 29, 2010

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Understanding repositories may sound difficult, but they're actually pretty straightforward, especially if you have already ventured into the terrain of Domain-Driven Design (DDD). A repository is an additional abstraction layer placed between the domain and mapping layers. It is used in applications with a rich domain for handling collections of domain objects that satisfy a specified criteria. In a typical implementation, a repository concentrates, behind an intuitive API, all of the logic required to query object collections. This significantly reduces code replication.

Naturally, the most effective way to understand the functionality of a repository in PHP (or in any other programming language, of course) is by means of a concrete example that justifies the upfront work required to construct this extra abstraction layer. In keeping with this idea, in the two previous tutorials of this series I went through the development of the domain and data access layers of a sample web application. The application's main objective is to manipulate a bunch of simple user objects that persist through a MySQL database.

Now that we have two layers existing independently of each other, the next step in the implementation of a functional user repository is to create yet another layer. It will act like a mediator between the previous ones without introducing an unnecessary, undesired coupling effect. As you may have already guessed from the article’s title, this layer will be comprised of a set of mapping classes, which can perform CRUD operations on domain objects, and handle collections of them in a fairly simple fashion.

Now that the subject of this third installment of the series has been clearly outlined, it’s time to start creating the mapping classes. Let’s get going!

Review: the data access layer

As always, before I start defining the mapping classes of this sample application, I’d like to spend a few moments reintroducing the source code corresponding to the interface and the implementing class created in the preceding tutorial. These were the building blocks of the application’s data access layer.

First, here’s the interface, which defines a contract that must be fulfilled by any class that interacts with a specific database server. Pay attention to it, please: 

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

With the small number of methods it declares, we can't say much about the “DatabaseAdapterInterface” interface, except that it establishes a contract that must be agreed to by all of the database adapters created from this point onward. In consonance with this, below is the definition of a concrete class which not only implements the interface, but abstracts common operations that are performed with MySQL:

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

Even though the source code is somewhat extensive, the above “MySQLAdapter” class is a simple wrapper for the “mysqli” PHP extension that allows it to execute typical operations on MySQL. These operations include fetching rows, handling result sets, and running queries. You've probably seen many classes similar to this one, so I’m not going to spend a long time explaining how it does its business.

Instead, it’s time to summarize what we've achieved so far. On one hand, this sample application contains a domain layer responsible for creating generic entities, and more specifically user objects; on the other hand, there’s a data access layer capable of dealing directly with the underlying persistence mechanism, which in this case turns out to be a MySQL database. The question is, how can these layers be bridged while preserving their mutual independence?

Well, as I said before, the implementation of a mapping layer will do the trick nicely. Thus, in the next section I’m going to start defining the structure and further behavior of this new layer.

To see how this will be done, jump ahead and read the lines to come.    



 
 
>>> More PHP Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: