Finishing a Data Mapper Class for the Data Mapper Design Pattern

In this penultimate part of the series on the data mapper design pattern, I demonstrate how to provide the UserMapper class we’ve previously created with the ability to delete users from its associated MySQL table.

Superbly described in Martin Fowler’s book “Patterns of Enterprise Application Architecture,” Data Mapper is a powerful database access pattern aimed at keeping domain objects isolated from the underlying storage mechanism (in most cases a relational database). This permits developers to build multi-layered applications that are much easier to maintain and scale.

Despite the pattern’s intimidating name, building basic data mapper classes in PHP 5 is a fairly straightforward process for any programmer with an average background in the object-oriented paradigm. On the other hand, it’s fair to say that fully-featured Object Relational Mappers (AKA ORMs) are hard-to-tame creatures that demand a lot of programming hours.

So, if you’re planning to build one of these on your own, think twice before swimming in troubled waters and look at some of the well-trusted third party ORMs available, like Doctrine and Propel, which will take care of the hard work for you.

Naturally, my intention here isn’t to demonstrate how to build an ORM from scratch. Instead, my plan is focused on explaining how to create simple data mapper classes, so you can see how useful they can be when it comes to working with domain objects that know nothing about the storage mechanism used to persist between requests.

In keeping with that goal, in the previous tutorial I defined a concrete data mapper class that partially performed CRUD operations against a MySQL table containing data on some fictional users. In simple terms, this mapper behaved as a bridge between user domain objects and the MySQL table.

However, the user mapper in its current state is still incomplete, as it does not have the ability to delete a specified user from its associated table. To address this issue, in the following lines I’m going to append to the mapper a method that will perform user deletions in a simple manner.

Ready to learn how this will be done? Then, let’s get started right now!

{mospagebreak title=Review: mapping domain objects to database tables}

As usual, before I provide the user mapper class with the capability to delete users from the associated MySQL table, I’d like to spend a few moments reintroducing the definitions of all of the sample classes developed so far. This includes the ones that allow you to create both domain objects and mappers, and the basic database driver that abstracts the access to MySQL.

First, here’s the base “DomainObjectAbstract” class that models generic domain objects:

(DomainObjectAbstract.php)

 

 

<?php

 

 

abstract class DomainObjectAbstract

{

    protected $_data = array();

   

    public function __construct(array $data = NULL)

    {

        if ($data !== NULL)

        {

            // populate domain object with an array of data

            foreach ($data as $property => $value)

            {

                if (!empty($property))

                {

                   $this->$property = $value;

                }

            }

        }

    }

   

    // set domain object property

    public function __set($property, $value)

    {

        if (!array_key_exists($property, $this->_data))

        {

            throw new ModelObjectException(‘The specified property is not valid for this domain object.’); 

        }

        if (strtolower($property) === ‘id’ AND $this->_data['id'] !== NULL)

        {

            throw new DomainObjectException(‘ID for this domain object is immutable.’);

        }

        $this->_data[$property] = $value;

    }

   

    // get domain object property

    public function __get($property)

    {

        if (!array_key_exists($property, $this->_data))

        {

            throw new DomainObjectException(‘The property requested is not valid for this domain object.’);

        }

        return $this->_data[$property];

    } 

   

    // check if given domain object property has been set

    public function __isset($property)

    {

        return isset($this->_data[$property]);

    }

   

    // unset domain object property

    public function __unset($property)

    {

        if (isset($this->_data[$property]))

        {

            unset($this->_data[$property]);

        }

    }

}

 

 

 

 

 

 

(DomainObjectException.php)

 

 

<?php

 

 

class DomainObjectException extends Exception{}

Did you recall how this class does its thing? Good. Now, it’s time to show the one that creates user domain objects. Here it is:

(User.php)

 

 

<?php

 

 

class User extends DomainObjectAbstract

{

    protected $_data = array(‘id’ => NULL, ‘fname’ => ”, ‘lname’ => ”, ‘email’ => ”);

}

The above class was easy to code and read, indeed. Therefore, let’s move on and look at the following one, which is the MySQL driver that makes up the data access layer:

(MySQLAdapter.php)

 

 

<?php

 

 

class MySQLAdapter

{

    private $_config = array();

    private static $_instance = NULL;

    private static $_connected = FALSE;

    private $_link = NULL;

    private $_result = NULL;

   

    // return Singleton instance of MySQLAdapter class

    public static function getInstance(array $config = array())

    {

        if (self::$_instance === NULL)

        {

            self::$_instance = new self($config);

        }

        return self::$_instance;

    }

   

    // private constructor

    private function __construct(array $config)

    {

        if (count($config) < 4)

        {

            throw new MySQLAdapterException(‘Invalid number of connection parameters’);  

        }

        $this->_config = $config;

    }

   

    // prevent cloning class instance

    private function __clone(){}

   

    // connect to MySQL

    private function connect()

    {

        // connect only once

        if (self::$_connected === FALSE)

        {

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

            }

            self::$_connected = TRUE;

            unset($host, $user, $password, $database);      

        }

    }

 

 

    // perform query

    public function query($query)

    {

        if (is_string($query) and !empty($query))

        {

            // lazy connect to MySQL

            $this->connect();

            if ((!$this->_result = mysqli_query($this->_link, $query)))

            {

                throw new MySQLAdapterException(‘Error performing query ‘ . $query . ‘ Error : ‘ . mysqli_error($this->_link));

            }

        }

    }

   

    // fetch row from result set

    public function fetch()

    {

        if ((!$row = mysqli_fetch_object($this->_result)))

        {

            mysqli_free_result($this->_result);

            return FALSE;

        }

        return $row;

    }

 

 

    // get insertion ID

    public function getInsertID()

    {

        if ($this->_link !== NUlL)

        {

            return mysqli_insert_id($this->_link); 

        }

        return NULL;  

    }

   

    // count rows in result set

    public function countRows()

    {

        if ($this->_result !== NULL)

        {

           return mysqli_num_rows($this->_result);

        }

        return 0;

    }

   

    // close the database connection

    function __destruct()

    {

        is_resource($this->_link) AND mysqli_close($this->_link);

    }

}

 

 

 

 

 

 

(MySQLAdapterException.php)

 

 

<?php

 

 

class MySQLAdapterException extends Exception{}

Having shown the definition of the previous “MySQLAdapter” class, it’s time now to list the full source code of the data mappers. Here you have them:

(DataMapperAbstract.php)

 

 

<?php

 

 

abstract class DataMapperAbstract

{

    protected $_db = NULL;

    protected $_table = ”;

    protected $_identityMap = array();

   

    public function __construct(MySQLAdapter $db)

    {

        $this->_db = $db;   

    }

   

    // get domain object by ID (implemented by concrete domain object subclasses)

    abstract public function find($id);

   

    // insert/update domain object (implemented by concrete domain object subclasses)

    abstract public function save(DomainObjectAbstract $domainObject);

   

    // delete domain object (implemented by concrete domain object subclasses)

    abstract public function delete(DomainObjectAbstract $domainObject);

}

(UserMapper.php)

 

 

<?php

 

 

class UserMapper extends DataMapperAbstract

{

    protected $_table = ‘users';

   

    // fetch domain object by ID

    public function find($id)

    {

        // if the requested domain object exists in the identity map, get it from the there

        if (array_key_exists($id, $this->_identityMap))

        {

            return $this->_identityMap[$id];

        }

        // if not, get domain object from the database

        $this->_db->query("SELECT * FROM $this->_table WHERE id = $id");

        if ($row = $this->_db->fetch())

        {

            $user = new User;

            $user->id = $row->id;

            $user->fname = $row->fname;

            $user->lname = $row->lname;

            $user->email = $row->email;

            // save domain object to the identity map

            $this->_identityMap[$id] = $user;

            return $user;

        }

    }

   

    // save domain object

    public function save(DomainObjectAbstract $user)

    {

        // update domain object

        if ($user->id !== NULL)

        {

            $this->_db->query("UPDATE $this->_table SET fname = ‘$user->fname’, lname = ‘$user->lname’, email = ‘$user->email’ WHERE id = $user->id");

        }

        // insert domain object

        else

        {

            $this->_db->query("INSERT INTO $this->_table (id, fname, lname, email) VALUES (NULL, ‘$user->fname’, ‘$user->lname’, ‘$user->email’)");

        }

    }

}

That was truly a lot of code to read! Yet the effort was really worthwhile, as the process reminded you how we built a few simple data mappers in PHP. What’s more, you recalled how to implement an identity map within a user mapper class, in order to keep track of what user objects have been retrieved from the database. What else can you ask for?

Well, the earlier user mapper still lacks the ability to delete users from the corresponding MySQL table. This issue obviously must be addressed properly. Therefore, in the following section I’m going to implement an additional method that will perform this deletion task in a comprehensive fashion.

Now, to see how this whole new method will be defined, click on the link below and keep reading.

{mospagebreak title=Enabling the user mapper class to delete users}

True to form, giving the previous “UserMapper” class the ability to delete users from its associated MySQL table is only a matter of coding a method that internally performs a DELETE statement via the corresponding MySQL driver and nothing else.

While this description sounds comprehensive, the best way to understand the logic that drives this method is by showing its entire implementation. That’s exactly what the code fragment below does:

// delete domain object

public function delete(DomainObjectAbstract $user)

{

    if ($user->id !== NULL)

    {

        $this->_db->query("DELETE FROM $this->_table WHERE id = $user->id");

    }

}

As you can see, the brand new “delete()” method takes a user domain object as an incoming argument, whose ID is used to perform the actual deletion operation via the MySQL adapter. I’ve seen some alternative implementations of a method like this that also directly accepts the ID of the object to be deleted, but adding this functionality is only a matter of personal preference. For the sake of brevity, the method will be kept simple.

All in all, now that the user mapper class has been given the capability to delete users through a clean interface, it’s time to show its full source code, so you’ll be able to see how this class looks after adding the “delete()” method to it that you saw before.

This will be done in the last segment of this tutorial. So click on the link below and read the next few lines.

{mospagebreak title=The user mapper class’s full source code}

As I promised in the segment that you just read, below I listed the full source code corresponding to the previous “UserMapper” class, this time including the “delete()” method discussed a moment ago:

(UserMapper.php)

 

 

<?php

 

 

class UserMapper extends DataMapperAbstract

{

    protected $_table = ‘users';

   

    // fetch domain object by ID

    public function find($id)

    {

        // if the requested domain object exists in the identity map, get it from the map

        if (array_key_exists($id, $this->_identityMap))

        {

            return $this->_identityMap[$id];

        }

        // if not, get domain object from the database

        $this->_db->query("SELECT * FROM $this->_table WHERE id = $id");

        if ($row = $this->_db->fetch())

        {

            $user = new User;

            $user->id = $row->id;

            $user->fname = $row->fname;

            $user->lname = $row->lname;

            $user->email = $row->email;

            // save domain object to the identity map

            $this->_identityMap[$id] = $user;

            return $user;

        }

    }

   

    // save domain object

    public function save(DomainObjectAbstract $user)

    {

        // update domain object

        if ($user->id !== NULL)

        {

            $this->_db->query("UPDATE $this->_table SET fname = ‘$user->fname’, lname = ‘$user->lname’, email = ‘$user->email’ WHERE id = $user->id");

        }

        // insert domain object

        else

        {

            $this->_db->query("INSERT INTO $this->_table (id, fname, lname, email) VALUES (NULL, ‘$user->fname’, ‘$user->lname’, ‘$user->email’)");

        }

    }

   

    // delete domain object

    public function delete(DomainObjectAbstract $user)

    {

        if ($user->id !== NULL)

        {

            $this->_db->query("DELETE FROM $this->_table WHERE id = $user->id");

        }

    }

}

Mission accomplished. Now, the user mapper encapsulates enough functionality to perform CRUD operations on its associated MySQL table via a fancy interface. Besides, it’s valid to stress again that it implements an identity map, a feature that makes it even more efficient when mapping domain objects to the database and vice versa.

Finally, feel free to edit and improve all of the code samples shown in this tutorial. Doing this will surely expand your current background in building data mapper classes in PHP.

Final thoughts

In this penultimate installment of the series, I demonstrated how to provide the previous “UserMapper” class with the ability to delete users from its associated MySQL table, certainly a process that didn’t raise any unexpected problems.

Now that the mapper is capable is performing full CRUD operations, the next logical step is to develop an example that shows how to use it with some user domain objects.

Precisely that educational exercise will be covered in depth in the last part of the series, so here’s my final piece of advice: don’t miss the final tutorial!

[gp-comments width="770" linklove="off" ]

chat