22 September, 2010

Map Objects to Database in PHP

Data Mapper Pattern
Since code and database change occurs often during development stage, the separation between domain code and database tends to be beneficial, so that change
in one does not create a need to change the other. There exists a pattern using which we can map objects to database. One probable solution for this can be a
Data Mapper Pattern.
A general idea of the mapper pattern is that a class translates attributes (properties) and methods of domain code to database fields and vice-versa.
Data Mapper is responsible for routing information between domain code and database, creating new domain objects depending on information from database and
updating/deleting information from database depending on information from domain objects.
The mapping between object-oriented code and database can be done in different ways. It can be extracted from XML files, extracting from php array in the class
itself, or hand-coding the correlation in Data Mapper Class. This way implementation of mapping php objects to database is relatively simpler.
Let’s consider an analogy; we have a problem domain for storing user information. So we would generally have two classes; User and UserMapper. So applying the
Data Mapper Pattern we can handle the mapping of objects of User class with database tables and columns using UserMapper Class. For this case we could use an
XML configuration file implementation.
In following xml file, we have “user" as root element that contains a series of field elements as shown below:



id
getId
setId


title
getTitle
setTitle


name
getName
setName



Save the file as uers.xml
The 'name' elements are actually the physical database field name. The .... holds a method "getTitle" to extract attributes.
And .... element holds User method to use when populating the object values. Information regarding creating table structure can also be
added in the xml configuration file, such as type and size of the field. Such information can be particularyl userful when we are creating some kind of
packaged installation script. The information can be used to dynamically create SQL to create database tables. This users.xml file can be read and parsed using
PHP5's SimpleXML functions, like

simplexml_load_file("users.xml");

This XML configuration file will be read by our UserMapper class which acts as Data Mapper in this case. Since Data Mapper Pattern is unobtrusive in nature,
the domain object (User class in this case) remains competely unaware to Data Mapper's (UserMapper Class in this case) existence and due to this reason all the
domain objects (methods) must provided public access to the Data Mapper. In following example, we have Users class, that have all protected attributes but
provides 'get' and 'set ' methods.
Following is our sample Users.php Class

class User {
protected $id;
protected $title;
public function setId($id) {
if (!$this->id) {
$this->id = $id;
}
}
//the following function is called whenever an undefined instance method is called
//name of missing method is passed as first parameter and method arguments as second parameter
public function __call($name, $args) {
if (preg_match("/^(get|set)(\w+)/", strtolower($name), $match)
&& $attribute = $this->validateAttribute($match[2])) {
if ("get" == $match[1]) {
return $this->$attribute;
} else {
$this->$attribute = $args[0];
}
} else {
throw new Exception(
"Undefined method User::".$name."()");
}
}
protected function validateAttribute($name) {
if (in_array(strtolower($name),
array_keys(get_class_vars(get_class($this))))) {
return strtolower($name);
}
}
public function fetch() {
return $this->title;
}
}

And following is our Data Mapper Class

class UserMapper {
protected $conn;
const INSERT_SQL = " insert into users (title, name) values (?, ?)";
const UPDATE_SQL = "update users set title = ?, name = ? where id = ? ";

public function __construct($conn) {
$this->conn = $conn;
foreach(simplexml_load_file("users.xml") as $field) {
$this->map[(string)$field->name] = $field;
}
}
public function save($user) {
$rs = $this->conn->execute(
self::INSERT_SQL
,array(
$user->getTitle()
,$user->getName()));

if ($rs) {
$inserted = $this->findById($this->conn->Insert_ID());
//clean up database related fields in parameter instance
$user->setId($inserted->getId());
}
else {
throw new Exception("Error: ".$this->conn->errorMsg());
}
}
public function findById($id) {
$row = $this->conn->getRow("select * from users where id = ?"
,array((int)$id)
);
if ($row) {
return $this->createUserFromRow($row);
}
else {
return false;
}
}
protected function createUserFromRow($row) {
$user = new User($this);
foreach($this->map as $field) {
$setproperties = (string)$field->presentor;
$value = $row[(string)$field->name];
if ($setproperties && $value) {
call_user_func(array($user, $setproperties), $value);
}
}
return $user;
}
public function add($title, $name) {
$user = new User;
$user->setTitle($title);
$user->setName($name);
$this->save($user);
return $user;
}
protected function insert($user) {
$rs = $this->conn->execute(
self::INSERT_SQL
,array(
$user->getTitle()
,$user->getName()));
if ($rs) {
$inserted = $this->findById($this->conn->Insert_ID());
//clean up database related fields in parameter instance
if (method_exists($inserted,"setId")) {
$user->setId($inserted->getId());
}
}
else {
throw new Exception("DB Error: ".$this->conn->errorMsg());
}
}
public function save($user) {
if ($user->getId()) {
$this->update($user);
}
else {
$this->insert($user);
}
}
protected function update($user) {
$binds = array();
foreach(array("title","name","id") as $fieldname) {
$field = $this->map[$fieldname];
$getproperties = (string)$field->access;
$binds[] = $user->$getproperties();
}
$this->conn->execute(
self::UPDATE_SQL
,$binds);
}
public function delete($user) {
$this->conn->execute(
"delete from users where id = ?"
,array((int)$user->getId()));
}
}
?>

So, in this way using Data Mapper Pattern, we can map php objects to database in a simple way.

No comments: