For those few who don't know, behaviors are "extensions" for models. In other words, they are reusable model logic. They help you keep your code DRY, easily maintainable and portable. In this case, we're creating a hit counter behavior, hitcount for short.
When I first began writing this behavior, I was a bit scared because I didn't have any idea where to start from. Then I've looked at some other behaviors and the Cake core itself and decided this was as easy as anything else written in Cake.
My initial version wasn't really as good, and I needed some help. Luckily for me, I did get some help.
But, let us take a look at the behavior itself.
To begin, create the file
/app/models/behaviors/hitcount.php
and type in the following:
class HitcountBehavior extends ModelBehavior { var $name = 'Hitcount'; var $__defaultOptions = array('keyField' => 'id', 'hitField' => 'hitcount'); var $mapMethods = array('/hit/' => 'hit'); function setup(&$Model, $settings = array()) { if (!isset($this->settings[$Model->alias])) { if (empty($settings)) { $this->settings[$Model->alias] = $this->__defaultOptions; } else if (is_array($settings)) { $this->settings[$Model->alias] = array_merge($this->__defaultOptions, $settings); } } } function hit(&$Model, $key = null, $settings = null) { $_settings = array(); if (isset($this->settings[$Model->alias])) $_settings = $this->settings[$Model->alias]; if (is_array($settings)) $_settings = array_merge($_settings, $settings); if (!isset($_settings['hitField']) || !isset($_settings['keyField'])) { $this->log('Miscofigured hitcount behavior!', LOG_WARNING); return; } if (!$Model->hasField($_settings['keyField'])) return; if (!$Model->hasField($_settings['hitField'])) return; if (empty($key)) $key = $Model->id; if (empty($key)) { $this->log('Invalid call to hitcount behavior!', LOG_WARNING); return; } $Model->updateAll ( array ($_settings['hitField'] => $Model->alias.'.'.$_settings['hitField'].' + 1'), array ($Model->alias.'.'.$_settings['keyField'] => $key) ); } }
And now for some naughty bits.
1. $mapMethods bit
var $mapMethods = array('/hit/' => 'hit');
This is a little something I've learned from a presentation about behaviors by Mariano Inglesias, called "Behaviors - Making Your Models Behave". Apparently, by using the $mapMethods variable, you can create magic methods, just like the model does with findBy*.
This means that you can call the hit method of this behavior by calling the $Model->hit(...) which will be mapped to $Model->Hitcount->hit(...) Pretty handy, right?
2. UpdateAll bit
$Model->updateAll ( array ($_settings['hitField'] => $Model->alias.'.'.$_settings['hitField'].' + 1'), array ($Model->alias.'.'.$_settings['keyField'] => $key) );
As mentioned in previously mentioned discussion, updateAll(...) is used for something completely different than the regular model calls. This is a nice thing to know, I really had no clue about it. One of the tricky parts was the $Model->alias prefix, which has to be used exactly as above.
Usage
I won't elaborate on how to use this behavior, I will just show you how do I use it in Neutrino, to count article reads and download count.
Article example:
// use it in model class Article extends AppModel { var $actsAs = array('Hitcount'); } // call it in controller class ArticlesController extends AppController { var $name = 'Articles'; function view($slug = null) { // snip.. // separate rss stats, with custom field if (isset($this->passedArgs['from']) && $this->passedArgs['from'] == 'rss') $this->Article->hit($article['Article']['id'], array('hitField' => 'hitcount_rss')); // regular hitcount $this->Article->hit($article['Article']['id']); // snip.. } }
Download example:
// use it in model, with default custom hitField class Download extends AppModel { var $name = 'Download'; var $actsAs = array ( 'Hitcount' => array('hitField' => 'downloaded') ); } // call it in controller class DownloadsController extends AppController { var $name = 'Downloads'; function get($slug = null) { // snip.. $this->Downloads->hit($download['Download']['id']); // snip.. } }
You can download the behaviour here: Hitcount behavior for CakePHP
I hope you find it useful in some way! If you find any bugs or have an advice, I'd be glad to hear it!
Happy baking!
Article comments — View · Add
Thanks for commenting, appreciate it!
great article - thanks.
Useful tip for reference if you see some strange errors when trying this out...
Remember the behaviour will need <?php and ?> around the source code provided for hitcount.php
Other than that - thank you for this great work!
UltimateOne