Part of the EllisLab Network
   
1 of 2
1
Active Records
Posted: 25 June 2007 03:39 PM   [ Ignore ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

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)

// 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 :)

 Signature 

I/O Reader

Profile
 
 
Posted: 25 June 2007 08:23 PM   [ Ignore ]   [ # 1 ]  
Lab Technician
Avatar
RankRankRankRank
Total Posts:  1321
Joined  07-27-2006

I’ve found that this sort of pattern works just fine with CI. Say $this->post is a model.

$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.

 Signature 

Check out the Template Library

Profile
 
 
Posted: 25 June 2007 08:29 PM   [ Ignore ]   [ # 2 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

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:

$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.

class CategoriesDefinition extends DatabaseModelDefinition {
    public
function init() {
        $this
->category_id('int')
             ->
length(10)
             ->
primary()
             ->
increment();
        
        
$this->hasMany('forums');
        
$this->hasMany('threads')->through('forums');
    
}
}

 Signature 

I/O Reader

Profile
 
 
Posted: 25 June 2007 08:29 PM   [ Ignore ]   [ # 3 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

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.

 Signature 

I/O Reader

Profile
 
 
Posted: 25 June 2007 10:46 PM   [ Ignore ]   [ # 4 ]  
Lab Technician
Avatar
RankRankRankRank
Total Posts:  1321
Joined  07-27-2006

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

 Signature 

Check out the Template Library

Profile
 
 
Posted: 26 June 2007 02:00 AM   [ Ignore ]   [ # 5 ]  
Summer Student
Total Posts:  23
Joined  02-06-2007

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

Profile
 
 
Posted: 26 June 2007 03:13 AM   [ Ignore ]   [ # 6 ]  
Research Assistant
RankRankRank
Total Posts:  551
Joined  06-17-2006

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

$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.

 Signature 

CodeCrafter - Open Source Code Generation for CI

Profile
 
 
Posted: 26 June 2007 03:36 AM   [ Ignore ]   [ # 7 ]  
Research Assistant
RankRankRank
Total Posts:  359
Joined  10-02-2006

From CI forum member number 1:

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://codeigniter.com/forums/viewthread/53619/P15/

 Signature 

CI version?
From SVN.

Profile
 
 
Posted: 26 June 2007 08:42 AM   [ Ignore ]   [ # 8 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

Thanks for the replies:

http://ioreader.com/2007/06/26/active-records-in-the-codeigniter-framework/

ZIP download link at the bottom.

 Signature 

I/O Reader

Profile
 
 
Posted: 26 June 2007 10:39 AM   [ Ignore ]   [ # 9 ]  
Lab Assistant
Avatar
RankRank
Total Posts:  165
Joined  08-22-2006

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

 Signature 

Don’t argue with an idiot, people watching may not be able to tell the difference.


marcoss
http://defmay.com
http://fenix.st

Profile
 
 
Posted: 26 June 2007 05:26 PM   [ Ignore ]   [ # 10 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

I’ve found a few small bugs in my code and I will update the ZIP later tonight after work. Otherwise, I wanted to introduce one nifty thing: We got to the point in work today where we needed to have table relations act slightly differently: normally the relation will select all columns (eg: SELECT * FROM ..) from the related tables but we wanted it to only select a few. I came up with a pretty sexy solution imo…

Consider that this is how a category would gets its forums through a relation:

$category->forums;


Well, lets say we only want the forum ids and forum names, then we can instead do this:

$category->forums('forum_id, name');


The relation will work in the same way but what it selects will be changed smile

 Signature 

I/O Reader

Profile
 
 
Posted: 26 June 2007 06:12 PM   [ Ignore ]   [ # 11 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

Later tonight after work I will try to add the ability to cascade delete things, e.g.: alongside hasOne/hasMany have ownsOne/ownsMany so that if you delete a row and that row owns other rows, those rows will be deleted. This will happen recursively so that all things are deleted (cascade).

 Signature 

I/O Reader

Profile
 
 
Posted: 27 June 2007 06:09 AM   [ Ignore ]   [ # 12 ]  
Grad Student
Rank
Total Posts:  85
Joined  04-01-2006

I agree with the $this->modelname being annoying. I’d much prefer it if you just did $bob = &$this->load(‘class’); in every place where you want to call that class. That’s how I did it with my framework and it makes things a lot more ogranised, and it then means PHPEdit’s code insight works.

Profile
 
 
Posted: 27 June 2007 09:39 AM   [ Ignore ]   [ # 13 ]  
Research Assistant
Avatar
RankRankRank
Total Posts:  492
Joined  02-21-2007
Peter Goodman - 25 June 2007 08:29 PM

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.

[hijacking the thread]
Just in need of feedback: please have a look at my solution to this.
[/hijacking]

Profile
 
 
Posted: 03 July 2007 04:54 PM   [ Ignore ]   [ # 14 ]  
Grad Student
Rank
Total Posts:  48
Joined  06-25-2007

Hey,

I just wanted to update everyone on the status of this library. I have just created extensive documentation that can be found at:

http://ioreader.com/code/ORM Documentation/index.php

Tell me what you think.

 Signature 

I/O Reader

Profile
 
 
Posted: 10 January 2008 01:38 PM   [ Ignore ]   [ # 15 ]  
Summer Student
Total Posts:  1
Joined  04-17-2007

I’m just starting to fiddle with this and it seems great.
While pushing the envelope to see how many ->through() models I could line up together, this error popped up:

A PHP Error was encountered
Severity
: Notice
Message
: Undefined variable: q
Filename
: database/relation.php
Line Number
: 167

I went to relation.php and the line 167 is:

$this->sql = $q;

I couldn’t find the $q variable being created anywhere… Is that right?
Commenting the line gets rid of the error without any noticeable side effects. At least I think so :)

Profile
 
 
   
1 of 2
1
 
Post Marker Legend
New Topic New posts Hot Topic Hot Topic with new posts New Poll New Poll Moved Topic Moved Topic Sticky Topic Sticky topic
Old Topic No new posts Hot Old Topic Hot Topic with no new posts Old Poll Old Poll Closed Topic Closed Topic Announcement Announcements
Theme
Change Theme
Visitor Statistics
The most visitors ever was 719, on June 06, 2008 10:16 AM
Total Registered Members: 64453 Total Logged-in Users: 17
Total Topics: 80961 Total Anonymous Users: 0
Total Replies: 435694 Total Guests: 203
Total Posts: 516655    
Members ( View Memberlist )