How I added real ActiveRecord support to adodb
For my new projects, I have decided to use PHP5, mostly because at least a couple projects will be worked on by a team and the learning curve is fairly forgiving.
I decided, however, to follow the model offered by Rails. I looked into a few PHP frameworks, such as Akelos, but realized that they were too constraining for our purpose. That’s when I decided to develop my own thin layer framework.
An excellent productivity tool is the ActiveRecord pattern. After all, it doesn’t matter what it is, that’s being mapped; whether it’s database tables or web services should not be relevant to the controller.
I have, in the past, used adodb successfully and I was happy when I realized that it comes with a ADOdb_Active_Record class.
Unfortunately, that class does a very basic job of mapping one class to a database table and that’s it. Therefore, support for has_many or belongs_to clauses.
I know that AR offers more features that those but I thought that these fit the very minimum requirement for them to be useful.
And that’s how I ended up modifying ADOdb_Active_Record to support these.
This article is mostly a series of listings that show the modifications-a bit-and how to use them-a bit more. First, here is how I am using them in my framework:
Let’s imagine a database that contains songs, artists and genres. Let’s look at the definition of a Song object:
<?php class Song extends ActiveRecord { function __construct() { parent::__construct('song'); $this->belongsTo('artist'); $this->belongsTo('genre'); } } ?> |
and an Artist object:
<?php class Artist extends ActiveRecord { function __construct() { parent::__construct('artist'); $this->hasMany('songs'); } } ?> |
let’s have a look at a very basic controller that will allow us to retrieve our artists. Their songs will also be retrieved, since artists are defined as having many songs:
<?php class ArtistController extends ApplicationController { function index() { $artist = new Artist(); $this->artists = &$artist->find(ALL, "artists.id=1", array('loading'=>ADODB_LAZY_AR)); } } ?> |
Through the magic of my framework, which is outside the scope of this post, we will then be able to display our artists with their respective songs like so:
<div id="colOne"> <?php foreach($artists as $artist) { print "<h1>{$artist->name}</h1>"; foreach($artist->songs as $song) { print " TITLE:{$song->title}<br />"; } } ?> </div> |
Note that, for this example, I removed as many irrelevant pieces of code as possible. For instance, we are always retrieving artist #1. The array that we are passing to the find() statement allows us to configure some of AR’s behaviours. In this example, we are passing ADODB_LAZY_AR, which is a hint that we do not wish to perform massive joins, but retrieve related objects on demand. If we wished to retrieve everything upfront, we could have used ADODB_JOIN_AR instead.
Now, our model classes are subclasses of ActiveRecord, which is another one of my framework classes, which provides a light abstraction on top of ADOdb_Active_Record. The relevant piece, for you, would be this one since we invoke it:
function find($mode, $condition=null, $extra=array(), $bindArr=false, $pKeyArr=false) { // id? if(ctype_digit($mode)) return $this->load($mode); // return one row if(FIRST == $mode) return parent::load($condition); // Assumption: ALL == $mode - return multiple rows return parent::find($condition, $bindArr, $pKeyArr, $extra); } |
It is interesting because the last line contains the actual call to ADOdb_Active_Record’s find() method.
In adodb itself, two files were modified: adodb-active-record.php and adodb.inc.php.
The former now contains two new structures: _hasMany and _belongsTo. ARs are now aware of the concept of foreign name, which is the name by which this object will be known to another object if a relationship is defined. This allows us, among other things, to naturally inflect tables names.
The latter was modified so that it accepts extra parameters, for instance in GetActiveRecordsClass(), and handle massive joins. Note that joins can be deferred through the use of ADODB_LAZY_AR. In which case I will use the model objects’ __get() method to load missing properties on-demand.
“Buyer Beware”
I hope to publish my full PHP framework in the near future; in the meantime, this hacked version of adodb has been very helpful in my work. I hope you too find it helpful. Beware, though: this is an early work and I would rather offer it to John Lim and let him integrate it in adodb-or can it if he’s too horrified!
I am aware of at least one bug, which may happen when using the join method on belongs_to objects. It could also do more things for you. Definitely not the cat’s meow.
But if you like it and need help, drop me a line.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Similar Posts:
- Where is getElementsByClassName()?
- PHP classes and Javascript: S2ajax says “hi()”
- Adobe Alchemy: Passing a ByteArray from Flex to C++
- Create your own WebTop in php/js in no time
- The confusing Smarty security model
If you enjoyed this post, please consider to leave a comment or subscribe to the feed and get future articles delivered to your feed reader.
Comments
Hi, this is very interesting.
You may wish to take a look at recent (< 6 months) releases of AdoDB, as my code has become part of AdoDB itself.
In fact, I cannot believe that I still haven't written a tutorial for its use, but John Lim has created some good examples that you can read at the phplenses website.
Note that currently supported are "belongsTo()" and "hasMany()" - if you have created more relationships, it may be interesting to integrate them with the existing package.
What do you think?
Hey, I just found your interesting article and see we had the same intention bringing Rails’ active record pattern to PHP with AdoDB. So maybe you are interested in my project, have suggestions or find inspirations:









February 6, 2009 @ 7:42 am
Yes, I already looked at it. Relations to other tables are built through proxies here, so you do not need to decide to use eager loading during class configuration. Also table names are inflected automatically (person vs. people) or automatically taken from the proxied class if it was set there.
My finders support a more rails like syntax which is probably not yet bullet-proof and convenience is preferred over performance - so performance may be pretty low although I will be working on that later.
Most important for my current situation (and demandings) is that my version is PHP4 compatible.