The Data Mapper Design Pattern: A Final Example

Welcome to the final part of a series on implementing the Data Mapper design pattern in PHP 5. This series teaches you the key concepts that surround the development of data mapper classes. It complements the corresponding theory with a decent variety of code samples.

And now that you’ve been introduced to the primary goal of this series, it’s time to review the topics that were discussed in the previous part. In the course of that tutorial I finished building a basic data mapper class, which in this particular case was responsible for mapping user domain objects to their associated MySQL table via a clean interface.

As one would expect from an appropriate implementation of a data mapper class, the one mentioned above was provided with the ability to perform CRUD operations on the target database table, while keeping the referenced domain objects entirely ignorant of their persistent storage mechanism.

Although describing the role of a data mapper is a fun and instructive process, the best way to understand its actual functionality is by seeing some illustrative examples. Bearing that idea in mind, in this last installment of the series I’m going to code for you a hands-on example that will show you how to utilize the user mapper class built previously to fetch, insert and delete a few user domain objects.

The experience will be fairly educational, trust me. Now, click on the link below and start reading right now!

{mospagebreak title=Review: the earlier domain object and the data mapper classes}

Before I show you how to use the data mapper class built in the previous chapter, it would first be helpful to reintroduce the definitions of all of the classes that I’ve developed so far in this series. This way you can recall (or learn) what they do and how they fit into the domain and data access layers respectively.

Having said that, I’m going to start showing you the pair of classes whose responsibility is to create simple domain objects, or as they’re known in programming jargon, plain old PHP objects (POPO). Here they are: 

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

 

(User.php)

 

 

<?php

 

 

class User extends DomainObjectAbstract

{

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

}

As I stated before, the two above classes compose the hierarchy required for building domain objects that are easy to test and maintain. The latter is tasked with creating concrete user objects, but naturally it’s possible to derive another subclass from the abstract parent “DomainObjectAbstract” and generate other sets of domain objects, such as blog posts, comments, and so forth.

Having listed the classes that make up the domain layer, it’s time to show the one that comprises the data access layer. It’s a MySQL adapter whose definition is as follows:

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

 

 

Considering that the logic implemented by the previous “MySQLAdapter” class is very easy to follow, let me show you the definition of a couple more. These are the mappers that glue together the two layers you just saw. Here’s how these mappers look:

 

 

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

        }

    }

} 

Well, if you take a peek at the definition of the above “UserMapper” class you’ll realize quickly how it works. Of course, the easiest way to understand how to put the previous mapper class to work is by coding a concrete example.

Considering the rather large number of source classes that need to be included on demand to put the mapper into action, in the section to come I’m going to define a basic autoloader class. It will take care of loading all of the requested classes behind the scenes.

Now, to see how this autoloader class will be created, click on the link below and read the lines to come.

{mospagebreak title=Building a simple autoloader class}

To simplify the code required to demonstrate the use of the previous user mapper class, in this section I’m going to define a basic autoloader. It will permit you to include all of the classes shown before through a unique, centralized point.

The logic implemented by the autoloader is very trivial; it looks like this: 

(Autoloader.php)

 

 

<?php

 

 

// register default autoloader class

spl_autoload_register(array(‘Autoloader’, ‘autoload’));

 

 

// define class ‘Autoloader’

class Autoloader

{

    public static function autoload($class)

    {

        if (class_exists($class, FALSE))

        {

            return TRUE;

        }

        $file = $class . ‘.php';

        if (!file_exists($file))

        {

            require_once ‘ClassNotFoundException.php';

            throw new ClassNotFoundException(‘The file containing the requested class was not found.’);

        }

        require_once $file;

        unset($file);

        if (!class_exists($class, FALSE))

        {

            require_once ‘ClassNotFoundException.php';

            throw new ClassNotFoundException(‘The requested class was not found.’);

        }

    }  

}

If you’ve ever developed autoloader classes before, you’ll find the definition of the above one easy to follow. It behaves like a basic wrapper for the “require_once” PHP construct. In addition, you can see that the autoloader will throw a custom exception at the appropriate places when a requested file or class is not found. So, below I included the file that contains this user-defined exception class:

(ClassNotFoundException.php)

 

 

<?php

 

 

class ClassNotFoundException extends Exception{}

Now that there exists an autoloader that will lazy load all the sample classes  listed in the preceding segment, we’re finally ready to put the previous user mapper class into action. Therefore, in the last segment I’m going to code a script that will show how to use the mapper to translate data stored in a MySQL table into user domain objects, and vice versa.

To understand the details of this process, read the following section.

{mospagebreak title=Putting the user mapper class to work}

To be frank, this final section of the tutorial should justify all of the effort that you’ve made so far when reading the whole series. In it you’ll see why data mapper classes can be so useful when building web applications that keep their domain objects isolated from the underlying storage mechanism used to persist between requests.

Now, it’s example time! So, suppose for a moment that a MySQL table has been previously created, and that its schema allows you to store the first and last names of users along with their email addresses. That’s a fairly typical scenario, isn’t it? Well, look at the following script. It shows how to use the user mapper class to fetch, insert and delete users from the table in question via the mapper’s interface:

<?php

try

{

    // include ‘Autoloader’ class

    require_once ‘Autoloader.php';

   

    $db = MySQLAdapter::getInstance(array(‘host’, ‘user’, ‘password’, ‘database’));

   

    // create instance of ‘UserMapper’ class

    $userMapper = new UserMapper($db);

   

    // get existing user by ID

    $user = $userMapper->find(1);

    // display user data

    echo ‘ID: ‘. $user->id . ‘<br />First Name: ‘ . $user->fname . ‘<br />Last Name: ‘ . $user->lname . ‘<br />Email: ‘ . $user->email;

   

    // create a new user and add her to the database

    $user = new User();

    $user->fname = ‘Sandra';

    $user->lname = ‘Smith';

    $user->email = ‘sandra@domain.com';

    $userMapper->save($user);

   

    // update existing user

    $user = $userMapper->find(2);

    $user->fname = ‘Sandy';

    $userMapper->save($user);

   

    // delete existing user

    $userMapper->delete($user);  

}

 

 

// catch DomainObject Exceptions

catch (DomainObjectException $e)

{

    echo $e->getMessage();

    exit();

}

 

 

// catch ClassNotFound Exceptions

catch (ClassNotFoundException $e)

{

    echo $e->getMessage();

    exit();

}

 

 

// catch MySQLAdapter Exceptions

catch (MySQLAdapterException $e)

{

    echo $e->getMessage();

    exit();

}

 

As the above example demonstrates, once an instance of the user mapper class has been created, performing CRUD operations on the fictional “users” MySQL table is a breeze. For obvious reasons, you might say that the same result can be achieved by using an Active Record implementation, and yes, you’d be right.

However, in this case the domain objects don’t know anything about how to fetch, save or update themselves (the so-called persistence ignorance), as it’s the responsibility of the mapper to perform these operations.

And with this final example, I’m finishing this series on using data mappers in PHP. At this time, you should be pretty familiar with the logic that stands behind implementing this database access pattern, so start building your own data mapper classes!

Final thoughts

Sad but true, we’ve come to the end of this series. As you saw for yourself, in many cases the implementation of data mappers is often coupled with other data access patterns, such as an Identity Map or a Unit of Work. Such a map was used in the previous example, by the way. Units of Work, incidentally, are particularly useful for inserting, updating and deleting a group of domain objects in one go, which avoids unnecessary trips to the database. For the moment, though, I guess you’ll have longs hours of fun playing with the previous data mapper classes.

See you in the next PHP tutorial!

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