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)
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
- unset($this->Photo->behaviors['file']);
- unset($this->Photo->behaviors['exif']);
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
- $behaviors = func_get_args();
- // Loop through method arguments
- foreach($behaviors as $index => $behavior)
- {
- // If method agrument is an array
- if (is_array($behavior))
- {
- // If method agrument contains more than one element then merge it with method arguments
- if (count($behavior) > 0)
- {
- $behaviors = array_merge($behaviors, $behavior);
- }
- // Unset method argument from method arguments
- unset($behaviors[$index]);
- }
- }
- // Loop through passed behaviors
- foreach ($behaviors as $behavior)
- {
- // Un-set the behavior
- unset($this->behaviors[$behavior]);
- }
- }
If you want to use the method in your controller – do
- $this->MyModel->dontActAs('mybehav1', 'mybehav2');
or
- $behaviorsToDetach = array('mybehav1', 'mybehav2');
- $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
- */
- function nowActsAs($behaviors = array())
- {
- // If passed behaviour is array and contain more than one element
- if (is_array($behaviors) && count($behaviors) > 0)
- {
- // 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
- if (!array_key_exists($method, $this->__behaviorMethods))
- {
- $this->__behaviorMethods[$method] = array($alias, $behavior);
- }
- }
- // Get behaviour's and model behaviour's class methods
- $methods = get_class_methods($this->behaviors[$behavior]);
- $parentMethods = get_class_methods('ModelBehavior');
- // 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 (!in_array($m, $parentMethods))
- {
- // 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
- $this->__behaviorMethods[$m] = array($m, $behavior);
- }
- }
- }
- }
- }
- }
- }
You can use above method in controller like same as you define your actsAs array
- $behaviorsToAttach = array
- (
- 'mybehav1' => array
- (
- 'setting11' => 'value11'
- , 'setting12' => 'value12'
- )
- , 'mybehav2' => array()
- );
- $this->MyModel->nowActsAs($behaviorsToAttach);
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

Just used this on a project where I’m occasionally updating fields , but don’t want the regular behaviours to fire – thanks a lot.
Hey! somebody actually used it
thanks for the comment
Interesting…
I had checked cake 3 years ago, but didn’t used it.
I’m more of a “self doer” than to rely on framework, but the idea of relying to behaviors attached to elements surely looks nice.
I’ll try to put some time on cake in the next week, I could be agreeably surprised.
Thanks for your article.
Great code. Thanks!
But there are several problems.
1) there is no loadBehavior in version 1.2 so I had to change ir to
App::import('Behavior', $behavior);2) It doesn’t work at all. To remove all (hopefully) behaviours you have to loop through attachments like this
foreach ($behaviors as $behavior) // Loop through passed behaviors{
unset($this->actsAs);
foreach ($this->Behaviors->_attached as $key => $attached)
{
if ($attached == $behavior)
unset($this->Behaviors->_attached[$key]);
}
unset($this->Behaviors->$behavior);
unset($this->locale);
}
3) cakephp caches queries. You can’t attach and detach behaviours in the next line. I’m now trying to figure out how to rebuild model shema.
Oh, I found it.
$this->schema(true)@iivs When the post was written there was a loadBehavior() thanks for the updated code
I found another solution. For example I have a model with a variable
var $actsAs = array('i18n' => array('fields'=>array('name')) );I can detach that beavior any time and attach back again. I’ve written 4 functions to do that. 1) private function to merge behavior array
function __behaviors($behaviors = null){
if ($behaviors)
{
foreach ($behaviors as $index => $behavior) // Loop through method arguments
{
if (is_array($behavior)) // If method agrument is an array
{
if (count($behavior) > 0) // If method agrument contains more than one element then merge it with method arguments
{
$behaviors = array_merge($behaviors, $behavior);
}
unset($behaviors[$index]); // Unset method argument from method arguments
}
}
return $behaviors;
}
}
2) private function checks if behavior is disabled or not. if it is disabled, return array index else return false.
function __is_disabled($behavior){
$disabled = false;
if (isset($this->Behaviors->_disabled) and count($this->Behaviors->_disabled) > 0)
{
foreach ($this->Behaviors->_disabled as $key => $val)
{
if ($val == $behavior)
{
$disabled = $key;
break;
}
}
}
return $disabled;
}
3) adds behaviors in $this->Model->Behaviors->_disabled array
function dontActAs()
{
$behaviors = $this->__behaviors(func_get_args());
if ($behaviors and count($behaviors) > 0)
{
foreach ($behaviors as $behavior)
{
if ($this->__is_disabled($behavior) == false)
$this->Behaviors->_disabled[] = $behavior; //add new one
}
$this->schema(true); //rebuild model schema
}
}
4) removes from $this->Model->Behaviors->_disabled array
function nowActsAs()
{
$behaviors = $this->__behaviors(func_get_args());
if ($behaviors and count($behaviors) > 0)
{
foreach ($behaviors as $behavior)
{
$key = $this->__is_disabled($behavior);
unset($this->Behaviors->_disabled[$key]);
}
$this->schema(true); //rebuild model schema
}
}
I also found some functions in cake/libs/model/behavior.php “detach”, “disable” “enable” etc. not sure how they work. I’m still new to this cakephp thing.
Just an update for CakePHP 1.3.x we can use BehaviorCollection
http://api13.cakephp.org/class/behavior-collection