Increment Behavior

by ketan
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.

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($id1'votes');
   }
}
?>

Hope this would come in handy to other users.

Cheers,
Ketan

Report

More on Behaviors

Advertising

Comments

  • petr.pavel posted on 09/27/09 07:12:42 AM
    Hi 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
  • mararual posted on 06/17/09 02:21:42 PM
    Hi all,

    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

  • mguezuraga posted on 09/02/08 01:55:10 PM
    you can decrement doing: $this->Article->doIncrement($id, -1);

    Thanks.
  • programmo posted on 06/07/08 11:37:31 AM
    In your app_model.php:

    function UpdateHits($id)
      {
        //debug ($this->useTable);exit;
        $FieldToInc = 'views';
        $this->execute("UPDATE $this->useTable SET $FieldToInc=$FieldToInc+1 WHERE id=$id"); 
      }
  • tariquesani posted on 05/13/08 11:35:46 PM
    For this to be more useful the ->doIncrement() call should be redundant. Hook it up to the afterFind()

    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
  • chrome posted on 05/13/08 07:25:20 PM
    I think counterCache in the model achieves the same thing, or am I missing something? Either way, it looks good.
login to post a comment.