on Apr 11th, 2008DO/DM Part2: LazyLoading, EmbeddedValue and some more…

Welcome traveler, to the second part in my Domain Object / Data Mapper guide & how-to & tutorial, this time we’re going to look at a more sophisticated implementation of the Data Mapper pattern that automates a lot more functionality - but we have to pay a price in complexity for that automation. We’re also going to look at a few new concepts: Lazy Loading, Embedded Value, A smarter insert/save() and possibly some Inheritance Mapping. As with the previous part the first thing you should do is to download this .zip-file containing all the classes and examples used in this article..

The first thing we should look up is the data_mapper_part2/lib/DataMapper.php file. Now if you remember the last DataMapper class used in my previous post, it was quite short; just one line - this DataMapper is a whole other beast; it weights in at about 230 lines of code including comments. Now, why have I done this? The previous class was so simple. Well, this is done mostly to give you more of a “real world” example of a proper Data Mapper that actually could be used in a real application (well almost), it also automates some things we had to do by hand, the mappers are not static anymore to allow for proper inheritance and we got a couple of convenience methods for working with our PDO-connection. So, assuming you’ve opened the DataMapper.php file by now, let’s start dissecting it - first up are our precious properties:

  • static $mappers - An array of all our instantiated mappers
  • static $dbconn - Our database connection
  • static $modelpath - The path where we can find our data mappers and domain objects
  • $domclass - DomainObject class for this mapper, DataMapper::__construct() tries to auto-resolve this from it’s own class name if we don’t overload it turning “PostMapper” into “Post”, etc.
  • $pkey - The primary key table field for this mapper
  • $table - The table name for this mapper, DataMapper::__construct() tries to auto-resolve this from it’s $domclass field if we don’t overload it in sub-classes, turning “Post” into “posts”
  • $map - Holds our IdentityMap object (also new for this part) that takes care of all object identities
  • $findstmt - A prepared PDOStatement that is created in DataMapper::__construct() and used by DataMapper::find() to achieve an automatic find()-method that works for subclasses
  • $deletestmt - A prepared PDOStatment that we created in DataMapper::__construct() to allow for an automatic delete()-method that works for all subclasses

After the properties, comes the methods - let’s skip the talk and get right to business:

  • static setConnection($dbconn) - Sets the database connection all the mappers use
  • static setModelPath($path) - Sets the path where we can find all our Data Mappers and Domain Objects
  • static getMapper($name) - Retrieves a mapper of $name type
  • static protected loadMapper($name) - Called by getMapper()-internally to load a mapper if it doesn’t exist
  • find($id) - Retrieves an object by primary key value (id) and returns it, using the $findstmt-prepared statement
  • save($obj) - Saves an object, calling either update() or insert() depending on if it’s an old or new object
  • delete($id) - Takes either and $id or an Domain object instance and deletes the row representing it in the database, using the $deletestmt-prepared satement
  • protected __construct - Constructor, tries to autoresolve the table name and domain object class name if they’re not overloaded in the subclass, also creates the IdentityMap object and puts it in the $map-field, last it sets up the two prepared statements $findstmt and $deletestmt used by find() and delete()
  • protected lastAutoincrementId() - Convenience method that returns the last auto-increment id
  • protected prepareExec($sql, $params) - Convenience method that takes an $sql string and $parameters creates a statement out of them and executes it, returning the statement
  • protected fetchAll($sql, $params) - Convenience method that returns all rows matching $sql with $parameters
  • protected fetchFirst($sql, $params) - Convenience method that returns the first row matchin $sql with $parameters
  • protected createObject($row) - Called internally to create an object from a $row, checks if the $row already has an $object in $map and returns that if so, if not it calls buildObject() with the $row and a fresh instance of our domain object ($domclass) class for this mapper
  • abstract protected update($obj) - Updates an object row in the database, overload in subclass
  • abstract protected insert($obj) - Inserts an object row into the database, overoad in subclass
  • abstract protected populateObject($row, $obj) - Populates $obj with fields from $row, overload in subclass

Phew *wipes forehead*, that’s all of them - this is starting to look more like an API documentation project then something about Data Mappers, so let’s get onto business. As you can see from the wall of text above explaining the properties and methods, we not have our find() and delete() methods *auto-magically* for every subclass of Data Mapper, so let’s wipe the PostMapper and UserMapper from the last part clean and just implement populateObject(), update() and insert() for them. That gives us complete CRUD (Create, Retrieve, Update and Delete) functionality from our mappers with just three methods. Open up models/UserMapper.php and let’s have a look at our three new methods. First out is UserMapper::populateObject() which is… simple, to say the least:

function populateObject (array $row, DomainObject $user) {
	$user->setName($row['name']);
}

Our 230-lines long DataMapper-class takes care of everything else, even setting the $id-property of the User, so the only thing we need to do is to set it’s name since this can’t be figured out automatically from mapper (or well, it can - but our mapper is to simple to do it at the moment), there’s not even any need to return the $user object.

The update() and insert() methods look pretty much alike, so we’ll look at them side by side:

protected function update (DomainObject $user) {
	$sql	= sprintf('update `%s` set `name` = ? where `%s` = ?', $this->table, $this->pkey);
	$stmt	= self::$dbconn->prepare($sql);
	$params	= array($user->getName(), $user->getId());

	$stmt->execute($params);
}

protected function insert (DomainObject $user) {
	$sql	= sprintf('insert into `%s` (`name`) values(?)', $this->table);
	$stmt	= self::$dbconn->prepare($sql);
	$params	= array($user->getName());

	$stmt->execute($params);
}

The first three rows in both update and insert are almost identical, we create a sql statement using the $table and $pkey properties, then prepare it using our $dbconn object and then setup the parameters, the update() method requires two parameters - first the name fields new value and then the object id, while insert only requires the name. That’s all there is to update() and insert() thanks to our clever base class DataMapper, let’s look at the DataMapper::save() method closer, here it is (without comments):

function save (DomainObject $obj) {
	if ($this->map->hasObject($obj)) {
		$this->update($obj);
	} else {
		$this->insert($obj);
		$obj->setId($this->lastAutoincrementId());
		$this->map->add($obj);
	}
}

As you can see save() first checks to see if the $map already has the object in it, if it does update it and be done. If the map doesn’t have the object, insert it and then set it’s Id to the lastAutoincrementId, and then add it to the map. Lets try this out in practice, I present to you listing1.php:

<?php
require	'bootstrap.php';
$userMapper = DataMapper::getMapper('User');

$yoda = new User;
$yoda->setName('Yoda');
var_dump($yoda->getId()); // NULL

$userMapper->save($yoda);
var_dump($yoda->getId()); // int(1)

$userMapper->save($yoda);
var_dump($yoda->getId()); // int(1)

First we just require boostrap.php that setups and requires all classes we need, next we retrieve the $userMapper with the static DataMapper::getMapper()-call. Secondly we create a new User, name it Yoda and save it with save() on $userMapper, now since this is a new object it won’t have an Id or be in the IdentityMap contained in $userMapper so it will call insert() this time creating the row and then assign the newly created auto-increment id to $yoda, var_dump():ing $yoda->getId() proves that the id has been set and the object is in the database, calling save() on $yoda will cause update() to trigger instead updating the row in the database.

Lazy Loading

So far everything we’ve been doing has just been a more abstract and enhanced version of what we did in part one, so let’s step it up a notch and do some Lazy Loading. If you remember in the first part where the PostMapper::findAll() method caused the User for each post to be loaded also, sometimes this isn’t optimal - so we’re going to introduce something called lazy loading that only loads the user object when we access it. Let’s have a look at the PostMapper::populateObject() method that we use to create a Post object:

function populateObject (array $row, DomainObject $post) {
	$post->setText($row['text']);
	$post->___lazyLoadSet___('user', $row['user_id']);
}

The first row looks familiar from part one, the second row is a bit weird tho, we call a method called ___lazyLoadSet__(), this is in fact just a “back door” method defined in DomainObject that lets us set an object property without going through the accessor-methods. If you remember the setUser() method required a User object, and since we’re giving it an integer here (the user_id) it would fail, there are other ways around this “issue” but this is the one that is most easily explained - and for the record, the DomainObject::___lazyLoadSet___ method looks like this:

function ___lazyLoadSet___ ($field, $value) {
	$this->$field = $value;
}

For this to work we have to modify the Post::getUser() method a bit, so it will check if the Post::$user property is an object or not, and if it’s not try to load the object that should be there in it’s place - here’s how it looks:

function getUser () {
	if (is_numeric($this->user)) {
		$this->user = DataMapper::getMapper('User')->find($this->user);
	}
	return $this->user;
}

A very simple modification of the old getUser() allows it to check if $this->user is a numeric value (not a User object) and if it is it replaces the numeric value with the real User object that should be there - but the key here is that it only happens when you access the getUser() method of the Post object, if you don’t access it the user will never be created

Before we put this all into motion I want to show two last methods, namely PostMapper::update() - since the Post is not guaranteed to always have the User object that is associated with it (for example if we load a Post, just change it texts and then save() it back again, the user will never be created) the update() method needs to be able to handle this (PostMapper::insert() looks exactly like the old PostMapper::insert() from part one except that it uses our new IdentityMap object, insert() always needs a proper User object so it can call getUser() without caring) without loading the User object from the database by calling getUser() immediately, for this we have another “magic” method called DomainObject::___lazyLoadGet___ that is the counterpart of ___lazyloadSet___ that allows us to retrieve a value without going through the get()-accessor method for it, it looks like this:

function ___lazyLoadGet___ ($field) {
	return $this->$field;
}

So, with this method ready at hand let’s go and take a look at PostMapper::update() (without comments):

protected function update (DomainObject $post) {
	if ($post->___lazyLoadGet___('user') instanceof User) {
		$user = $post->getUser();

		$userMapper = DataMapper::getMapper('User');
		if(!$userMapper->map->hasObject($user)) {
			$userMapper->save($user);
		}

		$userid = $user->getId();
	} else {
		$userid = $post->___lazyLoadGet___('user');
	}

	$sql	= sprintf('update `%s` set `text` = ?, `user_id` = ? where `%s` = ?', $this->table, $this->pkey);
	$stmt	= self::$dbconn->prepare($sql);
	$params	= array($post->getText(), $userid, $post->getId());

	$stmt->execute($params);
}

Ok, don’t be afraid - it won’t bite, let’s go through it line by line. At the top we call our ___lazyLoadGet___ method that retrieves the user property of the Post object, without triggering the autoload of getUser(), and we check if it’s an instance of the User class, if it is we retrieve it normally with getUser() and then get a hold of the $userMapper also. After that we check if the $userMapper’s identity map has the $user object in question, if it doesn’t we save() it. Next, get the id from the $user object. The else clause next is for if the ___lazyLoadGet___ call returns an value that isn’t a User object, so we just fetch it again and be on our way since it’s the user id we need. The rest of the method should be self explanatory by now so I’ll leave that for you to figure out.

So, let’s put this into motion - first let’s start with creating a few posts on our users, this is exactly the same thing we did in listing4.php in the first part: Getting an old user, creating a new and assigning one post each to them and then saving all of it to the database, so it should need no further explaination, listing2.php:

<?php
require	'bootstrap.php';
$userMapper = DataMapper::getMapper('User');
$postMapper = DataMapper::getMapper('Post');

$yoda = $userMapper->find(1);
$yodasPost = new Post;
$yodasPost->setUser($yoda);
$yodasPost->setText('Fear is the path to the dark side. Fear leads to anger.
Anger leads to hate. Hate leads to suffering.');

$hansolo = new User;
$hansolo->setName('Han-Solo');
$hansPost = new Post;
$hansPost->setUser($hansolo);
$hansPost->setText("Had a slight weapons malfunction, but everything's perfectly
alright now. We're fine, we're all fine, here, now, thank you. How are you?");

$postMapper->save($yodasPost);
$postMapper->save($hansPost);

So, as I said - almost identical to listing4.php in part one - no further explanation needed. Let’s have a look at listing3.php instead, here we retrieve all our posts with the findAll() method on PostMapper, then var_dump:ing them to show that the users are not loaded until we actually call getUser() on the Post object, listing3.php:

<?php
require	'bootstrap.php';
$postMapper = DataMapper::getMapper('Post');

$allPosts = $postMapper->findAll();
var_dump($allPosts[0], $allPosts[1]);

echo $allPosts[0]->getUser()->getName();
echo $allPosts[1]->getUser()->getName();

The var_dump() will print something like: object(Post)#6 (3) { ["text:protected"]=> string(101) "Fear is the path to the dark side. Fear leads to anger. Anger leads to hate. Hate leads to suffering." ["user:protected"]=> string(1) "1" ["id:protected"]=> int(1) } object(Post)#7 (3) { ["text:protected"]=> string(136) "Had a slight weapons malfunction, but everything's perfectly alright now. We're fine, we're all fine, here, now, thank you. How are you?" ["user:protected"]=> string(1) "2" ["id:protected"]=> int(2) }

But when we call getUser() on post #0 and #1 it will fetch the correct user object “behind the scenes” and return it instead, so we can chain getName() on there and get the expected result: YodaHan-Solo. One thing bugs me here, shouldn’t we be able to fetch all posts that are related to a User from that User object itself? Yeah we should! This requires two new methods, first: findByUser() on PostMapper and then getPosts() on User, you can look up PostMapper::findByUser() by yourself since it should (by now) be self explanatory and we’ll only look on User::getPosts() here.

function getPosts () {
	if (is_null($this->posts)) {
		$this->posts = DataMapper::getMapper('Post')->findByUser($this);
	}

	return $this->posts;
}

Looks quite a lot like Post::getUser(), basically all we do is to check if $this->posts is null, if it is replace it with the posts that have this User as their author. And that’s all there is to it, except the PostMapper::findByUser()-method. Let’s try it out, I present listing4.php:

<?php
require	'bootstrap.php';
$userMapper = DataMapper::getMapper('User');

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

foreach($yoda->getPosts() as $post) {
	echo '<p>', $post->getText(), '</p>';
}

A very simple piece of code, we retrieve $yoda which we know have Id 1 since earlier, then we just call $yoda->getPosts() and that will return all posts he has made and we loop through them, and it does indeed print something looking like this: <p>Fear is the path to the dark side. Fear leads to anger. Anger leads to hate. Hate leads to suffering.</p>, of course if you’ve inserted more posts for $yoda by refreshing listing2 all posts will print. That’s all I have to say about Lazy Loading for today - now it’s time for another subject that is at least as interesting but a bit easier on the brain.

Embedded Value

For this part of the article you will need a new .zip-file archive containing the updated files, it’s basically a clone of the previous archive without it’s examples and with the modified files required for these examples, you can find it here.

So, what are embedded values? Well, embedded values are objects that are represented by a field in a table - for example Date, Money, Temperature, etc. These fields often need to be objects but they don’t require their own table. Embedded Value is a design pattern that wraps this type of logic into an object that then is wrapped around a table field. Let’s create an EmbeddedDate (Can’t use Date as a class name since it already exists in PHP) class that wraps a unix timestamp:

<?php
class EmbeddedDate {

	protected $value = 0;

	function __construct ($value = 0) {
		$this->set($value);
	}

	function get () {
		return $this->value;
	}

	function getFormated ($format = 'Y-m-d H:i:s') {
		return date($format, (int)$this->value);
	}

	function set ($value) {
		if (is_numeric($value) or is_int($value)) {
			$this->value = (int)$value;

		} elseif(is_string($value)) {
			$this->value = strtotime($value);
		}
	}

	function __toString () {
		return $this->getFormated();
	}

}

An equally simple class, getFormat() formats the date we have while set() allows you to either input a string date, such as “2008-03-24 18:00″ or a numeric/integer date as a timestamp and get() returns the timestamp, __toString prints the default formated date. Let’s go ahead and add a new field to the User domain object called lastlogin that tells us when a user last logged in, this requires two new methods: User::getLastLogin(), and User::setLastLogin(), we also need to Modify our UserMapper::buildObject(), UserMapper::update() and UserMapper::insert() methods to accommodate for the changes in the User-object, as well as updating the users table in the database. The User-object methods look like this:

function getLastLogin () {
	return $this->lastlogin;
}

function setLastLogin (EmbeddedDate $lastlogin) {
	$this->lastlogin = $lastlogin;
}

The UserMapper::populateObject() method is changed so that we add one more line, looking like this: $user->setLastLogin(new EmbeddedDate($row['lastlogin'])); do accommodate the changes med to the User-class and users table, the insert() and update() methods just change their SQL and $params array to allow for one more field - should be self explanatory - let’s try this out in an example, I give you listing5.php:

<?php
require	'bootstrap.php';
$userMapper = DataMapper::getMapper('User');

$bobbafett = new User;
$bobbafett->setName('Bobba Fett');
$bobbafett->setLastLogin(new EmbeddedDate(time()));

$userMapper->save($bobbafett);

A rather straightforward example, we create a new user called Bobba Fett and assign a new LastLogin date to it which points to the current time() and then save it, so let’s retrieve it and see to that everything works:

<?php
require	'bootstrap.php';
$userMapper	= DataMapper::getMapper('User');

$bobbafett = $userMapper->find(3);
printf('%s last logged in: %s', $bobbafett->getName(), $bobbafett->getLastLogin());

An even shorter example, we retrieve the user with id 3 (Bobba Fett, Yoda is 1 and Han-Solo 2) and then print out his name and when he was last logged in (feeding the EmbeddedDate-object that is returned by getLastLogin() directly to printf() works because of the __toString()-method that we have defined in EmbeddedDate) the output should be something resembling this: Bobba Fett last logged in: 2008-04-11 17:21:41. If you get a warning about the systems timezone settings it’s because you’re running E_STRICT in php5+.

That will be all for today folks, getting tired here - how to map inheritance and many-to-many relationships is left for the third and maybe fourth part of this series, enjoy!

9 Responses to “DO/DM Part2: LazyLoading, EmbeddedValue and some more…”

  1. […] Update: Part2 Now Available […]

  2. jandion 11 Apr 2008 at 10:52 pm

    är det fel på mig för jag inte fattar ett piss?
    skriv dikter istället!!!

  3. Kyleon 14 Apr 2008 at 2:45 pm

    Hi Fredrik,

    once again: Two thumbs up! I’m eager to see your doctoral thesis ;)

    Two small things:

    1.) Once again your code should not depend on the way it is used. Your implementation of save() assumes, that existing objects are loaded (and stored in the IdentityMap) before they are saved/updated. If you have an update page for user object and a user changed his name you get a new user if you don’t load it first.

    Bad code:

    $user = new User( $_POST[’ID’], $_POST[’NAME’] );
    $userMapper->save( $user ); // new user created!

    So DataMapper::save() might better use “if( $obj->getId() !== null && $this->find( $obj->getId ) )” to check if an object is existing.

    2.) I’m not sure if it works in PHP, it shouldn’t anyway: Accessing the UserMappers “map” in PostMapper should not work, since it is a protected variable in a different class’s instance.

    Regards, Kyle

  4. Fredrik Holmströmon 14 Apr 2008 at 10:30 pm

    Hi Kyle,

    Thanks for your comment :), here’s my thoughts on your two points:

    1) This is a matter of ideology I think, if you should be able to effect a row in the database without loading it first. I don’t think you should, and such an update “action” would require you to first load the object and then save it with the modified field. Well, it’s also a matter of performance.

    2) Well, the $map attribute is defined in DataMapper which is the parent class to both UserMapper and PostMapper and thus they can access it (as far as I know this is all according to what people refer to as a proper object model) however if I would’ve defined an attribute just in UserMapper and protected it then no, PostMapper would not be able to access it.

  5. Kyleon 15 Apr 2008 at 9:16 am

    I was a bit surprised by 2) and even more when I found the same behavior in Java and C++ - that was new to me and I still find it strange. It a world of surprises ;)

    See you on part 3, Kyle

  6. Aon 27 Apr 2008 at 1:56 am

    Hi!

    I know I asked before, but could you please suggest some good php books, have been developing in php4 for ages and it’s finally time to take a step to a more object oriented programming (MVC-framework, patterns, etc). Could you please suggest some or point in the right direction? Thanks!

    A fellow Swede.

  7. Fredrik Holmströmon 15 May 2008 at 3:14 pm

    Sadly there are no good php books dealing with modern day design patterns, the closest you will get is “PHP Architect’s Guide to PHP Design Patterns” and the “PHP In Action” books.

    If you widen your search a bit you will come a cross a book called Patterns of Enterprise Application Architecture written by Martin Fowler, now this is a good book. All examples are in Java or C# however.

  8. Mazon 01 Jun 2008 at 10:18 pm

    Hi there. I love the blog post and this implementation of data mapper, which I intend to implement in my own application.

    I have one question: If I was to include summary data, not necessarily objects but simple scalar values, like a post count in your application, where would the data access code for that reside? It doesn’t seem to fit in the data mapper class itself, yet the data objects are meant to be database agnostic.

    The best I can think at this time is a Posts and PostsMapper pair where Posts has some methods to get summary information like post counts for users etc but this seems messy.

    Hope this is clear enough. Many thanks,
    Maz

  9. Vance Lucason 30 Dec 2008 at 11:25 pm

    Found this article doing a search - you may want to check out a project of mine that has already completed most of these nitty-gritty details.

    http://phpdatamapper.com

    You may find you like it, and it may be valuable to your visitors!

Trackback URI | Comments RSS

Leave a Reply