Thinking about the model II

Published on and tagged with cakephp  ideas  model

A while ago I wrote an article Thinking about the model in which I shared some ideas about how CakePHP’s Model class could be improved. In this article I want to share another idea for the Model class.

If you look at the Model class you will see that it represents two different “things”: on the one hand it represents a single record, and on the other hand it represents a table (or to be more generic: a container of records). That’s not that clean from an object-oriented point of view, and hence I think it would make sense to split the Model class into two parts, e.g. Record and Container. All properties and methods related to a single record would be in the Record class, and all table-oriented functionality in the Container class (see also the “Managers” section in the Django book, which describes a very similar concept). In practice it could look like:

$post = $this->Posts->findById(1); // Posts is a Container which returns a Post object (a Record class)
$post->delete(); // delete the post with id 1

Maybe this is something for CakePHP 2.0?

30 comments baked

  • Sam

    Maybe it’s better to separate model and active record that’s actually NOT a model?

  • Dieter_be

    Long time since I used cake so bear with me, but I would suggest you don’t make things needlessly complicated.

    What about for example keeping one object, and using memberfuctions for the activerecord logic, and static functions of the class for the ‘container’ functions?

  • Tim Daldini

    I believe (not sure!) I read somewhere that CakePHP 2.0 would indeed return objects instead of arrays. Not sure if these objects will also have methods for altering data.

    Since you’ll still need to be able to delete a post without actually fetching it, I suppose you will have overlapping functionality between the Record and the Container…which might in turn be confusing.

    Maybe for that reason the Hibernate framework uses clean business objects with no extra methods. Instead it uses a single session and determines what to based on the type of the passed object:
    sess.delete(post);

    So I don’t think the current approach is “wrong”. Imho, anything that works well and is not confusing will do.

  • Abhimanyu

    This is indeed interesting, and I think this will make development even sophisticated when records can be passed as an object anywhere, rather than the array.

  • nate

    Personally I really could not care less whether something seems “clean from an object-oriented point of view” as long as it works well. I’ve worked with frameworks that explicitly separate the model into a main definition class, a record class, and a collection class (which is the “proper” approach), and I can tell you first-hand it’s a royal pain in the ass.

    While I’ve considered the separate-record-object approach, I’d rather implement it in such a way that you could still write all the definition code in a single class.

  • the_woodsman

    I don’t think it’s that confusing – a cake model represents a table, with built in support afor an (array based) active record pattern.

    Once you start seperating the current record as an object (which would then in turn presumably have other objects to represent the associated tables) it all gets vey complex for, imho, very little benefit… it would also be a lot harder to convert result objects into various forms (HTML forms, SQL conditions, etc).

    I don’t think you’ve put much thought into this ;)

  • wangbo

    I agree dho’s words.

    current, cake1.2 model is disordered. include RowDataGateway and TableDataGateway at the same time.

    bad smell.

  • Sam

    Current Cake model implementation forces you to use AR every time even if you don’t need it. For example, I need a model that uses webservice and not a database.

  • Jonathan Snook

    An alternative to your approach which could work in the current cake (assuming it was built for it):

    $post = $this->Post->findById(1);
    $this->Post->delete($post); // delete the post with id 1

    Which could also allow you to do variations like:
    $posts = $this->Post->findAll($conditions);
    $this->Post->delete($post); // delete all posts being passed in.

  • Mark Story

    I’ve always thought that having a dynamic model would be handy. It would function much like cake’s current models in that it would allow generic queries and finds to be performed however if a find a single record is retrieved its delete/save methods can be called without additional parameters. Getting data back from finds as objects is a good start though.

    I’m not a huge fan of models acting as table gateways and requiring you to make ‘row babies’ before you can do things. As there are times that you want to perform actions without having to get a ‘row baby’ first. That is unless it is possible to do automagic queries when the id of a model is set but that is an entirely different discussion.

  • Martin Bavio

    Daniel: great idea, altought I think the best code is when you find a balance between standard code and easy code.

    Jonathan Snook: do you mean deleteAll in your second example?
    $posts = $this->Post->findAll($conditions);
    $this->Post->deleteAll($posts);

  • John M

    I’d love to see something like PHPLinq implemented into the model.

  • Andru

    @Jonathan Snook: I actually just tried this earlier today (first example), and it threw a mysql error when trying to update the counterCache. There is probably a simple solution but passing in the ID fixed it for me.

  • Jonathan Snook

    @Martin Bavio: actually, I’d personally like to see the syntax combine into a narrower approach where it’s just save(), delete() and find().

  • Martin Bavio

    Jonathan Snook: oh, I see, so your proposal is to minimize the ammount of functions… Nice, it would be very KISS.

  • cakebaker

    @all: Thanks for your comments!

    @Sam: That’s a good point. Yes, it could make sense to separate it in that way.

    @Dieter: Yes, this could be a possible solution. But I’m not sure whether it’s possible to implement it consequently in this way due to the magic functions like findAllByXXX.

    @Tim: Ah, you mean POJOs (plain old java objects). Yes, such an approach would also be interesting.

    And regarding returning objects instead of arrays, it is on the roadmap for 2.0.

    @nate: I’m looking forward to the future model implementation, and I hope it will no longer be such a beast as today :)

    @the_woodsman: I think objects instead of arrays are coming anyway in cake 2.0, independent of this article. Some things will probably get more complex with objects, others get easier.

    And what I described here is just a (high-level) idea of how to improve something in the model that I think is a design smell. It is by no means the only or best solution, it’s just me thinking aloud :)

    @Sam: I think you could (theoretically) define a datasource for the webservice and then use AR as usual.

    @Jonathan: Great idea!

    @Mark: Yes, I agree with you that it wouldn’t be cool if you always would have to create “row babies” before you could do anything. To take the example from the article, it would make sense to have a delete() method for the Posts object, too.

  • Tim Daldini

    @cakebaker: Because both the record and container would need overlapping functionality (such as delete), I don’t think it is good to seperate the model in this particular way.

  • cakebaker

    @Tim: I see it rather as complementary functionality. If you take the delete function, then I can delete a single record at the record level. And that’s it. At the table level, the delete function has to be much more powerful, as I want to be able to use some conditions to delete records. So from an abstract point of view those methods are quite different ;-) (sure, if you implement those methods, then they will probably use the same code)

  • Doug D

    daniel:

    It’s funny to read this. I remember finding myself confused when I was first getting started with cakephp because it didn’t already work in this manner. It’s been an ongoing process to derail myself (bad pun intended) from old habits, but frankly some conventions provide by that other framework just make good sense.

    I would be all for this type of syntax in a 2.0 release.

  • Tim Daldini

    @cakebaker: I have to admit that makes sense. But in a lot of occasions (probably most cases of a single record delete!) you will only need (and have!) the id…and it seems weird to me to create an object just to store the id in it so one can call the delete method on it.

  • cakebaker

    @Doug: Yeah, it would be cool to have such a syntax in cake 2.0.

    @Tim: In retrospect I think the example in the article is not that good ;-) I agree with you that it doesn’t make much sense to create an object simply to call delete() on it. In such a situation it would be easier to call delete() at the table level. On the other hand there are also situations when it is more natural to call delete() on a record object. In the end it is up to the developer to choose the “right” method.

  • Kyle Slattery

    It’s too bad PHP doesn’t support singleton functions (or maybe it does?). It would be great to do something like:

    $posts = Post::findAll();

    And have each of the $posts be an instance of Post. That way, the logic is separate, but it’s still all contained within the same class.

  • cakebaker

    @Kyle: That’s possible with PHP (at least with PHP 5) by using the “static” keyword to make the method a class method:

    public static function findAll() {
        // this method is used like Post::findAll()
    }
    
  • RainChen

    @cakebaker:

    I would like to hear some comments about this stuff:
    http://pastie.caboo.se/200932

  • cakebaker

    @RainChen: Looks very interesting and elegant :) A really nice detail is the dynamic countXXX method.

    I’m wondering about two things: is the enableActiveRecord() really necessary? I mean, if you add the ActiceRecord behavior then you probably want to use it, so I would enable it by default.

    The other point is the clone() method. I think it shouldn’t allow parameters, as cloning is about creating an identical object. Or maybe the method should be named differently.

    But all in all it looks very promising!

  • RainChen

    @cakebaker:

    IMO, I will not change the default behaviors that Cake does. Such as Model::find() returning array datas by default and the associations eager loading. So I’m prefer to give the users a choice and make sure they know what they are getting. Another reason, I use the bake command quite often for baking a quick admin interface, and the default baked codes will not working if returning an object.I don’t want to give the users to much “surprises” by the error notices.

    About the clone method, I just designed it for enabling users to add some alike data in a quick way. I’m not sure if this method is necessary.

  • cakebaker

    @RainChen: Yes, you are right, enabling it by default leads to “problems” with baked code, I didn’t consider that…

    I think the clone method is a “nice-to-have” feature, it is not essential but in certain situations it can allow you to write cleaner code.

  • speedmax

    During my exploration trying to get a ActiveResource like base class implemented with better semantics

    * static methods (class methods) used for table (collection) wide operations, new record, find
    * instance method use for row specific operation, delete, update

    Well that requires to get static property and methods inheritance happening, some of the tricks i have included in this project i was contributing, that including fixing the late-static binding for PHP 5.1 and 5.2

    I have build a semi-prototype code demonstration the concept on how to make it possible to do a ActiveRecord-like base class with better static method inheritance in PHP 5.1 +

    Concept + demo code

    http://gist.github.com/88016

    a project i contributing (rubyism in php + magic)

    https://github.com/speedmax/phuby/tree

  • speedmax

    plus the concept code i just posted
    * can be a lot faster with right engineering and optimizations
    * all association are lazily loaded until its really called (without the mess we have lazy load all models, Containable + findAll)
    * ResultCollection can implement some sort of iteration for batched pagination

    can easily expand to other features..

    * single table inheritance
    * class table inheritance if someone is up for challenge

  • cakebaker

    @speedmax: Thanks for your comments!

    Your concept looks interesting. However, I don’t understand why you use the array syntax to create the hobbies in your example. Wouldn’t it be more logical to use something like:

    echo Person::create(array(
        'name' => 'taylor luk';
        'hobbies' => array(
          Hobby::create(array('title' => 'swimming')),
          Hobby::create(array('title' => 'watch tv'))
        )
    ))

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License