ExpressionEngine CMS
Open, Free, Amazing

Thread

This is an archived forum and the content is probably no longer relevant, but is provided here for posterity.

The active forums are here.

ActiveRecord inspired ORM

April 05, 2008 3:50pm

Subscribe [0]
  • #1 / Apr 05, 2008 3:50pm

    m4rw3r

    647 posts

    I’ve finished an ORM model which I would like to have some comments on.
    It is inspired by ruby’s ActiveRecord and it should be very customizable (setting tablenames, foreign key columns, etc.), yet easy to use (assumes some default values if you don’t explicitly supply them).
    It has currently support for Belongs To, Has Many, Has One and Has And Belongs To Many relationships.
    It should be completely PHP 4 compatible (Haven’t got the opportunity to test, can somebody please verify that it woks on PHP 4?)
    When I am confident that everything works as it should, I will integrate MPTtree and this ORM (I will still have an ORM-less variant of MPTtree).

    I currently regard this model as a release candidate, so I have not written any manual or anything, but I think you can figure it out from all the comments in the file.

    Wiki download: ORM_by_m4rw3r_RC1

    Example:

    // model 1
    class thread extends ORM{
        var $__has_many = 'posts'; // you need to define relationships before you call the constructor of ORM
        // it assumes that the model is 'post' (if it exists) and that the foreign key is 'threads_id'
        function page(){
            parent::ORM();
        }
    }
    // model 2
    class post extends ORM{
        // here I specifically define a model and a foreign key for the users table
        var $__belongs_to = array('threads','users' => array('table' => 'users','col' => 'author_id'));
        function post(){
            parent::ORM();
        }
    }
    // controller
    $this->load->model('ORM'); // or include the Model class and orm.php in another way
    $this->load->model('thread'); // no need to load the post model, the ORM class loads it if needed
    $obj = $this->thread->find(1); // find by id
    $obj->load_rel(); // loads all relationships for this record
    $obj->posts[0]->load_related('threads'); // loads all related threads in the first post (array is empty / false if no related are found)
  • #2 / Apr 05, 2008 5:29pm

    nmweb

    206 posts

    Nice contribution to CI. How do you plan to integrate MPTtree? Using acts_as=mptt like behaviours.

  • #3 / Apr 06, 2008 9:35am

    m4rw3r

    647 posts

    That is a good idea, but I don’t know how easy it is to integrate, PHP 4 is missing the __call() method (which I could have used), but I’ll look into it.

    If I on the other hand let MPTtree extend ORM I don’t break the API, I can make two versions of base classes (one extending ORM, the other doesn’t) so you can choose if you need ORM, and you then don’t lock yourself to using ORM all the time (MPTtree is still there).

  • #4 / Apr 06, 2008 12:13pm

    nmweb

    206 posts

    Extending ORM is the other possibility. If you want MPTtree with and without ORM, you need to base classes though which leads to double code. With acts as this might be circumvented.

    __call() is an option for php5, but without it you could do in php4:
    <br /> $menu=Some_Model();<br /> $children=$menu->tree->get_children();<br />
    The tree property would be an instance of your MPTtree class.

  • #5 / Apr 07, 2008 6:27am

    m4rw3r

    647 posts

    I thought of something like this:

    define('MPTtreeORM',true);
    if(MPTtreeORM == true){
        class MPTtree_base extends ORM{
            function MPTtree_base(){
                parent::ORM();
            }
        }
    }
    else{
        class MPTtree_base{
            // some vars
            function MPTtree_base(){
                // some init
            }
        }
    }
    class MPTtree extends MPTtree_base{
        ...

    I can admit that act_as would be nice, I’ll look at it (I would like to get rid of the need of assigning MPTtree to a var in the model (var tree overwrites other == bad), and also an easier way for behaviors to add their methods to the child objects).
    In the mean time, you can submit suggestions for what types of behaviors you want (tree, list, etc.).

  • #6 / Apr 07, 2008 10:01am

    nmweb

    206 posts

    Conditional classes are not very neat I think. I would be reluctant to using it.

    You could look at implementing an observer pattern in your model so you can do

    <br /> $model=new Model;<br /> $model->attach(‘ORM_Tree_Observer’);<br />
    Perhaps this is something. Without __call I feel a bit crippled 😊

    Common behaviours are: tree (adjacency lists), nested_sets(mpttree), list and versioned (to have multiple versions of an article etc.)

  • #7 / Apr 08, 2008 9:14am

    James Pax

    53 posts

    This is really nice m4rw3r :O

    I thought I was happy with a model interface I developed but this is much richer!

    I learnt quite a bit just by looking at the layout, kudos for sharing this! Awesome stuff :D

    Btw one conern… is the price in performance relevant?

  • #8 / Apr 08, 2008 12:32pm

    m4rw3r

    647 posts

    NOTE: The measurements presented in this post were horribly wrong, in a later post I have posted the correct results.

    I think the only thing that slows it down is the loading of data into objects, because it fetches the data from the db and then CI converts it to an array and then the ORM populates an object with that data, the same applies to loading of relations (if anyone has suggestions on how to improve performance, mail me).

    Anyway, here are a quick benchmark:
    Fetching a row and loading one relation and then another on that one, this was repeated 1000 times for both the ORM and for CI’s Active Record db:

    ORM      5.2980
    CI AR    3.5974

    Result: My ORM class is about 47.27 % slower (Rubys ActiveRecord is also about 50% slower).

    Used software:
    OS: Windows XP Media Center
    Server: Apache 2.0.61
    PHP: 5.2.5

    Code used:

    $this->benchmark->mark('ORM_start');
    $this->load->model('ORM');
    $this->load->model('thread');
    $this->load->model('post');
    for($i = 0; $i < 1000; $i++){
        $obj = $this->thread->find(1);
        $obj->load_rel();
        $obj->posts[0]->load_rel();
    }
    $this->benchmark->mark('ORM_end');
    $this->benchmark->mark('CI_db_start');
    for($i = 0; $i < 1000; $i++){
        $q = $this->db->get_where('threads',array('id' => 1),1);
        $d = $q->row_array();
        $q = $this->db->get_where('posts',array('threads_id' => $d['id']));
        $d_p = $q->result_array();
        $q = $this->db->get_where('threads',array('id' => $d_p[0]['threads_id']),1);
        $d_p_t = $q->row_array();
    }
    $this->benchmark->mark('CI_db_end');

    The relevance of the price in performance always depends on what you get for sacrificing it. So if you only need to fetch some data without relations (and the site is used very frequently) I’d probably say no, it’s not worth it. But if it’s a bigger app with a lot of db work and relations, it would simplify a lot.
    But it’s like comparing PHP with C++, PHP makes making web services easy and C++ is a lot faster but more difficult.

    I’m currently checking on how to implement behaviors in the best way, but suggestions are still welcome 😛

  • #9 / Apr 10, 2008 8:25am

    m4rw3r

    647 posts

    Two questions:
    Are 50% slower too slow?
    Are there anyone who want to help me with implementing behaviours, increasing preformance, etc.?

  • #10 / Apr 10, 2008 8:54am

    xwero

    4145 posts

    I think the AR library result could be faster if it is only one query now you have 3 😉

    Why not use a file or files instead of a database table to store your meta data?

  • #11 / Apr 10, 2008 9:00am

    nmweb

    206 posts

    I think the performance is reasonable. You could perhaps look at return standard mysql object or array resultsets to cut on the memory usage. If you select a 1000 records and instantiate ORM for every record, memory usage is quite big and oftentimes you only need the results and not the ORM class. This would be optional of course.

    Using ORM and some other nifty tricks I was able to put together a full CRUD app in something like 1 hour. The views are the most work in the end. That’s where the real performance gain of ORM is.

  • #12 / Apr 10, 2008 10:22am

    m4rw3r

    647 posts

    Ok, I’m terribly sorry, I made an error when I was testing my ORM. I thought I had both models set to has_many (in thread) and belongs_to (in post), but instead I had a has_and_belongs_to_many in the post model :red: .
    So that’s not really a fair competition, a belongs_to relationship vs a has_and_belongs_to_many relationship (one more query and a little more PHP).

    So I remade the test:
    The same code as last time (except the change of the post model):

    ORM        5.0832
    CI AR      4.8359

    Result: My code is about only 5.11 % slower!! 😊 (A great increase in performance, really shows the difference between has_many and has_and_belongs_to_many)

    While I was at it, I made a test of memory consumption:
    The same code, but commented the one I wasn’t testing, then I copied the value from the profiler.

    ORM      2,612,520 bytes
    CI AR    2,223,888 bytes

    Result: My ORM uses about 17.47 % more memory (400 kb more, I think that can be lowered by using more references, but some of that memory are for the compiled classes, I believe)

    BTW, This model works like a factory, where all child classes have a reference to the object who created them, by using that pattern I only have one model for many records and not much in the actual ORM_records (a few methods, the row data, uid and related objects (when they are loaded)).

    So, now I have to work on the behaviours (suggestions, ideas, etc. are welcome).

  • #13 / Apr 19, 2008 7:15am

    m4rw3r

    647 posts

    I have now almost finished the implementation of Act As and the tree behaviour (using MPTtree).

    The acts work like classes that are instantiated as properties of the ORM class, they can add their code on the hooks I’ve added throughout the find/update/save/delete functions, and they can add child class helpers.
    The child class helpers are instantiated for every child object and assigned to a property on the child object, the helpers have a reference to the child to which they are assigned to so they can communicate with the record, ORM class and the act objects.
    To make it easier for PHP 5, I will include a commented __call() function, which aggregates the acts into the ORM class and aggregates the child class helpers into the child class.

    And now I am wondering how to make a good revision behaviour, how should I store the revision information in the db? and how to make it integrate well with other behaviours (that feature can be scrapped if it doesn’t work)?

    @xwero: What metadata? The relations? They are stored in the db, and they are defined in pure PHP, so I don’t really see anything that needs to be saved in files.

    Still sorry about the mistake when I measured the performance, but I’m happy that it didn’t become slower (100% slower or something would have been horrible).

  • #14 / Apr 23, 2008 1:23am

    MMCCQQ

    40 posts

    can you write an example with view , controller and model?

  • #15 / Apr 23, 2008 3:58am

    m4rw3r

    647 posts

    Not for the moment, the RC 2 (or more likely the 0.1) is taking much of my time.
    When I release version 0.1 I hope that I will have a manual ready (it will probably not be covering everything about the ORM class from the start, I will develop it as I develop the ORM), so for the moment you have to read the comments in the file (I know, they lack usage examples).

    If anyone has some more suggestions for this class, please post 😊

.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases