ORM the manual way

Kore Nordmann’s post “Why Active Record sucks” explains some of the issues you get by using the popular Active Record pattern for ORM purpose in more complex situations.

“AR is just a wrapper for database access and not appropriate as an ORM. Used carefully, or with very simple applications it really may proof useful.”

I often find myself considering an ORM tool when I start working on a new database library for a client. But every time I fall back to the approach I’m most familiar with and know would give the best performance and the best API design - do it myself.

I normally use a simple solution using the Singleton pattern for managing the database connection, the Value Object pattern for all business objects and the Data Mapper pattern to manage the mapping between business objects and the database schema. It’s as simple as that – handcrafted and no magic going on.

Here’s a simplified directory layout:

lib/
  client/
    trunk/
      src/
        DataMapper.php
        ValueObject.php
        Singleton.php
        ClientSingleton.php – extends Singleton
        Product.php – extends ValueObject
        ProductMapper.php – extends DataMapper
      tests/
        ProductMapperTest.php

I tend to use a set of abstract classes to more easily follow the patterns used. The following sections shows an example of each of these classes.

Singleton

“A class that can be instantiated only once.”

abstract class Singleton {
	private static $instance;

	private function __construct() {}
	private function __clone() {}
	abstract public static function getInstance();
}

A simple implementation of this class could look something like this:

class ClientSingleton extends Singleton {

	public static function getConnection($hostname='',$database='',$username='',$password='') {
		if(!parent::$instance) {
			parent::$instance = new PDO("mysql:host={$hostname};dbname={$database}",$username,$password,array());
		}
		return parent::$instance;
	}

	public static function getInstance() {
		return self::getConnection();
	}
}

Assuming you have your connection settings in a $config-array you can initialize the singleton in the start of your script:

ClientSingleton::getConnection($config['db.hostname'], $config['db.database'], $config['db.username'], $config['db.password']);

When the connection is set up all mapper classes can get the database connection by calling ClientSingleton::getConnection().

The Value Object

“An object whose conceptual identity is based on a combination of values of its properties.”

class ValueObject implements ArrayAccess {
	public function __get($name) {
		$getter = 'get'.$name;
		if(method_exists($this, $getter)) {
			return $this->$getter();
		} else {
			throw new Exception("Undefined property: $name");
		}
	}

	public function __set($name, $value) {
		$setter = 'set'.$name;
		if(method_exists($this, $setter)) {
			$this->$setter($value);
		} else {
			throw new Exception("Undefined property: $name");
		}
	}

	// functions to implement ArrayAccess
	public function offsetExists($offset) {
		return isset($this->$offset);
	}

	public function offsetGet($offset) {
		$getter = 'get'.$offset;
		return $this->$getter($offset);
	}

	public function offsetSet($offset, $value) {
		$setter = 'set'.$offset;
		$this->$setter($value);
	}

	public function offsetUnset($offset) {
		$setter = 'set'.$offset;
		$this->$setter(null);
	}
}

I find it useful to be able to access data inside a value object in various ways. Having these methods in place and given the following Product class:

class Product extends ValueObject {
	private $id;
	private $name;
	private $attributes;

	public function __construct() {
		$this->id = 0;
		$this->name = '';
		$this->attributes = new ArrayObject();
	}

	public function getID() {
		return $this->id;
	}

	public function setID($value) {
		$this->id = $value;
	}

	public function getName() {
		return $this->name;
	}

	public function setName($value) {
		$this->name = $value;
	}

	public function getAttributes() {
		return $this->attributes;
	}

	public function setAttributes($value) {
		$this->attributes = $value;
	}
}

I can set values with:

$product->setName("One name");
$product->Name = "Another name";
$product['Name'] = "A third name";

and get values with:

echo $product->getName();
echo $product->Name;
echo $product['Name'];

I also find it very useful to use the ArrayObject from SPL to do stuff like:

$product->Attributes['weight'] = "2.8 kg";

or:

foreach($product->Attributes as $key => $value) {
  echo "$key: $value\n";
}

The Data Mapper

“Moves data between objects and a database while keeping them independent of each other and the mapper itself.”

abstract class DataMapper {
	abstract public static function findByID($id);
	abstract public static function save($object);
	abstract public static function delete($object);
	abstract protected static function insert($object);
	abstract protected static function update($object);
}

These are just the basic set of methods I use in my data mappers. The save() is just a proxy for either insert() or update(). And I always end up with a few more finder methods for various purposes.

I hope you find some of this useful. This approach has at least helped me a lot. By doing ORM this way you are able to manage both domain-driven design and database-driven design and twist them both in the way you want.

6 Comments

Eduardo Pérez  on August 29th, 2007

Clear, concise and precise; thanks, thanks, thanks.

Carl  on May 28th, 2008

Jeg forsøkte denne strukturen til en superenkel kommunikasjon med et flash-spill. Takk skal du ha :)

Alvaro Carrasco  on June 25th, 2008

Some issues:
- Singleton::$instance is currently private which would make it inaccessible to the ClientSingleton class
- With code like ‘parent::$instance = new PDO(…)’, wouldn’t every sub-class of Singleton override each other’s instances when you call a method like getConnection() ?

Aside from that, it seems like a very nice way of dealing with entities, if you’re not happy with the existing ORM solutions.

Matthew Browne  on October 24th, 2008

Great post… I’m wondering, how would you handle the retrieval of more than one object at a time?…especially if it involved more than one database table, and you also needed pagination?

Vance Lucas  on December 30th, 2008

Good post - but how about ORM the automated way? :)

I created an open source project called phpDataMapper a few months ago that may be of good use to you and your readers. It’s simple, lightweight, and stays true to the definition of the DataMapper pattern.

http://phpdatamapper.com

Lior  on January 6th, 2009

Fully agree. I just wrote about something similar - http://lgorithms.blogspot.com/2009/01/5-frameworks-every-software-architect.html

Leave a Comment