Schneimi’s Dev Weblog


Behaviors on model associations

Posted in CakePHP by schneimi on September 6, 2009
Tags: , , , ,

I made a simple behavior that serializes (beforeSave) and deserializes (afterFind) data to and from the database.

The behavior works fine as long as I do a find directly over the model where my behavior is attached, but if I do a find through associated models using the containable behavior, my behavior is not triggered at all and I have to deserialize the data manually after the find, which is very uncomfortable.

Model behavior afterFind is triggered

$this->Model->find(...);

Model behavior afterFind isn’t triggered

$this->OtherModel->contain('Model');
$this->OtherModel->find(...);

I found a ticket and a thread concerning this topic, and it turns out that this is an existing problem with CakePHP for a longer time already.

Because I use the latest CakePHP 1.2.5 and couldn’t find any working patch for it, I adapted the patch found in the thread to this version. I am not very familiar with the core, so it’s still quick&dirty and I cannot tell if it works for every setup. Anyway I hope this still helps other people that run into the same problem.

Here are the changes that have to be done in the cake\libs\model\datasources\dbo_source.php, just place the code after the lines:

Line 877: $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association]);

foreach ($linkModel->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $data = array(array($association => $resultSet[$i][$association]));
    $filtered_data = $linkModel->Behaviors->{$behavior}->afterFind($linkModel, $data, false);
    
    if ($filtered_data) {
      $resultSet[$i][$association] = $filtered_data[0][$association];
    }
  }
}

Line 741: function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {

foreach ($linkModel->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $return = $linkModel->Behaviors->{$behavior}->beforeFind($linkModel, $assocData);
    $assocData = (is_array($return)) ? $return : $assocData;
  }
}

Line 716: $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);

foreach ($model->{$className}->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $filtered_data = $model->{$className}->Behaviors->{$behavior}->afterFind($model->{$className}, $data, false);
    
    if ($filtered_data) {
      $data = $filtered_data;
    }
  }
}

edit:
I added support for the beforeFind callback, because I needed this on a softdelete behavior to filter associated deleted data as well.

I also found out that you don’t have to really hack the core, instead just copy the cake\libs\model\datasources\dbo_source.php into your app app\models\datasources\dbo_source.php and change this file instead.

Eclipse Code Completion for cakePHP

Posted in CakePHP,Eclipse by schneimi on October 12, 2007
Tags: , , , , , ,

Code Completion in Controllers
Add this to your app_controller.php for every Component and Model you want to have completed by eclipse:

/**
 * @var CookieComponent
 */
var $Cookie;
.
.
.
/**
 * @var Yourmodel
 */
var $Yourmodel;
.
.
.

Eclipse will use the PHPdoc for code completion.

Code Completion in Views
Add this to an extra file e.g. eclipse_cc.php:

$html = new HtmlHelper();
$javascript = new JavascriptHelper();
.
.
.

There is no need to include this file, just put it in your app folder and it will fool eclipse.