Dynamic Domain Objects in PHP
Modeling real-world objects with computer Data Structures is one of the more fundamental principles of Computer Science.
Data Models however are often tied heavily to their storage medium, whether that be an SQL database, files on a filesystem or a web-service. The idea of implementing a Model/Mapper system is to decouple the format in which we wish to manipulate data from the format that we store it.
PHP is a great language to attempt something like this. It’s dynamic nature allows us to create complex Domain Objects with a large range of functionality with minimal setup.
In Part 1 of this tutorial I’m going to show you how to build a generic Model base class from which you can easily extend concrete classes for use in your application.
In Part 2, I’m going to explore the role of the Mapper class, and how best to form a layer between your storage and your Domain Objects.
Not your average Model class
Take the following simple PHP Domain object:
class SerialKiller
{
private $_name;
private $_weapon;
public function __construct ($name, $weapon)
{
$this->setName ($name)->setWeapon ($weapon);
}
public function getName ()
{
return $this->_name;
}
public function getWeapon ()
{
return $this->_weapon;
}
public function setName ($name)
{
$this->_name = $name;
return $this;
}
public function setWeapon ($weapon)
{
$this->_weapon = $weapon;
return $this;
}
}
Ok, it’s a little unconventional but I’ve been watching a lot of Dexter) lately. Highly recommended! But it does illustrate a Model class in the classic sense: a class that abstract a set of data and provides a consistent interface to access it.
Many will argue that this could be done very simply using a PHP stdClass or even just a vanilla Array. What we are trying to promote here however is Encapsulation; the idea is that we hide the implementation of how our Model stores it data away from our client code.
Now that was a fair bit of code for a class that basically allows the getting and setting of two fields. In just writing that example, I had to make at least 3 typo corrections, and that’s only for two fields.
Our Model class take 2
Hmmm… seems like there would be a smarter way to do this. After all we do have a very flexible programming language to work with. Let’s try abstracting the getting and setting somewhat:
class SerialKiller // Mk.2
{
private $_data = array (
'name' => null,
'weapon' => null
);
public function __construct ($name, $weapon)
{
$this->setField ('name', $name)->setField ('weapon', $weapon);
}
public function getField ($name)
{
return isset ($this->_data[$name]) ? $this->_data[$name] : null;
}
public function setField ($name, $value)
{
if (isset ($this->_data[$name])) {
$this->_data[$name] = (string) $value;
}
return $this;
}
}
Ok, much more concise! The new and improved SerialKiller class gives us a couple of advantages over it’s predecessor:
It’s become a lot more general. For instance if we wanted to add another field, we could simply add a new key to SerialKiller::$_data.
The code is less prone to bugs since it’s shorter, more elegant and easier to read.
The code is easier to maintain now since it doesn’t contain as may functions.
However I think we can still improve more on this, say for instance I want some special functionality for the ‘weapon’ field. Say I want it to be in a range of certain values, and if it isn’t an exception should be thrown. How would I implement this?
Easy you say! You would insert a quick check into the SerialKiller::setField function that checks for a field name and acts accordingly. But of course, say I have 20 fields and 5 of them require custom handling. Not a very scalable solution!
So what’s the real problem here? The problem is twofold:
We’ve now changed the external API to something that is less consistent and meaningful than our first attempt.
Every field is accessed by calling the same function externally.
What we really need is a separate external function for each field that’s abstracted internally somehow.
Further Improvements
Remember that PHP can catch calls to non-existent functions in a class if you define a public function name __call. Let’s see what use we can make of this feature:
class SerialKiller //Mk.3
{
private $_fields = array ('name', 'weapon');
private $_data = array ();
public function __construct ($name, $weapon)
{
$this->setName ($name)->setWeapon ($weapon);
}
public function __call ($fn, $args)
{
$type = substr ($fn, 0, 3);
$name = strtolower (substr ($fn, 3));
if ($type == 'get') {
return $this->_getField ($name);
}
if ($type == 'set') {
return $this->_setField ($name, $args[0]);
}
throw new Exception ("Function {$fn} is not implemeted.");
}
private function _getField ($name)
{
$this->_checkField ($name);
return isset ($this->_data[$name]) ? $this->_data[$name] : null;
}
private function _setField ($name, $value)
{
$this->_checkField ($name);
$this->_data[$name] = $value;
return $this;
}
private function _checkField ($field)
{
if (!in_array ($field, $this->_fields)) {
throw new Exception ("Field {$field} is invalid.");
}
}
}
So there’s quite a jump between Mk.2 and Mk.3. Let me go through some of features we now have with our latest revision:
We now have the API of the first class with the internal benefits that the second class offered.
We’ve abstracted the process of adding fields to our serial killer. This means that to add a field for ‘Age’, all we’d need is to add the value ‘age’ to the SerialKiller::$_fields array.
The benefit of that is that by making that small 7 character change (, ‘age’), we now can call getAge and setAge on any SerialKiller instance automatically!
Since __call is only called when a matching function is called and not found, we can easily introduce functionality for specific fields without altering existing functions:
private function setWeapon ($weapon) { if (!in_array ($weapon, array ('knife', 'axe', 'chainsaw'))) { throw new Exception ('Invalid weapon type!'); } return $this->_setField ('Weapon', $weapon); }Our SerialKiller is now extremely generic. If we wanted to specialise his class at all, we could do so very easily. In fact, if we were to rename SerialKiller to Model and make his class an abstract one, we now have a very generic Model class capable of storing all sorts of data in a very flexible way.
In Practice
Of course I like to bring these discussions back to something more pragmatic rather than keep the theoretical SerialKiller example going.
In practice this class works extremely well. All our internal Model classes extend a base class not dissimilar to the one we created above. Of course we’ve fleshed out our version considerably. Here’s a brief description of some features:
Better inflection of field names: Current this class converts everything to lowercase. Not a great idea since it means that more than one function can map to a field (e.g. setName, setNAME, setnAMe etc.).
We like to inflect names from camel case (setFieldValue) to names that match our backend storage, usually underscore separated (field_value).
Setting & Getting fields en masse: This one is mostly useful in circumstances where you want to convert from Form data say to a Model class. We’ve incorporated this into the Constructor so creating a model from an array becomes as simple as:
$serialKiller = new SerialKiller (array (‘name’ => ‘Dexter’, ‘weapon’ => ‘knife’));
Marking Concrete instances: We’ve added an id field to our base Model class separate from the other fields. This acts as a marker for Model instances that ties it to a row in the database. Instances without their id set are considered volatile instances and will expire when they go out of scope if they’re not saved.
There are hundreds of improvements you could make on the base class, and I’d strongly urge people to make improvements of their own.
10 Comments
wow! its great how you can do these things with so much simplicity but so much more functionality. I'll Definitely refer back to this post when i implement my next domain object.
A bit lost. How does your construct allow for this:
$serialKiller = new SerialKiller (array (‘name’ = ‘Dexter’, ‘weapon’ = ‘knife’));
and allow for:
$serialKiller = new SerialKiller (‘Dexter’, ‘knife’);
The constructor for SerialKiller Mk. 3 doesn't allow for:
$serialKiller = new SerialKiller (array (‘name’ = ‘Dexter’, ‘weapon’ = ‘knife’));
I was just illustrating some of the extensions we've made to a generic Model class internally that allows easy instantiation of Models from array structures.
However, it can allow it if you implements internally a type checking on the attributes and use defaults value. That's not difficult.
This article has made me understand some obscure __call() found in the Zend Framework... Nice code.
Traditional POJO-like PHP class help code completion. Your revised SerialKiller class does not have that advantages.
It's true that 3rd party tools would find it difficult to introspect the class and enumerate it's parameters. Most IDE's will have a difficult time providing anything useful about this class without some custom extensions.
I personally use TextMate and don't have a specific need for such a solution.
But this is not a disadvantage of the Code itself. It's a tradeoff that you make for having a class that is simple to maintain and extend.
Good post!
setWeapon should have public access.
http://li.php.net/manual/en/language.oop5.overloading.php
describes the __get and __set. I personally think it's more efficient.
What about:
return $this->_data[$name] || null;
instead of:
return isset ($this->_data[$name]) ? $this->_data[$name] : null;
i would recommend using this type of objects (agree with many previous comments) the use of __get and __set and one more change i would do is use a private proverty $attributes Array() where i would put all the attributes and their values in an asociative array.
Have something to say? Post a comment!
If you have a Gravatar linked to your email address it will be displayed along with your comment.
Please say something constructive in your post. Rants are fine (and somewhat encouraged!), but anything that is plainly offensive or abusive will be removed.