debuggable

 
Contact Us
 

Bringing the cold war to CakePHP 1.2 - The Containable Behavior

Posted on 13/5/07 by Felix Geisendörfer

Deprecated post

The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.

Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.

Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).

Update: JCSiegrist just noticed a problem in the code as I've released it. Somehow I must have deleted an important line of code right before uploading, sry about this. Please get the new version of the code.

Hi folks,

for those of you who feel like it's becoming a regular thing that I'm starting my posts with excuses for not having posted stuff in a while there is good news: I'm one final Math / History exam away from being a free man and done with school. That means I'll start heavy-duty blogging again, publishing all the good and great stuff I've been working on and learning in the past months.

For those of you who can't await this, here comes a little teaser in form of a behavior for Cake 1.2 called 'Containable'. Essentially it is yet another way to unbind associations from a model recursively on the fly. However, I think it's more powerful then all the ones released so far for several reasons:

  • It's a behavior! No manual AppModel hacking here ; ).
  • It is smart: It will recognize when you pass in conflicting associations and automatically correct that.
  • It's for the lazy: Always wondering what $recursive parameter to use? No More! Containable sets it for you.
  • It's convenient: Like to use dot-seperated Model strings as parameters? Arrays of Models? Or both at the same time? No problem!

Alright, enough marketing. Here is the code and how to install it:

  1. Put the code in /app/models/behaviors/containable.php
  2. Create an /app/app_model.php file like this:
class AppModel extends Model {
  var $actsAs = array('Containable');
}

Done! Now that was easy. But how to use this new behavior? Well, how would you like to use it? See if one of those alternatives cuts it for you:

$this->User->contain(array(
    'UserType',
    'Group' => array(
      'GroupPermission'
    ),
    'Post' => array(
      'Category',
      'Comment' => 'User',     
    )
  )
);

Uhm, that was verbose, how about:

$this->User->contain('UserType', 'Group.GroupPermission', 'Post.Category', 'Post.RelatedPost', 'Post.Comment.User');

Or a slightly longer, but more DRY alternative:

$this->User->contain('UserType', 'Group.GroupPermission', array(
  'Post' => array('Category', 'RelatedPost', 'Comment.User')
));

All of the snippets above will cause the exactly the same associations to be present in your next Model::find* query. They are just expressed in a different notation. The function does not care if you use several parameters, strings with dot notation or nested arrays. Define your own containment policies in whatever syntax you like the best! Or mix them : )!

I hope this will become a powerful weapon in your very own war on terrorizing associations you don't want in your resultsets and will make your present doomsday devices obsolete soon ; ).

Oh and before I forget, it might also be worth to read through the code a little. I've experimented with two cool things in there: Namely using static variables as a 'memory' in recursive functions as well as recognizing the root level of the recursive calls by checking if a certain parameter is a boolean : ).

-- Felix Geisendörfer aka the_undefined

PS: Let me know if you find any bugs so I can fix them ASAP.

 
&nsbp;

You can skip to the end and add a comment.

CraZyLeGs said on May 13, 2007:

http://othy.wordpress.com/2006/06/03/unbind-all-associations-except-some/ as the url says is from June 3rd, 2006 ;) nearly a year old, there were no behaviors at that time.

Anyway your stuff is interesting, can be extended to add fields and conditions.
that is, I want association x to stay but I only want fieldx1 andfieldx2 under xconditions.

Felix Geisendörfer said on May 13, 2007:

CraZyLeGs: Hey I was in no way trying to be critical against the existing implementations. If it wasn't for them and me studying them this post wouldn't have been made. And behaviors also do not exist in Cake 1.1 so the alternatives deserve their place. That "doomsday device" reference was just thrown in for the sake of the "cold war" theme on this post and hopefully will not be taken serious : ).

I'm not sure about limiting fields / adding conditions that would go beyond what the current implementation is designed for. But if you'd share what you mean with that I'll see what I can do : ).

nao  said on May 13, 2007:

I maked something very similar but I allow to not unbind child of one association with suffix '*'. It allow repeat association unbinding for self jointed association with suffix '@'. Finally, it'allow too select fields with syntax : "assoc:field1,field2".

$this->User->unbindAll(array(
'UserType:name,password',

'Group:name' => array(

'GroupPermission:foo*'

),

'Post' => array(

'Category',

'Comment' => 'User',

),

'Category:name,password' => array(

'Category:name,password@',

)

)

);

Felix Geisendörfer said on May 13, 2007:

Oh ok I think I know what both of you are talking about now. Yeah I might add that, but probably with a different syntax then the above.

nao  said on May 13, 2007:

I forget, I added "modifier" too. "Category:name,password|objectify|foo" : it look for "objectify" and "foo" method in each behaviour.

My code is dirty, it should be better to use your behaviour and implement my ideas to it. What to you thing about?

My code his here : http://bin.cakephp.org/view/271561981

Add this functions to your app model.

CraZyLeGs said on May 13, 2007:

felix: oh didn't mean you criticize or anything, I actually wrote ubindAll and yea a lot of work I see is based on it which is cool. I'm just thinking of getting the real weapon that will get the baker's doomsday kitchen device obsolete ;)

nao: Very interesting. perhaps a merge with felix's syntax.

Felix Geisendörfer said on May 13, 2007:

Nao: Interesting stuff indeed. However there is definitely too much going on there I think and I'm not sure if it should maybe split up in several behaviors. Anyway, I'll take a look at it again in the next days and see what I can come up with : ). Great to see your approach anyway!

Lucian Lature  said on May 14, 2007:

It's good to have you back...Waiting forward for more cake posts.

Felix said on May 14, 2007:

That's awesome!
I'm looking for a jQuery helper for Cake, are you or some guys out there working on it?

Felix Geisendörfer said on May 14, 2007:

No. I don't think a helper could ever meet my requirements, I prefer writing all my JS code by hand.

Nate Klaiber said on May 14, 2007:

That is a very handy piece of code! Nice to see you didn't fall off of the face of the earth, and I look forward to some more posts.

Hope you finish up strong with school.....

Felix Geisendörfer said on May 14, 2007:

Hey nate: Most posts will come. My long overdue web app will launch. Good stuff for the cake core will be released and some other fun things with this site and in general are planned as well : ).

About school: Just took my Math final today and boy was it hard. My problem is that I'm a platonic idealist when it comes to most things, so I cannot accept difficult solutions (involving lots of writing for things that ought to be simple). However this really gets into my way when taking a difficult test because sometimes you simply don't have the time to find the abstract solution to the problem and in reality they want you to go through pages of derivations, semi-thought-through conclusions and whatever else falls into the realm of cargo-cult science : /. So let's see what comes out of this one.

[...] Felix Geisendorfer is sharing a bit of his CakePHP knowledge with us today with this new post to his blog. It shines a spotlight on a feature of the framework - a behavior called “Containable”. For those of you who can’t await this, here comes a little teaser in form of a behavior for Cake 1.2 called ‘Containable’. Essentially it is yet another way to unbind associations from a model recursively on the fly. However, I think it’s more powerful then all the ones released so far. [...]

Brandon  said on May 15, 2007:

Bravo!
You finally come out of hiding :) and deliver something that is worth the month we all waited!! IMO, this is a great behavior!! I have always toiled with this issue and found it unbearable at times to have to manually set the recursion of associative models (or forget to and wonder why my pages take so long to load:)).

Thanks for this great code!!

JCSiegrist  said on May 15, 2007:

This looks fantastic! But I can't get it to work. For me it seems to always do an unbindAll. Going through the code it seems that $containments[$model->name] never gets set. But I'm probably overlooking something obvious. Currently using Cake 1.2r4925 on php5.

:jcs

Felix Geisendörfer said on May 15, 2007:

JCSiegrist: Good call! I must have messed up the version I uploaded right before I did so as it is indeed lacking this critical line of code. Please get the new version, it's fixed : ).

JCSiegrist  said on May 15, 2007:

Great, works wonderfully now! Thank You.

JCSiegrist  said on May 15, 2007:

Oh,oh. I found another issue at least with my app. As soon as I have another behavior on a model and call contain I get an sql error. Somewhere a db query gets executed with the string 'contain'. As soon as I remove the other behavior it works. I've tried various other behaviors, so I don't think it is any specific behavior.

I'll look into the containable code to see if I can find anything. Have you tested with other model specific behaviors?

JCSiegrist  said on May 15, 2007:

Ok, sorry. I found the problem. Of course I overwrote $actsAs in my Model, so that the ContainableBehavior was not available from the AppModel. So CakePHP ran the call to the contain method of my model as an overloadable callback. CakePHP just send it right to the db.

The solution is to also add the 'Containable' to the actsAs array in the specific Model, if other behaviors are used.

Felix Geisendörfer said on May 15, 2007:

I'm working on another (top secret) behavior right now which will make usage of Containable and had no problems with it so far. However I have not tested with any other behaviors yet and would appreciate your input on what may be causing your particular problem.

JCSiegrist  said on May 15, 2007:

Setting $actAs in MyModel overwrote $actsAs in AppModel, where the ContainableBehavior was set.

Solution for me was to just also include 'Containable' in the $actsAs array of MyModel.

CakePHP doesn't seem to merge the arrays like it seems to be doing with $uses, $helpers etc.

Have to check how that works in CakePHP and maybe file an enhancement ticket.

poLK said on May 21, 2007:

Well done, Felix :)
Just little note for PHP compatibility: http://bin.cakephp.org/view/725024741

Thank you for sharing this code!

Felix Geisendörfer said on May 21, 2007:

poLK: "PHP compatibility"? What do you mean by that?

JCSiegrist  said on May 21, 2007:

PHP4 does not support references in the foreach iterator(?).

I have also found that for some reason the newest cake console bake script doesn't like foreach loops using references, a php error is thrown in the console on each script that uses them.

Thank You Felix for writing this great Behavior and sharing it!

:jc

ThinkingPHP and beyond » said on May 27, 2007:

[...] I’m currently working hard on refactoring my Containable behavior to provide you with lot’s of fun stuff like fields, conditions etc. limitations for associations on the fly. I also do everything using test driven development which turns out to be *very* hard in this case and is the reason why the new version isn’t released even so I probably already spent ~10 hours on it. Please excuse the delay and stay tuned. [...]

nao  said on May 29, 2007:

That good!

I wonder that it syntax you will use to implement all this new functionnality.

I am impatient to see your new version, I hope soon!

headbiznatch  said on May 30, 2007:

contain is wonderful. very intuitive and especially great for Flash remoting... thank you for your contributions, and all the best regarding your exams. rock on.

Felix Geisendörfer said on May 31, 2007:

poLK / JCSiegrist: Arg, just realized I did my testing on PHP5 only so yeah you are right, this is a problem. Any way the new version is a complete rewrite and won't use this code : ).

Abbas Ali  said on Jun 09, 2007:

A temporary solution for php4 incompatibility might be this...

Replace

foreach ($this-&#gt;runtime[$model-&#gt;name] as $key =&#gt; &$containedModel) {
            if (!empty($containedModel-&#gt;__backAssociation)) {

                $containedModel-&#gt;__resetAssociations();

            }

        }

with

foreach ($this-&#gt;runtime[$model-&#gt;name] as $key =&#gt; $containedModel) {
            if (!empty($this-&#gt;runtime[$model-&#gt;name][$key]-&#gt;__backAssociation)) {

                $this-&#gt;runtime[$model-&#gt;name][$key]-&#gt;__resetAssociations();

            }

        }
Joshua Benner said on Jun 12, 2007:

I've been testing containable to see if it will work with my project. It's really a great tool. I'm getting an unexpected (to me) result, however:

If I did a normal recursive=3 find* on model Placement, the result would be like this:

Placement
PlacementType

School

Student

--Person

--Placement

----PlacementType

----School

----Student

This is what I want:

Placement
PlacementType

Student

--Person

--Placement

----PlacementType

This is the contain call I've tried:

$this->Placement->contain(array('PlacementType', 'Student.Person','Student.Placement.PlacementType'));

and I've tried:

$this->Placement->contain(array('PlacementType', 'Student' => array('Person', 'Placement' => array('PlacementType'))));

But each time, I get this:

Placement
PlacementType

Student

--Person

--Placement

----PlacementType

----Student

Does it fetch the Student association from Placement again since I wanted it once? Is this expected behavior, bug, or limitation (of code, or of my understanding! :)

Thanks!

Felix Geisendörfer said on Jun 12, 2007:

Joshua: This is expected behavior. If you do a findAll that retrieves a certain association two or more times then you'll not be able to fine-grain it's sub-associations other then having them all the same. My behavior helps you as good as it can with this by making sure it doesn't accidentally remove Student from Placement when you specify "Student.Placement.PlacementType" but it won't be able to more then this.

This is a limitation that arises from the current Model architecture that defines associations on a per model and not a per situation/query basis. As it's usually not a major problem I hope that'll be good enough for you are trying to accomplish, otherwise you'll have to either do 2 finds or manual querying.

-- Felix

nao  said on Jun 13, 2007:

Some news for your new version ? I am impatient to see how you implement my ideas of mine.

[...] sorry I've taken so long to get a new version of Containable Behavior released, but believe me I've not been slacking this time. Rather I noticed that my initial plan of refactoring, adding a couple features and unit testing the behavior (especially the later) turned out to be much more ambitious than I thought it would be. In fact I'm releasing the new version as a BETA right now since I'm still not 100% satisfied with the result and not all features have made it in yet, but I felt the need for iterating. However, the new version should be a big step up from this initial one and I hopefully bug free. [...]

sourtouch  said on Aug 16, 2007:

Hi Felix thanks for the very useful behavior. I am finding that I get a lot of memory errors when using this behavior though and I can't tell whats causing it. All I know is that when it sometimes happens that if I 'contain' a model to only include certain other models I get a memory exhausted error. The only way to make the error go away is to comment out the containing. Any ideas what might be causing this?

Thanks,
S

Felix Geisendörfer said on Aug 16, 2007:

sourtouch: Are you using the 2.0 version? I use this behavior on a huge project with 300db tables and virtually several hundred calls to it. See if you can investigate further on this as I have no idea what could be causing it.

sourtouch  said on Aug 17, 2007:

Hi Felix, sorry I posted this in the wrong thread as it is version 2.0 Betathat I'm using.

I think the problem lies with the $containments variable that is returned from the contain method. It seems to contain a really huge about amount of unnecessary data.

Try adding debug($containments);exit; at line 72 right before the contain method returns the $containments variable.

If you like I can email you a copy of what I get when I do that.

Best,
S :)

Felix Geisendörfer said on Aug 17, 2007:

sourtouch: Are you using PHP4? The $containments should merely be made up of references to models, none of that should take up any significant memory.

sourtouch  said on Aug 17, 2007:

No I'm using PHP5. $containments does seem to be made of information on models and tables but when i dump it into the browser it spits out over 40,000 lines of debug information which is as far as it gets before the memory error occurred.

Felix Geisendörfer said on Aug 17, 2007:

sourtouch: Yes, that is normal because the Models reference each other recursively indefinitely. Do you get the memory errors only from doing the debug() or during normal usage of the behavior? Please go ahead and paste me some code here so I get an idea about what you are doing: http://bin.cakephp.org/add/sourtouch

ian  said on Sep 17, 2007:

Hi Felix,

This is a good behavior but as with sourtouch, I get memory errors during normal usage, but only when trying to restrict models in a way equivalent to calling recursive = 2 and using the built-in pagination component. If I do a findAll instead of a paginate call, the behavior works perfectly.

For what its worth, if you comment out line 1411 in model.php (__resetAssociations) - $this->{$type} = $this->__backAssociation[$type]; then the paginate call also works ok.

More here: http://bin.cakephp.org/add/ian

Cheers, ian

[...] Last month I read - more like scanned - over a post on Felix Geisendörfer’s (the_undefined) blog all about a new behaviour he had just written for CakePHP. At the time I didn’t really get it and so didn’t see its value. But today while progressing with my Cake rewrite of Switchboard, I had a need to limit the associations that Cake was returning. The most obvious way was to play with the $recursive variable in my models, but that just isn’t fine grained enough. So I hopped on over to the Cake IRC channel and asked how others were doing it. Luckily for me, Felix had also hopped on over and suggested I take a look at his containable behaviour. He had just released version 2.0, so I grabbed the code and started playing. [...]

SwarnePepwale  said on Dec 20, 2008:

Thak you for the news

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.