Welcome Guest, Not a member yet? Register   Sign In
Active Records
#1

[eluser]Peter Goodman[/eluser]
So, I'm a PHP developer and I've been working on my own PHP5 framework. For work though, I work with the CodeIgniter framework. I've found that the current "active records" system is inadequate. So, I've essentially translated code from my framework to work with CI. I'm about to start testing it out, but if anyone is interested, I will be releasing it (if I get it to work) as an extension/library/thingy.

Here's how it works (from within a controller)
Code:
// simple updating
$post = $this->getFinder('model_name')->find(1); // find something by its primary key
$post->name = 'hello';
$post->body = 'mooo';
$post->save();

// simple inserting
$post = $this->getFinder('model_name')->createRecord();
...
$post->save();

// given a table relation specified by hasMany or hasOne in a model definition, other
// table rows can be accessed as:
$post->replies;
// where 'replies' is an automatically added table.

This only gives a small idea of the neat features I supply. If anyone is interested, please say so Smile
#2

[eluser]Colin Williams[/eluser]
I've found that this sort of pattern works just fine with CI. Say $this->post is a model.
Code:
$post = $this->post->get(1);
$post->title = 'Hello';
$post->body = 'Word up, G!';
$this->post->save($post);

The syntax is nearly identical, except that this requires you write your own 'post' model. Which, really, is not a big deal. I don't know if I would personally like to have an ORM system like yours assume things about my data, unless it just does an awesome job of it.
#3

[eluser]Peter Goodman[/eluser]
So, it works! I wanted to explain some improvements over CI's current active record system. First of all is how the modeling system works. This system supports anything that can be classified into a model, and the database is the obvious default one.

There are several things involved: Finders, Models, ModelDefinitions, Records, and RecordSets.

- Models are the high level things, eg: the DatabaseModel.
- Finders go and find things, in the case of the database, they will find row(s).
- Records and RecordSets are returned by the finder functions. A record set is simply an iterator that returns a Record on each iteration.


Why use this active records system instead of code igniters?
First of all, code igniter's current system is lacking. First, a CI_DB_active_record instance class is passed around and used to construct queries. This seems good, except for the fact that if you are building more than one query at the same time, you will end up having merged queries. Second, $this->load->model(). This function sets the model name to an instance variable--eg:$this->model_name--which is available everywhere. Again, this seems like a convenience but it's not. Consider having to write $this->model_name-> over and over again instead of $model_name->. It's a small difference on a small scale, but in an enterprise application it make a huge difference to programmer patience and readability. Third, again with the load->model(). Because it sets it to an instance variable, you end up losing track of which resources you have loaded. If the function were an explicit return, then the models that they return would enter and leave scope in a predicable way, and by having to assign variables to them, when refactoring it would be obvious that you have a defined variable that isn't in use. (this last point is really just nitpicking).

Well, whatever for criticisms. That won't get anyone anywhere and by and large code igniter is a solid product. So, here's the functionality I have added:

First, a new controller: ActiveController. This controller has two new methods: getFinder and setDatasource. The default datasource/model is 'database', but if you make any others, they are easy to work in. getFinder will look for /application/libraries/ActiveRecord/models/<datasource>/model.php. That is the main file for any model. The reason for the new controller is that you can use this extension and *not* break any existing code because the CI_Loader class is not extended.

To use finders, you look for a 'ModelDefinition'. In the case of databases, model definitions serve to describe database tables. They define everything: the name, primary key, all the columns, the foreign keys, and the relations. There is another special feature of model definitions and that is their prepareSelect function. This function allows the programmer to add things to a query object that is used every time a finder function is called to fetch row(s).

The model definitions supply 3 functions for dealing with table relations: hasOne, hasMany, and hasForeignKey. Within a model definition's init() function, the programmer can do things such as the following:
Code:
$this->hasOne(model name [, alias]);
$this->hasMany(model name [, alias]);
$this->hasMany(model name [, alias])[->through(model name)]
$this->hasForeignKey(this table column, <other model name>.<other column name>);
Note: ->through() can be chained as many times as you want.

So, some examples... Imagine a forum: categories, forums, threads.
Code:
class CategoriesDefinition extends DatabaseModelDefinition {
    public function init() {
        $this->category_id('int')
             ->length(10)
             ->primary()
             ->increment();
        
        $this->hasMany('forums');
        $this->hasMany('threads')->through('forums');
    }
}
#4

[eluser]Peter Goodman[/eluser]
That's the jist of what a model definition would look like. On to the next thing: Records. Database tables can have their own record classes but don't explicitly need them. The records are reminiscent of CI's query result objects, however with added functionality. Record classes have two main functions: save() and delete(). These functions should be obvious.

The Record class is also overloaded. The overloaded __get() function has access to several things: dirty values (data that doesn't exist in the db), saved data (data that exists in the db), cached data (keep reading), getter functions--eg: $row->moo is the same as $row->getMoo()--(these results are cached), and table relations!

If you remember back to hasMany(model_name [, alias]) then with the record you can do either $row->model_name, or if you specified an alias, you can do $row->alias. If you used hasOne, then that will return a new Record object, hasMany will return a record set.

RecordSets are caching iterators that loop over database result sets from CI. Unfortunately, CI goes and fetches an array with their query functions--essentially negating the use of iterators--but since this is a port from my own PHP5 framework, I used them. Also, maybe in the future the CI devs will implement iterators (yes, they can be done in php4 too.. ask me if you don't know how or do a search on my blog http://ioreader.com/).

Back to the Finder / DatabaseFinder class. It has a bunch of finding functions, some similar to CI's. For example: find, findAll, findWhere, findAllWhere, etc. Finder classes are allowed to be extended for custom table finders. The advantage of finders over CI's current implementation are numerous: first of all, they use a custom query builder class so there's no worry about query merging. Second, they work with the tables instead of against them: since finders are returned by ActiveController::getFinder(), the finder knows everything about a database table: name, keys, etc, and uses this information to make getting rows from the databse easier. Finally, the custom query object works very well with ModelDefinition::prepareSelect to allow the programmer to insert special sql into every query performed by the Finder functions.

Other things that are useful about this system is an extra layer of validation. Because the model definition essentially describes a database table, it means that all data used in records can be typecasted and checked for length. They can also have special regular expressions applied to them for advanced, centralized data checking.

I'll be posting a .zip up on my blog (http://ioreader.com) within the next few days if anyone is interested in getting it. Again, this is PHP5 only.
#5

[eluser]Colin Williams[/eluser]
Nice, thorough (perhaps winded) rundown.

Maybe what I don't like about it is that it doesn't match the syntax and style of CodeIgniter (which in your opinion is actually a good thing). It feels foreign. I'm sure it works wonderfully, and I'm sure many will love it. I think I'll be sticking with my pseudo-ORM for now. Smile
#6

[eluser]Stewart Matheson[/eluser]
hey Peter,

I have just started working on something very similar to what your talking about. It sounds like your a bit ahead of what I am at this stage. If you release it to your blog i would like to have a look.

-Stewart
#7

[eluser]Crafter[/eluser]
I've been working on using the MyAcriverecord library. It is a full Rails-like implementation of AR and so comes close to Fowlers original AR design.

The advantage of the MyAcriverecord implementation is that is is usable on PHP4 as well, but creates object persistence, so you can say something like
Code:
$article = & MyActiveRecord::CreateObject('article', 'id');
$article_details = $article->FindFirst('', $filters );
if ($article_details != false) {
   $article_id = $article_details->id;
}

I've been using and adapting it for my project. There are still some issues to be ironed out and it's not ready for public consumption yet.

I'd be intrested in looking at your implementation.
#8

[eluser]Crimp[/eluser]
From CI forum member number 1:

Quote:Here’s a concreted example of something we’re hoping to achieve for EE 2.0 that will cary over to CI. Currently, EE runs only on MySQL. We’d love, however, for EE to support other DB platforms. To do this, though, will require that the 2000+ database queries that exist in EE be ported over to a more abstracted mechanism. In essence, we need to rip the MySQL queries out of EE and replace them with an active record pattern.

CI, however, already supports multiple databases via a much nicer DB library then EE and it has an active record system, it’s just not powerful enough in its current form to meet our goals. But it’s a great starting point for our development, so as we move EE to this new database structure and rewrite the engine, it’s going to result in CI automatically getting much more kick-ass database classes.

http://ellislab.com/forums/viewthread/53619/P15/
#9

[eluser]Peter Goodman[/eluser]
Thanks for the replies:

http://ioreader.com/2007/06/26/active-re...framework/

ZIP download link at the bottom.
#10

[eluser]marcoss[/eluser]
Looks very interesting, I've been using a similar approach (by using an alternative controller) based on ADOdb implementation of AR .

Will give it a spin Wink




Theme © iAndrew 2016 - Forum software by © MyBB