问题
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.
回答1:
Attempt 1 (Imperfect, error prone):
One way to recover all the original associations is to call
$MyModel->resetBindings($MyModel->alias);
$belongsToAssoc = $MyModel->belongsTo; // will now have original belongsTo assoc
However, this approach it may fail (SQL error 1066 Not unique table/alias
) to work correctly if I had used joins
in my find call (using default alias
) to explicitly join to an already associated model. This is because Containable
will also attempt to join all these tables restored by resetBindings()
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 that ContainableBehavior
never used $__backContainableAssociation
as the variable name suggests) that was created and used by this behavior to perform resetBindings()
. So, my final solution was to simply check if Containable
is enabled on my modal (redundant in my case because it is attached in AppModel
and is never disabled or detached throughout the app) and check if the object is set on the model.
// somewhere in MyCustomBehavior
private function __getOriginalAssociations(Model $Model, $type = 'belongsTo') {
if(isset($Model->__backAssociation['belongsTo']) && !empty($Model->__backAssociation['belongsTo'])) { // do an additional test for $Model->Behaviors->enabled('Containable') if you need
return $Model->__backAssociation[$type];
}
return $Model->$type;
}
public function beforeFind(Model $Model, $query) {
// somewhere in MyCustomBehavior::beforeFind()
...
$belongsToAssoc = $this->__getOriginalAssociations($MyModel, 'belongsTo'); // will now have original belongsTo assoc
...
return $query;
}
$__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']
(or hasMany
, 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)
来源:https://stackoverflow.com/questions/52092268/get-original-associations-after-using-containable-behavior-in-cakephp