Example of CakePHP's Containable for deep model bindings

Here’s an example of using the Containble behavior when you’ve got deep and complex model relationships.

Let’s consider the following model associations…

User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag

It’s very possible that each of those models has other associations, so we need to use Containable to retrieve just the models we need (we’ll also specify fields and conditions for some models just to make it more “fun”). Remember, that Containable behavior has to be attached to all of the models in our relationship, therefore to make your life easier you should probably attach the behavior to all models in App Model…

Our find() call:

$this->User->find('all', array(
   'contain'=>array(
      'Profile',
      'Account'=>array('AccountSummary'),
      'Post'=>array(
         'PostAttachment'=>array(
            'fields'=>array('id','name'),
               'PostAttachmentHistory'=>array(
                  'HistoryNotes'=>array(
                     'fields'=>array('id', 'note')
                   )
               )
         ),
         'Tag'=>array('conditions'=>array('Tag.name LIKE'=>'%happy%'))
       )
    )
));

Crazy, huh?

Some things to keep in mind:

  • ‘contain’ key is only used once in the main model, you don’t use ‘contain’ again for related models
  • Deep binding is done by using the ‘contain’=>array(‘ModelA’=>array(‘ModelB’=>array(‘ModelC’…
  • Each model can have it’s own set of find() options, by using ‘contain’=>array(‘ModelA’=>array(‘fields’=>…, ‘conditions’=> …, ‘ModelB’=>array(‘fields’=>…etc.
  • As rafaelbandeira3 pointed out, ‘fields’ and ‘conditions’ are the only keys that will be of use. ‘limit’, ‘recursive’, ‘group’ and ‘order’ will not produce desired results for any of the “contained” models.
  • Of course you can still apply them to the main model (i.e. User). Well, except ‘recursive’, which is not needed since Containable handles the associations for you
m4s0n501
  • http://rafaelbandeira3.wordpress.com rafaelbandeira3

    I just think you could improve the last statement
    _

    Each model can have it’s own set of find() options […]
    _

    telling that only ‘fields’ and ‘conditions’ are supported.

    Well done Teknoid,
    and many thanks by the help you conceived me on IRC!

  • teknoid

    @rafaelbandeira3

    Ah, good point. I have updated the post to reflect that.
    And you are welcome ;)

  • http://www.topofcool.com Finster

    What about if I want to filter my result set based on associated models? For instance, what’s the easiest way of getting it to return ONLY Posts that have a specific Tag? In your example, I’ll still get back ALL of my Posts, but then for each Post, it will only show Tags where ‘Tag.name LIKE’=>’%happy%’

  • teknoid

    @Finster

    It depends on whether or not the JOIN is built for your associations. If cake does not build a JOIN, then strict filtering is not possible the way you describe (i.e. you will get Posts and if there is no matching Tag you’ll get an empty array for Tag).

    You can either filter out the results afterFind() or force cake to build joins.

  • http://www.sebgalarneau.com Sebastien G.

    Ok let me explain my case !… if somebody can help me… would be amazing.

    So I got an advanced search engine on a professional social network platform.

    Model
    BusinessGroup has many BusinessUnit
    Business Unit has many BusinessDepartment
    BusinessDepartment has many BusinessType

    Each User has many BusinessType
    Each User has One BusinessGroup

    We can search by BusinessGroup or BusinessUnit or BusinessDepartment or BusinessType

    and my pagination return data’s wrong… it returns me some results that are empty…

    $contain = array(
    ‘Privacy’,
    ‘BusinessGroup’,
    ‘Btype. BusinessType. BtypesDepartment. BusinessDepartment. BusinessUnit =’.$this->data[‘User’][‘business_unit_id’]
    );

    anybody can help me ???

  • http://teknoid.wordpress.com teknoid

    @Sebastien G.

    Well, your containable syntax is not correct… but event still, I don’t think that would solve your problem. Please see the reply above on how to do cross-model searches using JOIN’s (and various methods for doing so).

  • Kenzoh

    Can I ‘saveall’ the data which is retrived by the Containable behavior?

  • http://teknoid.wordpress.com teknoid

    @Kenzoh

    Most of the time you cannot.

  • http://twitter.com/melloukimohamed MELLOUKI Mohamed

    thank you sir,

    nearly 5 years later your post helped me to solve a problem with cake2.3 & CakeDC search plugin !

  • Jens Stigaard

    “Remember, that Containable behavior has to be attached to all of the models in our relationship, therefore to make your life easier you should probably attach the behavior to all models in App Model…”, couldn’t find this statement in the CakePHP documentation. So important one! Thanks!!

  • Steve

    Jens Stigaard, thank you for that comment, I was fight with this and my main model didn’t have the Containable behavior attached…

    Thanks!