Home arrow MySQL arrow Building an ORM in PHP

Building an ORM in PHP

In this first part of the tutorial, I implement the base functionality of a custom ORM (object relational mapper) using PHP.

TABLE OF CONTENTS:
  1. Building an ORM in PHP
  2. Building ORM Data Mappers
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
November 18, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Thereís no shortage of options when it comes to picking a PHP-based ORM. The current variety of options satisfies a broad number of tastes and palates, even the pickiest ones. Similar to some other utility packages available, the prolific nature of ORMs brings with it a healthy effect: whether you use a popular third-party MVC framework like CodeIgniter Reactor, CakePHP or Kohana (only to name a few) or employ your own MVC masterpiece, chances are that you can add the functionality of an ORM to your applications with only minor hassles.

Donít let the large number of ORMs fool you, though; the development of such packages is very often  challenging and complex. That's especially true if youíre planning to couple the management of the most common types of relationships (you know, one-to-one, one-to-many and many-to-many) to the functionality provided by enterprise-level design patterns, like Data Mappers, Identity Maps and a Unit of Work.

At this moment, Doctrine 2.x is undeniably the most powerful and full-featured ORM on PHP terrain. It not only implements the aforementioned patterns behind its API; it also provides a solid database abstraction layer. However, other libraries, like RedBeanPHP, dORM and Propel are solid contenders that yield quite impressive results too, so feel free to give them a shot.

Obviously, with so many ORMs at one's disposal for free, it seems pretty pointless to develop a custom one; are we trying to reinvent the wheel? No, of course not. But if you need to create a simple application that performs a few CRUD operations on some related domain objects and donít want to climb the learning curve of a third-party library, then implementing a custom ORM might make sense. There's alos the educational aspect of the process (yes, learning one or two things never hurts).

If this topic has caught your attention, over the course of this tutorial Iíll be building from scratch a small ORM. You'll be able to customize and extend it at will to fit your personal requirements.

With that said, itís time to start showing you some concrete and functional code. Letís get going!

Building a Simple Data Persistence Layer

As you know, in many cases the functionality of an ORM is encapsulated behind a set of classes responsible for handling the relationships between different types of objects, which are persisted in a RDBMS via an Active Record implementation, or in more modern libraries, via the Data Mapper pattern (ala Doctrine 2.x, where domain objects are persistence agnostic).

In this case, for obvious reasons, Iím going to take a much simpler approach: the workhorse of my ORM will be a mapping layer, which will not only move data between a domain model and the storage, but will be capable of reconstituting entities having a one-to-one and one-to-many relationship with other entities (the so-called aggregate roots in Domain-Driven Design).

The first step is to create the layer tasked with interacting with the underlying persistence mechanism. For the sake of simplicity, this will rest on a MySQL database. This layer is simple; its implementation is as follows:

(Blog/Library/Database/DatabaseAdapterInterface.php)

<?php

namespace Blog\Library\Database;

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


((Blog/Library/Database/MysqlAdapter.php)

<?php

namespace Blog\Library\Database;

class MysqlAdapter implements DatabaseAdapterInterface
{
    protected $_config = array();
    protected $_link;
    protected $_result;
   
    /**
     * Constructor
     */
    public function __construct(array $config)
    {
        if (count($config) !== 4) {
            throw new \InvalidArgumentException('Invalid number of connection parameters.');  
        }
        $this->_config = $config;
    }
   
    /**
     * 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 \RuntimeException('Error connecting to the server : ' . mysqli_connect_error());
            }
            unset($host, $user, $password, $database);
        }
        return $this->_link;
    }

    /**
     * Execute the specified query
     */
    public function query($query)
    {
        if (!is_string($query) || empty($query)) {
            throw new \InvalidArgumentException('The specified query is not valid.');
        }
        // lazy connect to MySQL
        $this->connect();
        if (!$this->_result = mysqli_query($this->_link, $query)) {
            throw new \RuntimeException('Error executing the specified query ' . $query . mysqli_error($this->_link));   
        }
        return $this->_result;
    }
   
    /**
     * 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();
    }
   
    /**
     * Escape the specified value
     */
    public function quoteValue($value)
    {
        $this->connect();
        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)) === false) {
                $this->freeResult();
            }
            return $row;
        }
        return false;
    }

    /**
     * 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) {
            return false;
        }
        mysqli_free_result($this->_result);
        return true;
    }
   
    /**
     * Close explicitly the database connection
     */
    public function disconnect()
    {
        if ($this->_link === null) {
            return false;
        }
        mysqli_close($this->_link);
        $this->_link = null;
        return true;
    }
   
    /**
     * Close automatically the database connection when the instance of the class is destroyed
     */
    public function __destruct()
    {
        $this->disconnect();
    }
}
    
If you've ever come across some of my PHP articles here at Developer Shed, you may already be familiar with the inner workings of the above ďMysqlAdapterĒ class and the interface that it implements. In either case, this adapter acts like a simple wrapper for the ďmysqliĒ PHP extension, which among other things, runs queries in a specified database, fetches rows and retrieves insertion IDs. It's nothing really overcomplicated, to be honest.

Nevertheless, Iíd like you to notice one subtle detail: the classís top-level namespace is called ďBlog.Ē I  did this because I plan to demonstrate the functionality of my humble ORM by deploying a blog application. Due to the flexibility of the project, however, the domain model will be easily substitutable, so feel free to switch to the top-level namespace that suits your needs best.

Having clarified that, itís safe to say that the ORMís persistence layer is ready to go. Therefore, itís time to develop a few more components. As I said a moment ago, the main functionality of the ORM will be built around the data mapper pattern. Therefore, in the coming section Iíll be implementing a simple hierarchy of mappers, which will be tasked with fetching, saving to and deleting entities from the aforementioned MySQL database.

To see how these brand new components will be created, click on the link that appears below and keep reading.



 
 
>>> More MySQL Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

MYSQL ARTICLES

- Oracle Unveils MySQL 5.6
- MySQL Vulnerabilities Threaten Databases
- MySQL Cloud Options Expand with Google Cloud...
- MySQL 5.6 Prepped to Handle Demanding Web Use
- ScaleBase Service Virtualizes MySQL Databases
- Oracle Unveils MySQL Conversion Tools
- Akiban Opens Database Software for MySQL Use...
- Oracle Fixes MySQL Bug
- MySQL Databases Vulnerable to Password Hack
- MySQL: Overview of the ALTER TABLE Statement
- MySQL: How to Use the GRANT Statement
- MySQL: Creating, Listing, and Removing Datab...
- MySQL: Create, Show, and Describe Database T...
- MySQL Data and Table Types
- McAfee Releases Audit Plugin for MySQL Users

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: