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.
blog comments powered by Disqus |
|
|
|
|
|
|
|