Bindable Behavior: control your model bindings

By Mariano Iglesias (mariano)
With the birth of CakePHP 1.2 pre-beta, here comes the newly, improved, and extended version of Bindable, a method to easily control what model relationships are obtained from a find operation, and even customize binding settings.

Deprecation


IMPORTANT NOTE: As of CakePHP 1.2 RC1, Bindable behavior will no longer be supported. You should use the core behavior Containable, a mix between the Bindable behavior and Containable. If you are using Bindable on your CakePHP application, then upgrade to 1.2 RC1 and take the following steps:

  1. Change your $actsAs = array('Bindable') to $actsAs = array('Containable') in your AppModel.
  2. Search & Replace: Change all your restrict() calls to contain().
  3. Search & Replace: Change all your 'restrict' find parameters to 'contain'.
  4. Search & Replace: (If you have any) Change your resetBindable() call to resetBindings().

Introduction


In case you haven't heard of it, the previous version of Bindable was expects, which was designed to work with CakePHP 1.1.x, and its objective was to provide an easy way to specify what models are returned when issuing a find (find / findAll) operation over a model that has one or more bindings, without having to deal with CakePHP's core unbindModel. Would the previous version of Bindable work with CakePHP 1.2? Sure. Then why change it? The 1.2 release of CakePHP has brought us lot of exciting features, amongst them behaviors: a way to extend the (you've guessed it) behavior of models.

While pursuing the same goals as those we had with the previous version, it was about time that expects brought something new to the table. Specifically, the ability to change binding settings, such as the order on which one to many and many to many relationships are returned, or what conditions the model binding has to meet, etc. We also needed a strong test coverage that would help all of us prevent any potential problems.

Meet Bindable Behavior, where expects meets CakePHP 1.2. On this article you'll learn what features it provides, how to get it up and running in no time, and what functionalities other than the ordinary it offers.

Download, Source Code and Bug Tracking

The latest Bindable Behavior release is 1.3.1. For those of you who wish to keep up with the latest (not necessarily stable) Bindable Behavior resides in the SVN repository of a project that includes other CakePHP goodies: Cake Syrup. All future official releases will be posted on this article.

Get Bindable Behavior 1.3.1 (Release Notes & Changelog)

All reports, enhancements and feature feedback should be provided through the project page, and not in comments for this article, so I can keep a closer track. Please do report any issues you find with Bindable Behavior using its tracker:

Cake Syrup Tracker (Bugs / Features)
If you want to view the source code of the latest version of the Bindable Behavior you can do so using the SVN browser: bindable.php

Features

Let's get right into the goodies. What does Bindable Behavior offer us?

  1. Flexibile calling methodology: you can call bindable and then issue your find, or take advantage of the embedded find calls in CakePHP 1.2 and do your find and bindable all in just one simple call.
  2. Parameter notation flexibility: you can choose different ways on how to specify the models you need. From dot notation, to associative arrays, or a combination of both.
  3. Ability to override binding settings: tired of having to do a bindModel() to override some binding settings (such as limit, order, offset) right before your bindable call? Want to only get certain fields of a related model, instead of all of them? Don't stress! Bindable Behavior allows you to override these settings easily.
  4. Auto recursiveness: determine the necessary recursiveness needed to fetch the models (up to the deepest level) you specified, and will set the main model recursiveness accordingly. No longer you need to remember to set the recursiveness to a high enough value to get the bindings you need.

Installation & Configuration

After downloading the file bindable.php, place it under your /app/models/behaviors directory. That's it, you are now ready to start using the Bindable Behavior. Since we will want to be able to run bindable on any model, we'll add it to the list of behaviors all our models will utilize: the list defined at the AppModel level. If you haven't done so before, create a file named /app/app_model.php with the contents listed below. If you have already created the file app_model.php, edit it and add the $actsAs member variable to your AppModel.

Model Class:

Download code <?php 
class AppModel extends Model {
    var 
$actsAs = array('Bindable');
}
?>

We now have the Bindable Behavior available for all our application models. Since we have not defined any particular setting, the behavior will be set up for all models using the default settings. If you haven't been exposed to CakePHP behaviors before then all you need to know is that some behaviors accept certain settings that affect the way they, oh well, behave. Those settings are given right on the $actsAs declaration, as a set of attribute-value pairs. Let's start by seeing what settings the Bindable Behavior offers us:

  1. recursive: optional, boolean value, defaults to true. Set it to true to automatically determine the recursiveness level needed to fetch specified models, and set the model recursiveness according to this level. Setting it to false disables this feature.
  2. notices: optional, boolean value, defaults to false. Set to true to let Bindable issue a E_NOTICE error message when bindings referenced in a call do not exist. Setting it to false disable these notices.
  3. autoFields: optional, boolean value, defaults to true. If you override the fields obtained for your bindings and autoFields is set to true, Bindable will make sure that all the necessary fields required to fetch your bindings are also included.
Let's say that we want to get notices on all our models when a referenced binding is invalid, so we go back to app_model.php and edit it so the $actsAs statement now looks like:

PHP Snippet:

Download code <?php var $actsAs = array('Bindable' => array('notices' => true));?>
If you set recursive to false then you are responsible for setting the model recursiveness level to fetch your desired bindings. However you can still use Bindable to let it tell you what recursiveness level you should set, by keeping the result given after calling Bindable:

PHP Snippet:

Download code <?php 
$this
->Model->recursive $this->Model->restrict(...);
$results $this->Model->findAll(...);
?>

Simple Usage

Suppose we have the following models setup:

  1. Article: belongsTo User; hasMany Comment; hasAndBelongsToMany Tag
  2. Comment: belongsTo User, Article; hasMany Attachment
  3. User: hasMany Article, Comment; hasOne Profile
  4. Attachment: belongsTo Comment, Type
  5. Profile: belongsTo User; hasOne Picture; hasMany Friend, Setting; hasAndBelongsToMany Tag
Let's say that we want to get a list of all articles, but we are just interested in the User that created each article. That means that by the time we are issuing our find call on model Article, we just need the User binding. We then would use Bindable Behavior to define this limitation, and perform the find:

Controller Class:

Download code <?php 
$this
->Article->restrict('User');
$articles $this->Article->findAll();
?>

The first line tells Bindable Behavior to unbind all bindings linked to Article except the binding it has with User. At the same time, all bindings in User are also removed. This is per-bindable call, since all our bindings will get restored to their original definition right after our find operation. Furthermore, and since we have not changed the automatic set of recursiveness, Bindable Behavior will set the recursiveness level of the Article model to 1, which is the level needed to obtain the requested binding. The second line will issue the actual find call to get all records in Article. In effect, the above call is the equivalent of the following block of code that uses pure CakePHP core methods:

Controller Class:

Download code <?php 
$this
->Article->unbindModel(array(
    
'hasMany' => array('Comment'),
    
'hasAndBelongsToMany' => array('Tag')
));
$this->Article->User->unbindModel(array(
    
'hasMany' => array('Article''Comment'),
    
'hasOne' => array('Profile')
));
$this->Article->recursive 1;
$articles $this->Article->findAll();
?>

So we can say we have definitely saved a few lines of code. Now what about that first feature we mentioned, "Flexibile calling methodology"? Those of you who have been keeping up with CakePHP 1.2 release notes may have heard that there's an improved syntax to execute your model find operations. This new syntax replaces find (to find just one record), findCount (to find the total number of records that optionally match a given condition), and findAll (to find a set of records that optionally match a given condition.).

Though the old style of calling each of these methods will still work (that's in fact what we've used on the previous example), some bakers prefer the new way. So let's see how Bindable Behavior fits into this. Let's write the same Bindable call as we did before, but using the new syntax:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array('User')));
?>

It is up to you what syntax you decide to use, I personally prefer the last one since it allows you to quickly see what you are searching for (that is, if there are conditions being sent to the find), and what models you are expecting as a result.

Deeper Bindings

So far we have seen the simplest usage possible for Bindable: working with first level bindings. Let's now get all articles, and for each article the user who wrote it (together with its profile record), and the comments it has (along with the user who wrote each comment). Our Bindable call now looks like:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => 'Profile',
    
'Comment' => 'User'
)));
?>

Let's add one more: we are also interested in getting the attachments each comment has, and for each of these attachments we want to get their type:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => 'Profile',
    
'Comment' => array('User''Attachment' => 'Type')
)));
?>

Get the picture? Now some of you may have already got used to the dot notation found in the previous expects version. The Bindable Behavior can handle it too, let's rewrite the previous call to use dot notation instead:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User.Profile'
    
'Comment.User''Comment.Attachment.Type'
)));
?>

Mixing the two notations would also work:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => 'Profile',
    
'Comment' => array('User''Attachment.Type')
)));
?>

As you have pointed out before, Bindable is automatically setting the appropiate recursiveness for the model so we can fetch the models we need. This is a setting that can be changed at any time, as we learned on the Installation & Configuration section. But what happens if we want to get all models linked to an inner relationship, and we don't really want to list them all in Bindable? That is, take the last call we made. Suppose we are interested in fetching all models linked to the Profile binding. Profile, as we saw before, has the following relationships: User (belongsTo), Picture (hasOne), Friend, Setting (hasMany), and Tag (hasAndBelongsToMany). One way is to define all these models on the Bindable call:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => array('Profile' => array(
        
'User''Picture''Friend''Setting''Tag'
    
)),
    
'Comment' => array('User''Attachment.Type')
)));
?>

But that seems like a waste of time, and we might run into trouble if we forget to add a possible future binding we'll need for Profile. So instead let's use the wildcard to let Bindable know that we are interested in all models directly bound to Profile:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => array('Profile' => '*'),
    
'Comment' => array('User''Attachment.Type')
)));
?>

or with dot notation:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => 'Profile.*',
    
'Comment' => array('User''Attachment.Type')
)));
?>

Overriding binding settings

Another great feature the Bindable Behavior has is the ability to change binding settings on the fly. Just as we specify which models should or should not be returned in a find operation, we can also specify what binding settings should be used when we issue the find call. Let's get all articles, for each article the user who wrote it, and the tags linked to the article:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User',
    
'Tag'
)));
?>

Let's say that we don't really want all the tags linked to the article, but we only need 5 of them. We can then specify Bindable to override the limit:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User',
    
'Tag' => array('limit' => 5)
)));
?>

What about getting the latest 5 tags instead? No problem, just override the order binding setting:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User',
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

Assume we want only the username and the email of the user who wrote the article. Eventhough we have that information already on our previous call (since the User model is returned with all its fields), we are interested in saving resources, so we want to specify exactly the fields we need:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => array('fields' => array('username''email')),
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

So as you see, fields is just another binding setting we can override. The good thing about fields is that we don't really need to tell Bindable when we're specifying fields, it will figure it out by itself. So instead we can define the fields just as we would define a model binding:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => array('username''email'),
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

If we also needed User bindings to be returned, but we forget to specify the fields needed to fetch it (like its primary key), Bindable Behavior will do it for us.

You can also mix your binding settings definition with which models should be returned. For example, we can take the previous example and also define that the User model should include the Profile each user has, but we are still only interested in the username and email fields:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User' => array('username''email''Profile'),
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

Bindable behavior will be smart enough to include additional fields that may be required to fetch the Profile binding. If you don't want Bindable to include the mandatory fields, set the behavior setting autoFields to false.

Just in case you are interested, the above fields override can also be written diferently, using dot notation. If we were not interested in the Profile binding we would do:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User(username, email)',
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

But if we are still interested in this notation and getting the Profile model:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User(username, email)' => 'Profile',
    
'Tag' => array('limit' => 5'order' => 'Tag.created DESC')
)));
?>

Once again, it is up to you what notation you decide to use.

Making your binding changes permanent

Every example we've seen to this point shows how CakePHP will restore the original bindings, and its settings, right after a find operation is completed. This is because Bindable uses bindModel, which backs up the bindings for later resetting. However we may find situations on where we need to make the binding changes permanent. CakePHP already offers us a way to specify when calling a bindModel/unbindModel if the original associations should be reset after a find, and since Bindable is a complex wrapper for these core functions, the behavior also offers us a way to do such thing. Let's take the following call:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'User',
    
'Tag' => array('limit' => 5)
)));
?>

And let's assume we want these changes (that is, that Article is now only bound to User and Tag, and the Tag binding will only return 5 records) to be permanent. We specify that through the 'reset' setting:

Controller Class:

Download code <?php 
$articles 
$this->Article->find('all', array('restrict' => array(
    
'reset' => false,
    
'User',
    
'Tag' => array('limit' => 5)
)));
?>

In case you fancy the non-embedded Bindable call, the above would be (we set the reset parameter as the first argument):

Controller Class:

Download code <?php 
$this
->Article->restrict(false, array(
    
'User',
    
'Tag' => array('limit' => 5)
));
$articles $this->Article->find('all');
?>

After running this find operation, if we would've done a normal find('all') on Article we would see that the bindings returned are the same as we've specified on the Bindable call above. This is because the original bindings have not been reset. In fact they won't reset until we say so. Unlike CakePHP's core unbindModel/bindModel, where permanent changes are, well permanent, Bindable still gives us a way to change our minds and restore the original bindings, by calling resetBindable with a force parameter set to true:

Controller Class:

Download code <?php 
$articles 
$this->Article->resetBindable(true);
?>

 

Comments 569

CakePHP Team Comments Author Comments
 

Comment

1 Woot

Thank you so much, although I haven't tried this code out, I've been wanting this functionality even before I wrote my first cake app. Good stuff!
Posted Nov 24, 2007 by Adam
 

Question

2 Nice

Is it possible to change binding settings for an assoc and set assoc binding in the same time ?

Like this :

$articles = $this->Article->find('all', array('restrict' => array(
'Tag' => array('limit' => 5, 'order' => 'Tag.created DESC', 'Comment')
)));
?>
Posted Nov 26, 2007 by Fab
 

Comment

3 Nice

@Fab: yes of course, thanks for pointing me to the fact that I didn't explain that on the tutorial. Look at the last paragraphs of the "Overriding binding settings" section, particularly the paragraph that starts with "You can also mix your binding settings definition"...
Posted Nov 26, 2007 by Mariano Iglesias
 

Comment

4 Great

Mariano thanks for this tool I have done a similar behaviour but is is very trashy, thanks for this One I will use it.
Posted Nov 27, 2007 by Boris Barroso
 

Comment

5 Paginate and count

What if I want to paginate this with the pagination controller?
Or if I want to count number of records in an assosiation.
Like get all articles, and Count the number of comments for each, can I specify this in the fields?
Posted Nov 28, 2007 by Thomas Pedersen
 

Bug

6 Model alias is an array

Bindable 1.2.38 (from SF SVN).

Got some warinings at lines 60-70 of bindable.php.

'Illegal offset type in isset or empty [ROOT\\app\models\behaviors\bindable.php'

I think this is because an array $Model->alias used as index value for __settings
Posted Nov 28, 2007 by Sergey Rodovnichenko
 

Comment

7 Model alias is an array

@Sergey: you need to have at least CakePHP 1.2 pre-beta to use Bindable. Refer to this ticket.
Posted Nov 28, 2007 by Mariano Iglesias
 

Comment

8 CakePHP Version

I think you need to be using SVN for this to work. The App class (used in the test case) and the switch of alias to a string happened after the pre-beta release.
Posted Nov 28, 2007 by Matt Curry
 

Comment

9 Re Model alias is an array and CakePHP Version

Has anyone been able to use this with Pre-Beta: 1.2.0.5875 pre-beta?
Posted Dec 3, 2007 by Adam Kennedy
 

Comment

10 Mariano where can i ..

Mariano Where can i worship You ? :D
No seriously man - this behavior rocks! Thanks!

@Adam Kennedy
You would probably have less problems with the svn branch instead of changing the behavior.
Posted Dec 3, 2007 by Marcin Domanski
 

Comment

11 No

Has anyone been able to use this with Pre-Beta: 1.2.0.5875 pre-beta?
It works only with lastest SVN version.
Posted Dec 3, 2007 by Sergey Rodovnichenko
 

Comment

12 Works with alpha version as well ...

Hey, no rocket science, but small fix and it works in alpha release as well (1.2.0.5427alpha for example):

// add this
$Model->alias = $Model->name;

// before this (lines 60-70)
if (!isset($this->__settings[$Model->alias]))
{
$this->__settings[$Model->alias] = $default;
}
Posted Dec 5, 2007 by Marek
 

Question

13 Nice

But is there also a way to filter on requirements.. for example find articles with this title and those tags?

So you can filter on more then one table? Like a normal join?

Posted Dec 9, 2007 by chris
 

Comment

14 Thank you

Thanks, this behavior is amazing!
Posted Dec 11, 2007 by Aurelijus Valeisa
 

Question

15 Will this make it to the core

Hi Mariano

This is critical functionality - and as a replacement for 'expects()' in 1.2, can we 'expect' to see this code work it's way into the core?

Also, how does this compare/relate to Felix's 'Containable' behaviour?

Many thanks for all your hard work.

Posted Dec 11, 2007 by GreyCells
 

Question

16 What Is The Next Hack

But is there also a way to filter on requirements.. for example find articles with this title and those tags?

So you can filter on more then one table? Like a normal join?


Yeah, but what do you do when the SVN version breaks your other stuff?

I'm using 1.2 pre beta and I applied the above hack, but I also encounter this:

Undefined index: dequest [CORE\mosi\models\behaviors\bindable.php, line 224]
Posted Dec 11, 2007 by Baz L
 

Comment

17 Amazing ... should be in the core

I just tried out this code for the user profile page of the website I'm doing ... it worked as advertised and is a joy to use!

Thank you for sharing this, it is so good I think it should be in the core!

I wonder if any other database abstraction layers exists that have something as powerful as this. We're so lucky these days, think of how we used to have to write multiple extraordinarily long SQL queries by hand for stuff like this.
Posted Dec 13, 2007 by chanon
 

Comment

18 Dont use pre beta use a newer SVN version

Yeah, but what do you do when the SVN version breaks your other stuff?

I'm using 1.2 pre beta and I applied the above hack, but I also encounter this:

Undefined index: dequest [COREmosimodelsbehaviorsbindable.php, line 224]

The 1.2 "pre-beta" version actually has a lot of bugs. A newer 1.2 revision in SVN would actually be less buggy.

Use SVN checkout so that you can easily jump to any revision and just stick to a version that works for you.
Posted Dec 13, 2007 by chanon
 

Comment

19 Problem

I have a User that hasOne UserProfile. In a Page model, I tried $this->User->restrict('UserProfile'). I get this error:

Query: restrict

Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'restrict' at line 1 [CORE\cake\libs\model\datasources\dbo_source.php, line 439]
I've added the $actAs in app_model as stated. Any clue what I could be doing wrong? I've tried other models and the restrict command seems to give me the same errors.

Thanks and it looks like a great behavior if I could get it to work.

BTW I'm using today's trunk of CakePHP so it's the latest SVN version. I've tried the pre-beta version with that hack above, and same error.
Posted Dec 14, 2007 by Herman C
 

Comment

20 Awesome looking

I feel like I waste loads of time tuning the bindings in queries only to end up looking at some really goofy overlapping recursion going on by default. This sounds awesome. I can't currently upgrade to svn trunk, but once this has stabilized a bit, I will be happy to use it on http://dishola.com
Posted Dec 17, 2007 by Lindsey Simon
 

Comment

21 Reply to Herman C

I've added the $actAs in app_model as stated. Any clue what I could be doing wrong? I've tried other models and the restrict command seems to give me the same errors.
Try to use $actsAs instead of $actAs. :)
Posted Dec 28, 2007 by Finjon Kiang
 

Comment

22 SVN access

Mariano,

I've been browsing through the Cake Syrup repository. Some cool behaviors in there - Sluggable I will be taking a look at, if it does what I think it does it will be very useful.

I was intending on using svn:externals to pull down the behavior for Bindable, but of course that only works on directories, not single files. Unfortunately, the way your repository is currently set up means that I would have to take all of the behaviors you have written, instead of just the one(s) I want.

I know I could just grab the file, but I like the flexibility of being able to get right up-to-date with all my add-ons just using svn update. Any chance of a different directory structure so that myself and like-minded people can get only the behaviors they want?
Posted Jan 8, 2008 by Stephen Orr
 

Question

23 Deep Binding

Hi,

How should the bindings set, for relations that run deep? Eg: My application has something like this:

Subject hasMany Topics
Topic hasMany Tests
Test hasMany Questions
Child HABTM Test

I want to restrict so that the output:
subject->topic->test->child (no questions)

also,
subject->topic->test->child (for a single child)

Can this be made possible with bindable Behavior?
Thanks
Posted Jan 14, 2008 by sabkaraja
 

Comment

24 Wow

Mariano, you are my hero. You've obviously put a lot of work into this, and while I know much of the power comes from the release of CakePHP 1.2, what you've provided here has saved me a lot of time and struggling! Many, many thanks for your contribution!
Posted Jan 23, 2008 by Matt Huggins
 

Comment

25 Thanks

Hi Mariano,

Great work and works really well.
I just discover a small bug when using the conditions on binding :

$this->Request->restrict(array('Customer' => array('Document' => array('conditions' => array('OR' => array('vehicle_id' => array(0, NULL, $v_id))),.....

The NULL in the OR array is generating (surprisingly) an error in the "set class" :

Call to undefined method BindableBehavior::get() in C:\xampp\htdocs\cake12\cake\libs\set.php on line 653

I am using cake SVN branch from today ..

Thanks

Posted Feb 12, 2008 by franck
 

Bug

26 Not working

Please help! What am i doing wrong?
I am following these instructions to a T, but none of the associations are being restricted. With the following code, I get the same result as if I had never called "restrict".
I don't get any errors, just too much data =[
i'm on cakephp 1.2 using model.php branch code 6438 to fix habtm save errors.

$this->Community->restrict(false, array(
'User' => array('fields'=>array('id')),
'CommunityUser' => array('fields'=>array('user_id', 'community_id')),
'CommunityType' => array('fields'=>array('id', 'name'))
));
$communities = $this->Community->find('all');

Posted Feb 18, 2008 by ambiguator
 

Comment

27 Does bindable work in PHP4

IF someone has been successful in getting it working in PHP4, it would be appreciated if you could post your version of the behavior.
Posted Mar 4, 2008 by keymaster
 

Comment

28 issues with conditions

First, let me say "thank you" for this excellent behavior.

I've noticed one problem that if I pass a 'conditions' array to the main model, any conditions defined in the restriected array for the related modles are ignored.

I believe someone else had reported that the same applies for the 'fields' array.

Posted Mar 11, 2008 by teknoid
 

Comment

29 most excellent

This should be part of the Cake core.
Posted Mar 12, 2008 by Ariel Arjona
 

Comment

30 howto use conditions with Bindable Behaviour

First of all, thanks Mariano your behavior rules :-)

After spend few hours trying to filter the resultset of the model, we realized of this:

Event hasOne FrontPage
FrontPage belongsTo Event

Controller Class:

<?php $events $this->Event->find('Event.date_start < now()' ,array(),
'Event.date_start ASC');?>

This works ok, but this doesn't work

Controller Class:

<?php $events $this->Event->find('Event.date_start < now()', array('restrict'
=>array('FrontPage')), 'Event.date_start ASC');?>

If you change the first parameter, conditions, to 'all' it works ok:

Controller Class:

<?php $events $this->Event->find('all', array('restrict' =>array('FrontPage')),
'Event.date_start ASC');?>

The solution??? Pass the condition into the second parameter of find.

Controller Class:

<?php 
$events 
$this->Event->find('all', array('conditions'=>'Event.date_start < now()','limit' => 10'order' => 'Event.date_start ASC''restrict' =>array('FrontPage')));
?>
Posted Mar 27, 2008 by David Bolufer
 

Comment

31 Conditions by another model example

It’s a quick example on how you can pass conditions to another model, using bindable.

Example models:
User – has many Profiles
User – has many Tasks

My goal is to retrieve an active profile information (is_active =1 in the DB) for a given User. While at it, I'm also going to grab the Tasks.

It couldn't be easier using bindable (Assuming that $id variable is defined or passed to the action):


<?php
$result 
$this->User->find('all', array('conditions'=>array('User.id'=>$id), 
'restrict' => array('Task''Profile' => array('conditions' => array('Profile.is_active'=>1)))));
?>
Posted Apr 11, 2008 by teknoid
 

Bug

32 Small problem in resetBindable

Hi Mariano,

Thanks for the behavior and I use it intesively. I am doing quite a lot of "Shell" process for heavy batch part of the application. I use quite a lot the "reset = false", and discover a problem when resetting the bindable.
Exemple:
$this->Contract->restrict(false, array('Request.Item', 'Customer(id, type, name)'));
This work perfect for fetching what I need, then I have another series of queries that need to fetch more informations about the Customer, so I do :
$this->Contract->resetBindable(true);

And when tracing the belongsTo relation, I see that the fields "id, type, name" are still into the belongsTo, even restricting again with fields => '*', does not correct the problem ..

Work around : avoid restriction on fields works :-)

Thanks
Posted Apr 15, 2008 by franck
 

Comment

33 Small problem in resetBindable

@franck: can you please report this as a bug on Cake Syrup Tracker? I'm working on a new stable version of bindable and would like to solve that issue as part of the release.
Posted Apr 15, 2008 by Mariano Iglesias
 

Comment

34 similar error and fix

$this->Request->restrict(array('Customer' => array('Document' => array('conditions' => array('OR' => array('vehicle_id' => array(0, NULL, $v_id))),.....

The NULL in the OR array is generating (surprisingly) an error in the "set class" :

Call to undefined method BindableBehavior::get() in C:\xampp\htdocs\cake12\cake\libs\set.php on line 653

I am using cake SVN branch from today ..

Thanks


@franck: I have run into a similar bug when passing a conditions array. the workaround that seems to have worked is passing the conditions as a string instead. ie. instead of

'conditions' => array('Model.field' => 'condition')

you would pass

'conditions' => array("Model.field = 'condition'")

or

'conditions' => array("Model.field in (array_values)")
Posted Apr 16, 2008 by ambiguator
 

Comment

35 thanks for the fix

@ambiguator: thanks, I will give a try ..Actually I do not remember how I did eventually make it work, but certainly not as simple as your solution.
cheers
Posted Apr 16, 2008 by franck
 

Comment

36 amazing

it's amazing,really! Thanks!
Posted Apr 22, 2008 by guoguolove
 

Comment

37 Fantastic

Probably the most useful and practical behaviours for CakePHP to date! Having been dealing with large, complex data models in my application, this saved me hours of writing long-winded binding and unbinding code. I'd say this belongs in the core quite frankly.
Posted Apr 24, 2008 by James King
 

Comment

38 Bindable 1.3.0 released

I've just released the first stable version of Bindable, named 1.3.0. Go at the top of this article to find the usual download links and information.

This release fixes the support for PHP4 that was a little buggy on the beta version, fixes other reported issues at the tracker, and adds a couple of requested enhancements.

Enjoy!
Posted May 3, 2008 by Mariano Iglesias
 

Comment

39 Perhaps a bug

The new version will cause certain statements with aggregate functions with no GROUP BY to fail because of the auto adding of primaryKey to the fields list.

E.g. If you wish to get the no. of posts a user have and the last ID of the post made, you might do something like:


SELECT COUNT(*), MAX(`id`) FROM `table` WHERE `user_id` = 1;

MySQL will throw a problem if there is no GROUP BY clause when the field added turns that query into:

SELECT COUNT(*), MAX(`id`), `Model`.`id` FROM `table` WHERE `user_id` = 1;

Will it be possible to include a toggle so we can optionally disable the auto adding?
Posted May 7, 2008 by Derick Ng
 

Comment

40 Improvements to Bindable

On SVN there's an improved version of Bindable (revision 45) that does a much better job when auto-adding the needed fields to fetch the required bindings. I'm most probably going to release a new version of Bindable very soon, in the meantime get the copy from the SVN repository.

@Derick: included in that version is the possibility to disable the auto-discovery of fields, which is what you are looking for.
Posted May 8, 2008 by Mariano Iglesias
 

Comment

41 Small problem

Hi Mariano,

I have been trying the rev 45 from SVN and I have a small problem in line 275 of bindable (ie: beforeFind).
Actually I am doing a restrict, and a paginate after that has a "fields" array also.
When I am doing the paginate, i get the warning :
Warning (2): array_merge() [function.array-merge]: Argument #1 is not an array [APP\models\behaviors\bindable.php, line 275]
It's quite normal actually because the $query['fields], is not an array but the "count" of paginate :

"COUNT(*) AS `count`"

To solve this I just added :
"&& $Model->findQueryType != 'count'" into the condition (line 274).

hth
francky06l
Posted May 9, 2008 by franck
 

Comment

42 Bindable and paginate

@francky06l: at revision 46 you will find a fix to your problem.

Everyone can also find (see this DIFF) a test case showing how you can use Bindable together with Controller::paginate().

The idea is basically: call restrict() telling it not to reset bindings after a find (first parameter to false), call your paginate(), and then do a resetBindable(true) (to go back to the original bindings & settings the model had prior to our restrict call.)
Posted May 9, 2008 by Mariano Iglesias
 

Comment

43 Bindable 1.3.1 released

I've just released Bindable 1.3.1. Go to the top of this article to find the usual download links and information.

This release improves the detection of mandatory fields needed to fetch the required bindings, it allows you to override the automatic detection using a behavior setting, and brings the test coverage to over 99%.

Enjoy!
Posted May 13, 2008 by Mariano Iglesias
 

Comment

44 OMG

Just had a chance to use this for the first time...

Its freaking brilliant! Thank you so much for this behaviour. It makes so many tasks, that are normally tedious, totally painless.

Major props!
Posted May 17, 2008 by Jay Mallison
 

Comment

45 Bindable in the core

For those of you who stay updated with the latest in the CakePHP repository you may have noticed that after Changeset 6918 we have added a new behavior to CakePHP core: Containable.

This behavior is a mix between Containable and Bindable, and provides the best out-of-the-box functionality for model binding manipulation.

Those of you who are already using Bindable are encouraged to update to this new behavior. Here's the list of very easy steps you need to make to let your current Bindable-based code work with Containable (since all of Bindable's parameter and method calls are supported in the new Containable):

  1. Change your $actsAs = array('Bindable') in AppModel to $actsAs = array('Containable')
  2. Change your 'restrict' find parameters to 'contain'
  3. Change your restrict() method calls to contain()
  4. Change your resetBindable() method calls to resetBindings()

That's it! I told you it was going to be easy :)

As you can probably imagine, I will no longer support and change Bindable as my effort towards this functionality will be put only on CakePHP's Containable behavior.

Enjoy!
Posted May 18, 2008 by Mariano Iglesias
 

Comment

46 Yippeeee (exclamation mark)

I just noticed this in the timeline and thought I'd mention it here, but you beat me to it... This is just great news Mariano! If there were one single feature in CakePHP that I could have added to the core, this would've been it.

Many thanks for yours and Felix's hard work.

~GreyCells
Posted May 18, 2008 by GreyCells