Attach & detach behaviors at run-time in CakePHP Models
Behaviors are one of the best things that have been added to CakePHP 1.2, they allow you to add functionality to your models in a very elegant and modular fashion. They also promote a lot of code reuse.
Perhaps a real life example would illustrate it better. In Cheesecake Photoblog when a new photo was added it was the controller which checked if a proper file was uploaded, resized it and then move it, once this was done the EXIF vendor class was used to extract the EXIF info and store that in the database. The code for the photos/add method was fairly bloated and ugly. However with behaviors, in Cheesecake Photoblog 2.0, we were able to move these two task away from the action and now all that the add action code does is call $this->Photo->save() the attached 'file' and 'exif' behaviors automagically take care of the rest. Also with the file upload handling moved code to a behavior it can be reused with any model which needs to handle file uploads
While writing code for V2.0 I needed to detach some behaviors at run-time. More specifically I did not want the File Upload and EXIF behavior to act during an edit action when the picture was not being replaced! This functionality was not built in to CakePHP 1.2 last I checked and I wanted it NOW - solution? code it!
Detaching behavior was simple in the controller I did
This got me thinking... why not have a method which will do it more elegantly. The result was
-
/**
-
* Method used to un-set behaviors at run-time
-
*
-
* @access public
-
*/
-
function dontActAs()
-
{
-
// Get method arguments
-
-
// Loop through method arguments
-
foreach($behaviors as $index => $behavior)
-
{
-
// If method agrument is an array
-
{
-
// If method agrument contains more than one element then merge it with method arguments
-
{
-
}
-
-
// Unset method argument from method arguments
-
}
-
}
-
-
// Loop through passed behaviors
-
foreach ($behaviors as $behavior)
-
{
-
// Un-set the behavior
-
}
-
}
If you want to use the method in your controller - do
-
$this->MyModel->dontActAs('mybehav1', 'mybehav2');
or
-
-
$this->MyModel->dontActAs($behaviorsToDetach);
The benefit of being allowed to code on Open Source projects during work is that you can do what you want and as you want. While just detaching behaviors at runtime would be enough for most projects, For safety I wanted a method to attach a behavior at runtime.
A bit of delving in to cake's core model's constructor code (related to behavior attachment) I ended up with following code
-
/**
-
* Method used to set behaviors at run-time
-
*
-
* @access public
-
* @param array $behaviors List of behaviors to set at run-time
-
*/
-
{
-
// If passed behaviour is array and contain more than one element
-
{
-
// Initialize behavior's callback methods and then normalize passed behaviors array
-
$callbacks = array('setup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError');
-
$behaviors = Set::normalize($behaviors);
-
-
// Loop through passed behaviors
-
foreach ($behaviors as $behavior => $config)
-
{
-
// Build behavior's class name
-
$className = $behavior . 'Behavior';
-
-
// Behavior's class file included successfully
-
if (loadBehavior($behavior))
-
{
-
// If behavior's class object is in registry
-
if (ClassRegistry::isKeySet($className))
-
{
-
// If PHP5 then get object directly
-
if (PHP5)
-
{
-
$this->behaviors[$behavior] = ClassRegistry::getObject($className);
-
}
-
else
-
{
-
// If not PHP5 then get object by reference
-
$this->behaviors[$behavior] =& ClassRegistry::getObject($className);
-
}
-
}
-
else
-
{
-
// If PHP5 then create/get object directly
-
if (PHP5)
-
{
-
$this->behaviors[$behavior] = new $className;
-
}
-
else
-
{
-
// If not PHP5 then create/get object by reference
-
$this->behaviors[$behavior] =& new $className;
-
}
-
-
// Store object in registry
-
ClassRegistry::addObject($className, $this->behaviors[$behavior]);
-
}
-
-
// Call behavior's 'setup' method and then get behavior's map methods
-
$this->behaviors[$behavior]->setup($this, $config);
-
$methods = $this->behaviors[$behavior]->mapMethods;
-
-
// Loop through behavior's map methods
-
foreach ($methods as $method => $alias)
-
{
-
// If map method is not already added in array then add it
-
{
-
}
-
}
-
-
// Get behaviour's and model behaviour's class methods
-
-
// Loop through behavior's class methods
-
foreach ($methods as $m)
-
{
-
// If behavior's class method is not present in model behaviour's class methods then proceed further
-
{
-
// If behavior's class method is not protected/private, it is not in behavior methods and also not in callback methods
-
if (strpos($m, '_') !== 0 && !array_key_exists($m, $this->__behaviorMethods) && !in_array($m, $callbacks))
-
{
-
// Store behavior's class method details
-
}
-
}
-
}
-
}
-
}
-
}
-
}
You can use above method in controller like same as you define your actsAs array
Of Course - you all know that the above two methods should go into your app_model.php - right?
P.S. Thanks to Dr. Tarique Sani for his help with this article
Update Cakebaker blog tells us that attaching and detaching behaviors on the fly is now built in CakePHP core (we obviously have been doing something right ![]()
About this entry
You’re currently reading “ Attach & detach behaviors at run-time in CakePHP Models ,” an entry on SANIsoft - PHP for E Biz
- Published:
- 6.26.07 / 11am
- Category:
- CakePHP, Cheesecake, HowTo, PHP
- Author:
- Amit Badkas
5 Comments
Jump to comment form | comments rss | trackback uri