Let's say, for example, that you are developing a blog or some kind of CMS and you want to track the number of times each post was viewed (maybe to show a list of the most viewed ones).
The easier way to do that is by adding a column to the post table, which will be used to store the number of visits of that item. Each time the post is displayed the value of this column will be increased by 1. The code for this would be something like:
public function actionView($id) {
$post = Post::model()->findByPk($id);
$post->visits += 1;
$post->save();
$this->render('view', array('post' => $post));
}
We have three problems with this approach. All we want is to update the visits column, but the entire record will be updated. Also, if we didn't pass the false argument to the save method, all the validation process will be executed. Also if someone in parallel will update record from 4 to 5, we still save it as 5, as our version of post record have 4.
Since 1.1.8, the CActiveRecord class has a method that can help us with this task. It's the [CActiveRecord::saveCounters()] method and its usage is pretty simple:
public function actionView($id) {
$post = Post::model()->findByPk($id);
$post->saveCounters(array('visits'=>1));
$this->render('view', array('post' => $post));
}
With this, only the visits column will be updated and the validation will not be triggered.
You can also update multiple models at once by passing a custom criteria to updateCounters():
public function actionView($id) {
Post::model()->updateCounters(
array('visits'=>1),
array('condition' => "id = :id"), // or another, broader condition
array(':id' => $id),
);
$this->render('view', array('post' => $post));
}
Important: If you do not pass a condition, all records will be updated.
static call
Second example ia a little bit confusing - you do not need to fetch object by PK prior to updateCounters call (in fact it will be only extra database query that is not needed at all):
public function actionView($id) { Post::model()->updateCounters( array('visits'=>1), array('condition' => "id = :id"), array(':id' => $id), ); }
what about "manipulation"
with this mehtod you will update your counter every time when a view is called, but what i.e. about users who refresh their browser?
this piece of code as a standalone will not produce a really usefull statistic. you have to think about using cookie or session to avoid counting something like refreshing the browser.
Static call
@redguy you're right. You don't need to instantiate the ActiveRecord to use the updateCounters method, but my examples are all based in an action that loads a post, increment the counter and then display the loaded item (just updated the article to make this clear).
what about manipulation
@dionyseos the purpose of the article is to show the usage of the saveCounters and updateCounters methods. Check if the user refreshed the page or something like that is out of the scope of this article
@davi_alexandre
@davi_alexandre: now, when you updated your article it makes more sense :) My comment was made when there was no call to render method in this action and Post instance was not used in any way...
Inaccurate information
Hi,
I think the first example is inaccurate because you can easily update only a single column by passing array of columns to be updated like so:
$post->save(true, array('visits'));
If you dont want do any validation use:
$post->save(false, array('visits'));
But normally validation should be encouraged.
@lubosdz
I also don't like to encourage developers to cancel validation. That's why I noted that it can be done but didn't used it in my example. I'd prefer to use the save method only when I need to insert or update an entire record. I don't like to think that the same method can be used to update an entire record or just some fields. That's too many responsibilities to one single method.
Besides that, calling the save method will trigger the beforeSave and afterSave events, which I think isn't necessary in this scenario. Instead of your approach, I think that saveAttributes is a better choice.
Basically, the difference between saveAttributes and saveCounters is that with the later we don't have to manually increment the field value. Also, thinking about clean code, the saveCounters method name is clearly indicating that we're dealing with counters.
this way it worked
the updateCounters way worked only when I changed array('condition' => "city_id = :id") to just "city_id = :id" :
$post->updateCounters( array('product_count'=>-1), "city_id =:id", array(':id' =>1) );
updateCounters()
I updated the misleading example about updateCounters():
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.