The inane, sometimes insane, ramblings from the mind of Brenton Alker.
Data Mapper Pattern in PHP
I have been trying to get together a post on the Data Mapper pattern since I started experimenting with it in a personal project. It seems to me to be a fantastic answer to the decoupling of in-memory data objects and the data store. I still don’t have all the answers, but Rob Allen’s recent post On models in a Zend Framework application, and the associated discussion provoked me to publish some of what I do have.
Warning: This post will be quite long and is very code heavy, but I hope it illustrates some of my current thoughts.
Anyway, lets get down to it.
I will be showing 4 classes. Two (2) Abstract “library” classes, and 2 examples of their use;
And now, the code. Instead of breaking the code into little pieces, I’ll add inline comments to explain what’s going on.
Classes
Library Classes
MapperAbstract
MapperAbstract is the base class for mappers. It defines a standard interface for all mappers.
<?phpabstractclassMapperAbstract{/** * Create a new instance of the DomainObject that this * mapper is responsible for. Optionally populating it * from a data array. * * @param array $data * @return DomainObjectAbstract */publicfunctioncreate(array$data=null){$obj=$this->_create();if($data){$obj=$this->populate($obj,$data);}return$obj;}/** * Save the DomainObject * * Store the DomainObject in persistent storage. Either insert * or update the store as required. * * @param DomainObjectAbstract $obj */publicfunctionsave(DomainObjectAbstract$obj){if(is_null($obj->getId())){$this->_insert($obj);}else{$this->_update($obj);}}/** * Delete the DomainObject * * Delete the DomainObject from persistent storage. * * @param DomainObjectAbstract $obj */publicfunctiondelete(DomainObjectAbstract$obj){$this->_delete($obj);}/** * Populate the DomainObject with the values * from the data array. * * To be implemented by the concrete mapper class * * @param DomainObjectAbstract $obj * @param array $data * @return DomainObjectAbstract */abstractpublicfunctionpopulate(DomainObjectAbstract$obj,array$data);/** * Create a new instance of a DomainObject * * @return DomainObjectAbstract */abstractprotectedfunction_create();/** * Insert the DomainObject to persistent storage * * @param DomainObjectAbstract $obj */abstractprotectedfunction_insert(DomainObjectAbstract$obj);/** * Update the DomainObject in persistent storage * * @param DomainObjectAbstract $obj */abstractprotectedfunction_update(DomainObjectAbstract$obj);/** * Delete the DomainObject from peristent Storage * * @param DomainObjectAbstract $obj */abstractprotectedfunction_delete(DomainObjectAbstract$obj);}
DomainObjectAbstract
A basic class describing an object that is part of the Domain Model. This model assumes that all DomainObjects have an integer as the primary key, but it would be a trivial change to make it a GUID for example. It also ensures the ID is immutable.
<?phpabstractclassDomainObjectAbstract{protected$_id=null;/** * Get the ID of this object (unique to the * object type) * * @return int */publicfunctiongetId(){return$this->_id;}/** * Set the id for this object. * * @param int $id * @return int * @throws Exception If the id on the object is already set */publicfunctionsetId($id){if(!is_null($this->_id)){thrownewException('ID is immutable');}return$this->_id=$id;}}
Example Implementation Classes
User
A concrete DomainObject describing a user. This is a very barebones example, usually getters/setters would be used to set protected properties.
1234567891011121314151617181920
<?phpclassUserextendsDomainObjectAbstract{public$firstname;public$lastname;public$username;/** * Get the full name of the User * * Demonstrates how other functions can be * added to the DomainObject * * @return string */publicfunctiongetName(){return$this->firstname.' '.$this->lastname;}}
UserMapper
A concrete implementation of a mapper between the User domain object and persistent storage.
<?phpclassUserMapperextendsMapperAbstract{/** * Fetch a user object by ID * * An example skeleton of a "Fetch" function showing * how the database data ($dataFromDb) is used to * create a new User instance via the create function. * * @param string $id * @return User */publicfunctionfindById($id){// Query database for User with $id// ...$dataFromDb=array('id'=>$id,'firstname'=>'Brenton','lastname'=>'','username'=>'Tekerson',);return$this->create($dataFromDb);}/** * Poplate the User (DomainObject) with * the data array. * * This is a very simple example, but the mapping * can be as complex as required. * * @param DomainObjectAbstract $obj * @param array $data * @return User */publicfunctionpopulate(DomainObjectAbstract$obj,array$data){$obj->setId($data['id']);$obj->firstname=$data['firstname'];$obj->lastname=$data['lastname'];$obj->username=$data['username'];return$obj;}/** * Create a new User DomainObject * * @return User */protectedfunction_create(){returnnewUser();}/** * Insert the DomainObject in persistent storage * * This may include connecting to the database * and running an insert statement. * * @param DomainObjectAbstract $obj */protectedfunction_insert(DomainObjectAbstract$obj){// ...}/** * Update the DomainObject in persistent storage * * This may include connecting to the database * and running an update statement. * * @param DomainObjectAbstract $obj */protectedfunction_update(DomainObjectAbstract$obj){// ...}/** * Delete the DomainObject from persistent storage * * This may include connecting to the database * and running a delete statement. * * @param DomainObjectAbstract $obj */protectedfunction_delete(DomainObjectAbstract$obj){// ...}}
Example
Ok, I realize that I’ve dumped a lot of code and we’re nearly finished. Now we finally get to see how it all fits together to do something. Here, I am creating a simple example where I fetch a User based on their ID, change the Users name, and store it back to the database.
1234567891011
<?php// Initialise the Mapper.$userMapper=newUserMapper();// Fetch and manipulate the User object$user=$userMapper->findById(1);$user->lastname='Alker';// Tell the UserMapper that the User needs to be saved.$userMapper->save($user);
Well, I hope everything makes sense. This is a very simplified version of the concepts I have been working on.
These simple examples could be expanded in may directions. Such as;
Validation of the DomainObject fields, I haven’t decided if the validation should be contained in the DomainObject or the Mapper.
An identity map to ensure only 1 instance of each DomainObject exists in memory at a time.
A UnitOfWork to track which objects need saving, deleting and to co-ordinate roll backs on error, or the order of persistence to satisfy foreign key constraints.
The Mapper could access many data stores; RDBMSs, Session/Cookie data or web services to name a few. Remember, the Mapper decouples the DomainObject completely from the persistent storage
Anyway, I hope this provides a fairly simple concrete example that you can build upon to create a more complete solution that you can then share with the rest of us.