Quick Doctrine ORM Tip: Hydration Using Doctrine_Table

Did you know you can have what Propel calls "Peers" in Doctrine ORM? A peer is basically an object which is responsible for hydrating (populating) a model. From the documentation, you might think this can only be done using DQL, but you can actually use Doctrine_Table to do the work for you.

The first thing you must do is define the model you want to be hydratable. I'll use "Article" as my model. This step is important because it gives Doctrine an object to hydrate when we perform operations against our Doctrine_Table object. It also, of course, allows Doctrine to do alias translation and automatically detect relationships (I.E., so you don't have to manually perform joins.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Article extends Doctrine_Record
{
 
	public function setTableDefinition()
	{
 
		$this->hasColumn( 'title', 'string', 255,
			array( 'notblank' => true )
		);
 
		$this->hasColumn( 'author', 'string', 255,
			array( 'notblank' => true )
		);
 
		$this->hasColumn( 'body', 'clob' );
 
		$this->hasColumn( 'published', 'boolean', null,
			array( 'default' => false )
		);
	}
 
	public function setUp()
	{
		$this->hasMany( 'Comment', array(
			'local' => 'id',
            		'foreign' => 'article_id',
		);
	}
}

*It's good practice to make all of your modelsTimestampable, but I left that out of this example for simplicity

Now that our models have been defined, lets move on to our Peer, theDoctrine_Table. In it's simplest form, our Peer only needs to extend Doctrine_Table. In order to harness the power of Doctrine out of the box, we need to make sure the 3rd parameter of the Doctrine_Table constructor is set to "true". This allows Doctrine to bind our model (defined above) to the Table that this Doctrine_Table instance is representing. Without it, we would have to manually bind the model.

TIP: Instead of doing this for every Peer, create a base class which does it for you. I haven't run into a case yet where I didn't want to let Doctrine do it's work for me. As long as the models exist, it should work fine.

1
2
3
4
5
6
7
8
class ArticlePeer extends Doctrine_Table
{
 
	public function __construct( Doctrine_Connection $oDbCon )
	{
		parent::__construct(  'Article', $oDbCon, true );
	}
}

Now that our peer is defined, we can save ourselves the trouble of writing any DQL. Doctrine_Table makes available some magic functions, allowing you to do simple queries against any field defined in the model you're peering. For example, we can easily retrieve a collection of Articles by "A.J. Brown":

1
2
$peer = new ArticlePeer( $connection );
$articles = $peer->findByAuthor( 'A.J. Brown' );

or all published articles:

1
2
$peer = new ArticlePeer( $connection );
$articles = $peer->findByPublished( true );

The advantage of this approach might not be obvious at first. After all, you could just as easily instanciate a new Doctrine_Table, passing the correct parameters, right?

Most applications won't use the simple findBy methods made availble through the Doctrine_Table API. We'll typically need to do lookups that will require custom DQL to return the objects we need. It's a good practice to make these queries reusable, and place them at a lower level in your code. In fact, If you follow the "Fat Model, Skinny Controller" philosophy, you would never be writing DQL at any level of your application outside of the model / data access layer.

With that said, any query that hydrates Article models (or agregate data of Article models, such as counts and averages) should be performed within a method of our ArticlePeer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
< ?php
class ArticlePeer extends Doctrine_Table
{
 
	public function __construct( Doctrine_Connection $oDbCon )
	{
		parent::__construct(  'Article', $oDbCon, true );
	}
 
	public function findArticlesForReview()
	{
		return $this->createQuery()
			->where( 'Article.pendingReview = true' )
			->addWhere( "Article.status != 'draft' ")
			->orderBy( "Article.title" )
			->execute()
			;  
	}
 
}

Now our user code doesn't have to know the query needed for the data, it only needs to know what the method returns. If your definition of an article pending review changes later, you won't need to grep through all of your code for the related queries.

TIP: An added benefit of placing your DQL queries inside Doctrine_Table objects is caching. The ArticlePeer above makes a great place to implement Memcached caching, since it would be completely transparent to the user.

Happy Doctrine...ing?

For more basic information on using Doctrine, check the Doctrine ORM Online Manual.

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Printed from: http://ajbrown.org/blog/2008/12/31/hydration-using-doctrine-table.html .
© © A.J. Brown 2012.