Increment Behavior
Increment Behavior is ideal for use when you want to increment a certain field by a delta increment such as adding votes, updating view counters, etc.
First, the code for the Increment Behavior Class, save the following code to a file called increment.php to the app/model/behavior directory.
Next, you want to implement this increment behavior in your model. Say you have an article model for which you want to increment the field 'views' each time the user views the article. So to do this:
Now, you want to increment the view counter each time you show the article to the user. So in your view action of the article controller, you implement the call as follows:
Hope this would come in handy to other users.
Cheers,
Ketan
Behavior Class:
<?php
/**
* Increment Behavior Class file
*
* @author Ketan Patel
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @version 1
*
*/
/**
* Increment Behavior to allow incrementing a single field by given amount.
*
*/
class IncrementBehavior extends ModelBehavior {
var $__settings = array();
/**
* Initiate behavior for the model using specified settings. Available settings:
*
* - incrementFieldName: (string) The name of the field which needs to be incremented
*
*
* @param object $Model Model using the behaviour
* @param array $settings Settings to override for model.
* @access public
*/
function setup(&$Model, $settings = array())
{
$default = array('incrementFieldName' => array('views'));
if (!isset($this->__settings[$Model->alias]))
{
$this->__settings[$Model->alias] = $default;
}
$this->__settings[$Model->alias] = am($this->__settings[$Model->alias], ife(is_array($settings), $settings, array()));
}
function beforeFind(&$model, $query) {}
function afterFind(&$model, $results, $primary) {}
function beforeSave(&$model) {}
function afterSave(&$model, $created) {}
function beforeDelete(&$model) {}
function afterDelete(&$model) {}
function onError(&$model, $error) {}
//Custom Method for a Behavior
/**
* doIncrement method will allow user to increment
* a given field by calling this function from its model.
*
* @param ModelObject $model
* @param integer $id - Record Id for which the $field is to be incremented
* @param integer (optional) $incrementValue, default is 1
* @param string $field (optional) - If not supplied then field name which was provided
* during initialization is used, otherwise
* it is overwritten with the supplied argument.
* @return boolean
*/
function doIncrement(&$model, $id, $incrementValue=1, $field=null)
{
$answer = false;
if (empty($field))
{
$field = $this->__settings[$model->alias]['incrementFieldName'];
}
// Save the internal variables for the model
$recursiveLevel = $model->recursive ;
$data = $model->data;
$model->recursive = -1;
$model->data = $model->findById((int)$id, array('id', $field));
if (!empty($model->data))
{
$counter = (int)$model->data[$model->alias][$field] + (int)$incrementValue;
$conditions = array($model->alias.'.id'=>$id);
$fields = array($field=>$counter);
// Issue updateAll as it won't call any other methods like beforeSave and such in the Model or the
// Behavior methods. Just a step for saving callbacks which are not required.
$answer = $model->updateAll($fields, $conditions);
}
// restore the variables back to original
$model->data = $data;
$model->recursive = $recursiveLevel;
return $answer;
}
}
?>
Next, you want to implement this increment behavior in your model. Say you have an article model for which you want to increment the field 'views' each time the user views the article. So to do this:
Model Class:
<?php
class Article extend AppModel{
var $name = 'Article';
// Add the Increment Behavior as follows
var $actsAs = array('Increment'=>array('incrementFieldName'=>'views'));
}
?>
Now, you want to increment the view counter each time you show the article to the user. So in your view action of the article controller, you implement the call as follows:
Controller Class:
<?php
class ArticlesController extends AppController{
var $name = 'Articles';
function view($id){
// Call the doIncrement Behavior Method to increment the views counter.
// Scenario 1: Increment 'views' field in article table.
// In model article, we specified that 'views' field is the
// increment field and we would like to increment
// by default value of 1, so issue the following command.
$this->Article->doIncrement($id);
// Scenario 2: Increment 'votes' field in article table.
// Since I haven't set it up in model article,
// I can still increment the votes field
// but I have to specify it as below.
$this->Article->doIncrement($id, 1, 'votes');
}
}
?>
Hope this would come in handy to other users.
Cheers,
Ketan

I debugged a call to updateAll() in CakePHP 1.2.5 and it never cares about value of $this->recursive. It always joins all hasOne and belongsTo models.
I wrote a workaround for it by overriding updateAll() on AppModel level. It only supports recursive = -1 but that's what we need anyway.
http://blog.pepa.info/php-html-css/cakephp/getting-rid-of-joins-in-updateall-query/
Petr
I needed to modify the increment behaviour to handle a join table. This means, incrementing a record identified by more than 1 field(2 in my case).
So I modified the doIncrement method to handle the $id parameter as both an array of field values or a simple id.
This is what I added (right after $model->recursive = -1;):
if (is_array($id)) {
$conditions = array();
foreach($id as $fieldName => $fieldValue) {
$conditions[$model->alias.'.'.$fieldName] = $fieldValue;
}
$model->data = $model->find($conditions);
} else {
$conditions = array($model->alias.'.id'=>$id);
$model->data = $model->read(null, $id);
}
Also, remove this line after setting $counter:
$conditions = array($model->alias.'.id'=>$id);
So, to use this code you can call doIncrement like this:
$this->Model->doIncrement(array("model1_id"=>$value1, "model2_id"=>$value2), 1, "clicks");
In my case, I had to call the doIncrement method of the join table from one of the parent models like this:
$this->MyModel->JoinModel->doIncrement(etc...)This fails with a weird SQL error, which is not actually an SQL Error I guess, but can't figure out the cause exactly.
To workaround this, I had to add a HasMany relationship from the Parent model to the Join Model(even though there was already a hasandbelongstomany already defined).
What do you think of this approach? If you have a better idea, let me know.
And thanks Ketan for this useful behaviour.
Cheers,
Marcos
*****************************
UPDATE:
I just found out that I can make it work without adding the extra HasMany array to the model. I was missing the 'with' option when defining the HABTM relation in both sides.
So if I have 3 Models(Model1, Model2 and ModelJoin1and2, I have to add 'with' => 'ModelJoin1and2' to use this model for my join table(add it to Model1 and Model2) and the simply call:
$this->Model1->ModelJoin1and2->doIncrement(etc...)Hope this helps anyone.
Cheers!
Marcos
Thanks.
function UpdateHits($id)
{
//debug ($this->useTable);exit;
$FieldToInc = 'views';
$this->execute("UPDATE $this->useTable SET $FieldToInc=$FieldToInc+1 WHERE id=$id");
}
Why are you again doing a findById inside the behavior?
Lastly if updateAll does not call any of the callbacks then I think it is a bug and will sooner or later be rectified.... don't depend on it.
@Chad counterCache is subtly different it keeps a count of related model rows - Ketan is just incrementing a field