Background: CakePHP 2.6.3. A pretty stable app. New behavior (MyCustomBehavior
) created to output some extra info.
I have a Model MyModel
acting as Containable
(defined in AppModel
) and then MyCustom
(defined in MyModel
). MyCustomBehavior
is written in a way that it needs to work with the Model's associations with other Models in my app.
Problem: Whenever I contain related models in my find()
call of MyModel
, I cannot get a complete list of MyModel
associations because Containable
behavior unbinds the models that are not contained. However, if I don't set contain
in my find()
options or set 'contain' => false
everything works as expected.
Sample MyModel->belongsTo
public $belongsTo = array(
'MyAnotherModel' => array(
'className' => 'MyAnotherModel',
'foreignKey' => 'my_another_model_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Creator' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Approver' => array(
'className' => 'User',
'foreignKey' => 'approver_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Status' => array(
'className' => 'Status',
'foreignKey' => 'status_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
Sample find()
$this->MyModel->find('all', array(
'fields' => array(...),
'conditions' => array(...),
'contain' => array('Approver', 'Status')
));
Result of MyModel->belongsTo
in MyCustomBehavior::beforeFind()
$belongsTo = array(
'Approver' => array(
...
),
'Status' => array(
...
),
);
Expected MyModel->belongsTo
in MyCustomBehavior::beforeFind()
$belongsTo = array(
'MyAnotherModel' => array(
...
),
'Creator' => array(
...
),
'Approver' => array(
...
),
'Status' => array(
...
),
);
Obvious solution: One dumb way to solve the problem is to simply set Containable
behavior in MyModel
instead of AppModel
to control the order of loading the behaviors, i.e., public $actsAs = ['MyCustom', 'Containable']
. This solution is not the best because there may be other behaviors in other models that depend on Containable
, so the order of Containable
needs to set in each model in app explicitly and hope that I didn't break the app somewhere.
A similar(related) question was asked on SO here but has no answers.
Need a more robust solution that can address the needs of MyCustomBehavior
without having to make changes in rest of the app and looking out for any unexpected behavior.
Attempt 1 (Imperfect, error prone):
One way to recover all the original associations is to call
However, this approach it may fail (SQL error
1066 Not unique table/alias
) to work correctly if I had usedjoins
in my find call (using defaultalias
) to explicitly join to an already associated model. This is becauseContainable
will also attempt to join all these tables restored byresetBindings()
call resulting in join being performed twice with same alias.Attempt 2 (perfect#, no known side effects##):
Further digging through the core
Containable
behavior and docs led me to object$MyModel->__backOriginalAssociation
and$MyModel->__backAssociation
(weird enough thatContainableBehavior
never used$__backContainableAssociation
as the variable name suggests) that was created and used by this behavior to performresetBindings()
. So, my final solution was to simply check ifContainable
is enabled on my modal (redundant in my case because it is attached inAppModel
and is never disabled or detached throughout the app) and check if the object is set on the model.$__backAssociation
holds model associations temporarily to allow for dynamic (un)binding. This solution can definitely be further improved by merging results of$Model->belongsTo
and$Model->__backAssociation['belongsTo']
(orhasMany
,hasOne
,hasAndBelongsToMany
) to include any models that were bound on the fly. I don't need it, so I will skip the code for merging.# Perfect for my own use case and my app setup.
## No side effects were found in my testing that is limited by my level of expertise/skill.
Disclaimer: My work in this post is licensed under WTF Public License(WTFPL). So, do what the f**k you want with the material. Additionally, I claim no responsibility for any financial, physical or mental loss of any kind whatsoever due to the use of above material. Use at your own risk and do your own f**king research before attempting a copy/paste. Don't forget to take a look at cc by-sa 3.0 because SO says "user contributions licensed under cc by-sa 3.0 with attribution required." (check the footer on this page. I know you never noticed it before today! :p)