RSS
 

Posts Tagged ‘Behavior’

Helper? Component? Lib? CakePHP2

03 Apr

Note: For 1.3 see the old article on this subject.

I want to outline some ideas on how to chose the appropriate class type if you want to add some additional feature to your cake app. For a beginner it might be difficult to decide what to use in which case. Hope this article helps to clarify things. Feel free to comment on this below.

Overview

  • bootstrap functions (most generic methods – no class context)
  • Lib (most generic class)
  • Helper (view level)
  • Component (controller level)

And of course the base classes: Datasource, Model and Controller (and View).

Level of Independence

We need to ask ourselves if this feature needs to interact with other cake elements, like controller, some components, models, …

If it needs to save to the session, or if it needs some controller functionality, it will have to be a component. With initialize(Controller $controllerReference) and startup(Controller $controllerReference) this is very easy to accomplish.

With Cake13 libs have been introduced. Not every piece of “controller” code necessarily needs to be component anymore. So if you retrieve an RSS feed or get the weather from a weather channel web service you could just make a clean and independent lib class. No need to extend the cake object or even pass the controller reference. Less memory and dependency is a good thing. And its easier to test, anyway.

Helpers are used if the result is in relation to the view – in other words if it gets printed/echoed right away. If you want to retrieve some web service information and save it to the database use a component instead.

Database related?

Often times we need to adjust some model data, we either use a component first and then pass it to the model or we use beforeValidate() and beforeSave() in the model. Same goes for the other direction (from model to controller): afterFind() or a component call afterwards. This is fine for custom changes. As soon as it could be something useful for several models, it might make sense to build a behavior. The code gets cleaner and your models more powerful.

Examples would be: “Last Editor/Last Change”, “Geocoding”, “Auto-Capitalize first letter of name/title”, “Format/Localize Date/Time”, …

Reducing code redundancy

Now that we have a vague understanding where to use what type of tool, we should think about cutting down the redundancy. Lets say we use the vendor class “phpThump”. We would have to write two wrappers. one for the helper (display images in the view) and one for the component (uploading images and resizing), maybe even for some behavior (validating + uploading + resizing). This wrapper handles default values from Configure::read() and other cake related settings. In this scenario we should build one single library in /Lib, maybe called PhpthumbLib.php. Here we put our wrapper with our custom functions. Then we build a helper (view side) as well as a component or a behavior (controller side). They will import and use the library file. This is a cleaner approach because changes in the library class will be available in all files it is used in. Bonus: The main library file is easier to test. And therefore testing the other classes afterwards is easier, too.

Generally speaking, all web services should be some kind of library file (some would make a datasource out of it). It doesn’t matter then if we use it in components or helpers, because it will fit either way.

A helper in a controller, though, is not really a nice thing. With Cake2.1 there is no need to use helpers in the controller or model code anymore. All text/number/time helper methods have been moved to the Utility package and can now by used from anywhere within your application in a dry and clean way:

App::uses('CakeNumber', 'Utility');
$myValueInPercent = CakeNumber::toPercentage(45.12, 0); // or dynamically - but statically is easier in this case

Plugin or not?

If your feature is not site-specific but very generic it probably makes sense to build a plugin. This way all other apps can easily use the same plugin. Additionally, test cases, assets etc are all combined in one folder – clean and extendable.

Examples: A bookmark helper usually can be used in several apps, whereas a custom helper with two functions for this one app will not be very useful anywhere else.

Usage of those resources

For components, add it to the controller in order to use it in the corresponding actions:

var $components = array('MyC'); # file is in /APP/controllers/components named MyCComponent.php
And in one of the controller’s actions:
$this->MyC->foo();

For helpers, add it to the controller in order to use it in the corresponding views:

var $helpers = array('MyH'); # file is in /APP/View/Helper named MyHHelper.php
And in one of the controller’s views:
$this->MyH->foo();

Libs can be used everywhere – include them at runtime:

App::uses('MyL', 'Lib'); # file is in /APP/Lib/ named MyL.php
$MyL = new MyL();
$result = $MyL->foo($input);
Possible in controllers, components, behaviors, view, helpers, elements and everything else. They are the most generic classes you can create.

Sidenote: I like to keep those files appended with a suffix, as well (Lib to avoid collisions with other classes or core classes):

App::uses('MyLLib', 'Lib'); # file is in /APP/Lib/ named MyLLib.php
$MyL = new MyLLib();
But that is just my personal convention.

Also note that you are encouraged in 2.x to group your lib classes in packages. So if you have some Utility Helper of your own, you might want to create a subfolder for it (you can use the core folder names or your own naming scheme):

App::uses('MyUtilityLib', 'Utility'); # file is in /APP/Lib/Utility/ named MyUtilityLib.php
$MyL = new MyLLib();
Same with plugins:
App::uses('UrlCacheManagerLib', 'Tools.Routing'); # file is in /APP/Plugin/Tools/Lib/Routing/ named UrlCacheManagerLib.php
$UrlCacheManager = new UrlCacheManagerLib();

Behaviors and other elements are used similar to the above.

For Plugins simply add the plugin name: “Text” becomes “Plugin.Text” etc

bootstrap functions: If those functions are so generic that you want to use them like h() or env() you can define them in your bootstrap. But be aware that this can lead to chaos if you do that for too many things.

Hacks for special use cases

Sometimes we need to break MVC in order to avoid redundance (and stay DRY). If a helper has to be available in a controller we need to manually include it then at runtime:

App::uses('MySpecialHelper', 'Helper');
App::uses('View', 'View');
$MySpecialHelper = new MySpecialHelper(new View(null));
$myText = $MySpecialHelper->foo($myText);
I want to emphasize that this should only be done if not possible any other way. What I do in this case: Move the functionality to a Lib class and use it inside the helper. This way we can use the Lib in the controller/model level and the helper is still the convenience access for the view level.

 
2 Comments

Posted in CakePHP

 

Bitmasked – Using bitmasks in CakePHP

26 Feb

Introduction

Based on Mark Story’s Post about this and the need in one of my apps for this lead me to believe that a behavior would be most appropriate to handle this. So I gave it a try.

There already is a Bitmasked Behavior out there – but it uses a secondary table which will get joined to the primary one. I wanted something that works on the same table (and an existing field), though. It is supposed to be simple and yet powerful.

Use cases

In some cases a boolean flag in your model is not enough anymore. It might need a second flag – or even a third. You could do that and add those new fields – or combine them all using a bitmask. You can work with quite a few flags this way using a single database field.

The theoretical limit for a 64-bit integer [SQL: BIGINT unsigned] would be 64 bits (2^64). But don’t use bitmasks if you seem to need more than a hand full. Then you obviously do something wrong and should better use a joined table etc. I highly recommend using tinyint(3) unsigned which can hold up to 8 bits – more than enough. It still only needs 1 byte.

There are two different ways to check against those bitmasks. The first one – in my implementation the default one – assumes that you are using them similar to a list of multiple selects/checkboxes. Those can be individually edited in a form and saved as this single field. On retrieval you usually want to get only those records with certain bit groups set (a kind of OR condition). The second use case is on retrieving data based on the value of single flags. Where it doesn’t matter if other flags are also set or not. This case is covered by the last chapter and those “contains” methods of the behavior which create a SQL snippet to be used in the condition.

Bitmasked Behavior

Basically it encodes the array of bit flags into a single bitmask on save and vice versa on find. I created it as an extension of my pretty well working Enum stuff. It can use this type of enum declaration for our bitmask, as well. We use constants as this is the cleanest approach to define model based field values that need to be hardcoded in your application.

The code can be found in the Tools Plugin.

Basic Usage

We first want to attach the behavior via public $uses() or at runtime:

$this->Comment->Behaviors->attach('Bitmasked', array('mappedField'=>'statuses'));
The mappedField param is quite handy if you want more control over your bitmask. It stores the array under this alias and does not override the bitmask key. So in our case status will always contain the integer bitmask and statuses the verbose array of it.

We then need to define the bitmask in our model:

const STATUS_ACTIVE = 1;
const STATUS_PUBLISHED = 2;
const STATUS_APPROVED = 4;
const STATUS_FLAGGED = 8;
 
public static function statuses($value = null) {
    $options = array(
        self::STATUS_ACTIVE => __('Active'),
        self::STATUS_PUBLISHED => __('Published'),
        self::STATUS_APPROVED => __('Approved'),
        self::STATUS_FLAGGED => __('Flagged'),
    );
    return parent::enum($value, $options);
}
Please not that you need to define MyModel::enum by extending my MyModel or by putting it into your AppModel manually if you want to use that. You don’t have to, of course.

Either way, we need an array of bits that we want to work with. They should start at 1 and can go as high as your database field is designed for (2, 4, 8, 16, 32, …) or common sense cries “stop”.

The behavior tries to find the plural method of your field name automatically (statuses for status here). You can always override this by manually setting param bits to your method name. You may also directly assign the bits to this param. The advantage of the static model method is that we can use it everywhere else, as well. For instance in our forms to display some multiple checkbox form field for those flags.

Now, in the add/edit form we can add the field:

echo $this->Form->input('statuses', array('options'=>Comment::statuses(), 'multiple'=>'checkbox'));
It will save the final bitmask to the field status.

Searching for a record can be done using the bitmask itself:

$conditions = array('status'=>BitmaskedComment::STATUS_ACTIVE | BitmaskedComment::STATUS_APPROVED);
$comment = $this->Comment->find('first', array('conditions'=>$conditions));

If you want to search for a specific record, you can also use the array structure, though:

$conditions = array('statuses'=>array(BitmaskedComment::STATUS_ACTIVE, BitmaskedComment::STATUS_APPROVED));
$comment = $this->Comment->find('first', array('conditions'=>$conditions));
Note that it uses the mappedField – in this case statuses – for the array lookup.

Retrieving the record will then transform the bitmask value back into an array of bits. If you use a mappedField, you will find it there instead of an overwritten field value.

Extended Usage

In your model you should define a rule for it if you want at least one flag to be selected:

public $validate = array(
    'status' => array(
        'notEmpty' => array(
            'rule' => 'notEmpty',
            'last' => true
        )
    )
);
You can always add more rules manually – for example if you want to make sure only some combinations are valid etc.

There are cases where you want to get all records that have a specific bit set, no matter what the other bits are set to. In this case you currently need to wrap it like so:

// contains BitmaskedComment::STATUS_PUBLISHED:
$conditions = $this->Comment->containsBit(BitmaskedComment::STATUS_PUBLISHED);
$res = $this->Comment->find('all', array('conditions'=>$conditions));
 
// dos not contains BitmaskedComment::STATUS_PUBLISHED:
$conditions = $this->Comment->containsNotBit(BitmaskedComment::STATUS_PUBLISHED);
$res = $this->Comment->find('all', array('conditions'=>$conditions));

Tip: Looking at the test cases is usually one of the best ways to figure out how a behavior is supposed to work.

Manual usage

In some cases you might want to control encoding/decoding yourself (or have to by using saveField() method which does not trigger the behavior). Let’s also get the enums of a different model here (using a static method – see my enums for details) for testing purposes:

$this->UserSetting->Behaviors->attach('Tools.Bitmasked', array('field'=>'choices', 'bits'=>'ProfileChoice::types'));
$choices = (int) $this->UserSetting->field('my_choices', array('id'=>$uid)); // a numeric value (0 ... x)
$choicesArray = $this->UserSetting->decodeBitmask($choices); // now the array for the checkboxes
 
if ($this->request->isPost()) {
    $choicesArray = $this->request->data['UserSetting']['choices']; // our posted form array
    $choices = $this->UserSetting->encodeBitmask($choicesArray); // back to integer
    $this->UserSetting->id = $uid;
    $this->UserSetting->saveField('my_choices', $choices);
    // flash message and redirect
} else {
    $this->request->data['UserSetting']['choices'] = $choicesArray;
}
 
// in the view:
echo $this->Form->input('choices', array('options'=>ProfileChoice::types(), 'multiple'=>'checkbox'));

 
2 Comments

Posted in CakePHP

 

Working with passwords in CakePHP

25 Aug

A clean approach for working with passwords

Most beginners make the mistake to use the baked templates and “password” as fieldname for the database field “password”. But this causes quite some issues. For starters it will also hash and save empty strings (no input) and mess up previously entered passwords. So you should never use this field in the forms. Always use “pwd” or some other temporary field with you will then map the real field in your model’s beforeSave() method:

public function beforeSave($options = array()) {
    parent::beforeSave($options);
    if (!empty($this->data[$this->alias]['pwd'])) {
        $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['pwd']);
    }
    ...
    return true;
}
The main advantage of ALWAYS using a different name (even with cake2) is that you cannot accidently write an unhandled/unhashed password to your database. Also consider providing a “pwd_repeat” field to ensure the entered password was typed correctly – especially on the user side. To keep this easy and extendable use the following behavior.

Introducing my ChangePassword behavior

The code can be found on github (CakePHP2.0) [1.3]. What it does is taking care of all the stuff that usually is done redundant in several places: hashing, validating, whitelisting, ensuring fields, … Using the behavior the controller code stays slim (remember: fat model, slim controller).

You can override the default values with Configure::write('ChangePassword.auth', 'MyAuth'); for example (config/bootstrap). It is also possible to change them at runtime – where you include the behavior. It is important NOT to globally assign the behavior to the model – for security purposes. Only use it in those actions that actually work with the password (update/reset/…). This way other actions cannot accidently use the behavior to update the password (or by tempering with the forms).

PWD_MIN_LENGTH and PWD_MAX_LENGTH are constants and can be set in the bootstrap.

Admin Forms

if ($this->request->isPost() || $this->request->isPut()) {
    $this->User->Behaviors->attach('Tools.ChangePassword', array('confirm'=>false, 'allowEmpty'=>true));
    if ($this->User->save($this->request->data)) {
        //YEAH
    } else {
        //OH NO
    }
}
As admin I do not want to retype the password. So confirm is set to false. Allowing empty passwords can be set as well (although not recommended).

Change password form for the user

if ($this->request->isPost() || $this->request->isPut()) {
    # attach the behavior and force the user to enter the current password for security purposes
 $this->User->Behaviors->attach('Tools.ChangePassword', array('current'=>true));
    $this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
    if ($this->User->save($this->request->data, true, array('pwd', 'pwd_repeat', 'id'))) {
        //SUCCESS flash/redirect
    }
    //ERROR flash
 
    # pw should not be passed to the view again for security reasons
 unset($this->request->data['User']['pwd']);
    unset($this->request->data['User']['pwd_repeat']);
}
As user I have to first provide the old password and confirm (retype) the new password. Confirmation is active by default.

Note: this is an own change_pw action. But it could as well a complete edit form. Please make sure, though, that you set the white list to only those fields that are allowed to be updated (or use Security component). Adding pwd and pwd_repeat etc should not be necessary as the behavior takes care of adding them to the whitelist.

Register forms

Quite similar to the user edit forms. Only without the user_id part, of course. It boils down to:

if ($this->request->isPost() || $this->request->isPut()) {
    $this->User->Behaviors->attach('Tools.ChangePassword', array('current'=>false));
    if ($this->User->save($this->request->data, true, array('pwd', 'pwd_repeat', ...))) {
        //SUCCESS flash/redirect
    }
    //ERROR flash
    unset($this->request->data['User']['pwd']);
    unset($this->request->data['User']['pwd_repeat']);
}

Login

In login forms some forget to clear the password after an unsuccessful post.

if ($this->request->isPost() || $this->request->isPut()) {
    if ($this->Auth->login()) { # for 1.3 use ->user() here
     ...
    }
    # the important part after every post:
 $this->request->data['User']['password'] = '';
}
Now the password field stays empty. Which is usually a good thing to do in forms. The ChangePassword behavior is not needed here as the Auth component already takes care of the login procedure. That’s why “password” as fieldname is all right in this case.

Some tricky parts

Maybe you wondered about “nonEmptyToEmpty”. Well, if you do allow empty passwords and you simply add pwd to the form fields you would always override any (maybe non empty) password with the hashed empty string. Therefore the behavior does by default not allow empty passwords once a (nonempty) password is set. You can – if you design your edit forms carefully – set this to true.

Notes

In Cake1.3 the framework automatically hashes the field “password”. that’s one of the reasons why you should use “pwd” or something similar and either let the behavior/model handle it or assign “pwd” to “password” manually on save. In Cake2.0 no automatic hashing will be done anymore. But it will still be useful not to use “password”. Especially because the validation rules would not apply to a sha1 string etc^^

For resetting local passwords for development purposes (or after salt/hash changes) you might want to look into my password reset shell script.

Note: this is written for 2.x (so $this->request->data instead of this->data is being used).

 
10 Comments

Posted in CakePHP

 

Working with models

08 Mar

Since there is so much to talk about it here, I will cut right to the chase.

We all know about “Fat Models, Slim Controllers”. So basically, as much as possible of the code should be in the model. For a good reason: If you need that code in another controller, you can now easily access it. Having it inside a controller does not give you this option. Thats another principle: “Don’t repeat yourself”.

What if I use the same model in different contexts?

We sometimes need the “User” (class name!) Model to be more than just that. Imagine a forum with posts. We could have “User”, “LastUser”, “FirstUser” etc. For all those we have custom belongsTo relations in our models.

If we had something like this in our code, we could get in trouble:

function beforeValidate() {
    if (isset($this->data['User']['title'])) {
        $this->data['User']['title'] = ucfirst($this->data['User']['title']); 
    }
    return true;
}
So always use $this->alias:
function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias]['title'])) {
        $this->data[$this->alias]['title'] = ucfirst($this->data[$this->alias]['title']);
    }
    return true;
}
Now we can set up any additional relation without breaking anything:
var $belongsTo = array(
    'LastUser' => array(
        'className'    => 'User',
        'foreignKey'   => 'last_user_id',
        'fields' => array('id', 'username')
    )
);
$this->alias is now “LastUser” for this relation.

Always try to outsource code, that repeats itself, into behaviors or the AppModel callbacks. So if the “ucfirst()” code snippet from above is used in pretty much all models, you can either use beforeValidate() of the AppModel:

function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias][$this->displayField])) {
        $this->data[$this->alias][$this->displayField] = ucfirst($this->data[$this->alias][$this->displayField]);
    }
    return true;
}
or you can create a behavior:
class DoFancyBehavior extends ModelBehavior {
 
    function setup(Model $Model, $config = array()) {
        $this->config[$Model->alias] = $this->default;
        $this->config[$Model->alias] = array_merge($this->config[$Model->alias], $config);
        // [...]
        $this->Model = $Model;
    }
 
    function beforeValidate(Model $Model) {
        $this->_doFancyUcfirst($Model->data);
        return true;
    }
 
    function _doFancyUcfirst(&$data) {
        if (isset($data[$this->Model->alias][$this->Model->displayField])) { 
            $data[$this->Model->alias][$this->Model->displayField] = ucfirst($data[$this->Model->alias][$this->Model->displayField]);
        }
    }
 
}
With the behavior you can define which models will and which won’t “behave” fancy. But of course the same would be possible for the AppModel method using an internal variable ($this->behaveFancy: true/false) and checking on it in our callback.

 
No Comments

Posted in CakePHP

 

Behaviors in CakePHP

02 Oct

I didn’t use behaviors for the first 12 months with Cake. I regret it now, because the code is no much cleaner and easier to extend. Let’s make a test with a simple task. We need some additional statistics to a specific model. We could do that in the controller (very messy!) or with a custom model function. Second option can be used in different views etc. But to cover all kinds of find operations it is likely to get messy as well. The easiest and cleanest approach is a behavior which goes over every found record, does the calculation, maybe gets some related data and attaches the result to the current record before it reaches the controller.

Workflow

Controller: Model->Behaviours->attach(‘MyStatistics’); # one line of code Controller: Model->find(all) Model: find(all) Behaviour: afterFind() # here we add the statistics Controller: passes data to view View: displays data + statistics

The nice byproduct: We won’t have any overhead if we don’t need the statistics. The Behavior will simply not be included.

$model->alias or $model->name?

If we want to use a behavior in many different places and models, we cannot hardcode the model names. We need to dynamically retrieve them. In most cases it is $this->alias or $model->alias. $model->name is the original name and can cause errors if you use some non-standard model relations.

example:

$hasMany = array('Receiver' => array('className' => 'User'));
$model->alias is “Receiver” (which the behavior needs) $model->name still is “User”

Basic set up

class MyStatisticsBehavior extends ModelBehavior {
    var $default = array(
        'fields' => array(),
        'prefix' => 'stat_',
        'primary' => true
    );
    var $config = array();
 
    /**
    * adjust configs like: $model->Behaviors-attach('MyStatistics', array('fields'=>array('xyz')))
    */
    function setup(&$model, $config = array()) {
        $this->config[$model->alias] = $this->default;
        $this->config[$model->alias] = array_merge($this->config[$model->alias], $config);
    }
 
    /**
    * main function
    */
    function afterFind(&$model, $result, $primary = false) {
        //modify the result here
        return $result;
    }
}

Now we want to do some operation for each of the given fields. First we need to check if the result exists. In this example i simplified “primary”. Usually you can control this with the above settings. In our case we only want the statistics if the model is the primary model (the one which initialized the query). After that we loop over every record.

function afterFind(&$model, $result, $primary = false) {
    if (!$result || !$primary) {
        return $result;
    }
 
    # check for single of multiple model
 $keys = array_keys($result);
    if (!is_numeric($keys[0])) {
        $this->stats($model, $result);
    } else {
        foreach ($keys as $index) {
            $this->stats($model, $result[$index]);
        }
    }
    return $result;
}
Note that we use “reference” for passing the data. this way we don’t need to return the data from the sub-methods. it is the very same record we modify.

The stats() method:

function stats(&$model, &$data = null) {
    if ($data === null) {
        $data = &$model->data;
    }
    if (empty($data[$model->alias]['id'])) {
        return $data;
    }
 
    if (isset($this->config[$model->alias])) {
        $fields = $this->config[$model->alias]['fields'];
 
        foreach ($fields as $column) {
            $count = $this->_countRecords($model, $data, $column);
            $column = $this->config[$model->alias]['prefix'] . $column;
 
            $data[$model->alias][$column] = $count;
        }
    }
 
    return $data;
}

Any finally, the _countRecords() method. it could be located in any other model, as well. To simplify again, everythings in the same place.

function _countRecords(&$model, &$data, $column) {
    $id = $data[$model->alias]['id'];
    $count = 0;
    switch ($column) {
        case 'images':
            return $model->Image->find('count', array('contain'=>array(), 'conditions'=>array('user_id'=>$id)));
        case 'friends':
            return $model->Friend->find('count', array('contain'=>array(), 'conditions'=>array('Role.user_id'=>$id)));
        ... # of course there could be more complex queries here
 }
    return $count;
}

Other callbacks

Of course you can build more complex behaviors, as well. Some that modifiy the record prior to saving it. Or doing something after saving a record.

Result

In the controller index (or view):

# We want the stats "images" and "friends"
$this->User->Behaviours->attach('MyStatistics', array('images', 'friends'));
$user = $this->User->find(all, array(...)); # paginated or limited to xx users!
pr($user);

Besides the real database fields the statistics now show up:

[0] => Array
        (
            [Gallery] => Array
                (
                    [id] => 4c979553-1500-4922-8866-07d452b0caef
                    [name] => 'Test'
                    [created] => 2010-09-20 19:09:39
                    [modified] => 2010-09-20 19:09:39
                    [stat_images] => 3
                    [stat_friends] => 1

Available is:

beforeFind()
beforeValidate()
beforeSave()
afterSave()
beforeDelete()
afterDelete()
onError()

Tip: Look at the cake core behaviors. You can learn a lot from those (as will all available code).

Naming conventions

Usually plugins are in adjective-form (“Accessable”, “Ratable”, …). Sometimes this sounds awkward: Plugin handling Points => “Pointable”? No – we can simply chose a noun then: “Points”. Note the plural form. Models are singular, so use a plural name your behavior. You never know when a new model “Point” is introduced. And we want no conflicts whatsoever.

 
No Comments

Posted in CakePHP

 

Helper? Component? Lib?

26 Jun

Some ideas what to use if you want to add some additional feature. Feel free to comment on this below.

Level of Independence

We need to ask ourselves if this feature needs to interact with other cake elements, like controller, some components, models, …

If it needs to save to the session, or if it needs some controller functionality, it will have to be a component. With initialize(&$controllerReference) and startup(&$controllerReference) this is very easy to accomplish.

But with Cake13 libs have been introduced. Not every piece of “controller” code necessarily needs to be component anymore. So if you retrieve an RSS feed or get the weather from a weather channel web service you could just make a clean and independent lib class. No need to extend the cake object or even pass the controller reference. Less memory and dependency is a good thing. And its easier to test, anyway.

Helpers are used if the result is in relation to the view – in other words if it gets printed/echoed right away. If you want to retrieve some web service information and save it to the database use a component instead.

Database related?

Often times we need to adjust some model data, we either use a component first and then pass it to the model or we use beforeValidate() and beforeSave() in the model. Same goes for the other direction (from model to controller): afterFind() or a component call afterwards. This is fine for custom changes. As soon as it could be something useful for several models, it might make sense to build a behavior. The code gets cleaner and your models more powerful.

Examples would be: “Last Editor/Last Change”, “Geocoding”, “Auto-Capitalize first letter of name/title”, “Format/Localize Date/Time”, …

Reducing code redundancy

Now that we have a vague understanding where to use what type of tool, we should think about cutting down the redundancy. Lets say we use the vendor class “phpThump”. We would have to write two wrappers. one for the helper (display images in the view) and one for the component (uploading images and resizing), maybe even for some behavior (validating + uploading + resizing). This wrapper handles default values from Configure::read() and other cake related settings. In this scenario we should build one single library in /libs, maybe called “phpthumb_lib.php”. Here we put our wrapper with our custom functions. Then we build a helper (view side) as well as a component or a behavior (controller side). They will import and use the library file. This is a cleaner approach because changes in the library class will be available in all files it is used in. Bonus: The main library file is easier to test. And therefore testing the other classes afterwards is easier, too.

Generally speaking, all web services should be some kind of library file (some would make a data source out of it). It doesn’t matter then if we use it in components or helpers, because it will fit either way. A helper in a controller, though, is not really a nice thing.

Thats – by the way – something i don’t like about the core helpers. They have functionality which is often useful in the controller (replacing a timestamp with localized date in session flash messages). So there should be a library called “Time” or whatever which is then extended or used in the helper. But, if needed, it can be used in the controller, as well. Same goes for “Text” and “Number”.

Plugin or not?

If your feature is not site-specific but very generic it probably makes sense to build a plugin. This way all other apps can easily use the same plugin. Additionally, test cases, assets etc are all combined in one folder – clean and extendable.

Examples: A bookmark helper usually can be used in several apps, whereas a custom helper with two functions for this one app will not be very useful anywhere else.

Usage of those resources

For components, add it to the controller in order to use it in the corresponding actions:

var $components = array('MyC'); # file is in /app/controllers/components named my_c.php
And in one of the controller’s actions:
$this->MyC->foo();

For helpers, add it to the controller in order to use it in the corresponding views:

var $helpers = array('MyH'); # file is in /app/views/helpers named my_h.php
And in one of the controller’s views:
$this->MyH->foo();

Libs can be used everywhere – include them at runtime:

App::import('Lib', 'MyL'); # file is in /app/libs named my_l.php
$myL = new MyL();
$myL->foo();
Possible in controllers, components, behaviors, view, helpers, elements and everything else.

Behaviors and other elements are used similar to the above.

For Plugins simply add the plugin name: “Text” becomes “Plugin.Text” etc

Hacks for special use cases

Sometimes we need to break MVC in order to avoid redundance (and stay DRY). A typical szenario is when we need a core helper in the controller (e.g. TextHelper). We need to manually include it then at runtime:

App::import('Helper', 'Text');
$text = new TextHelper();
$myText = $text->truncate($myText);
I want to emphasize that this should only be done if not possible any other way. You would also have to manually start and attach all helpers which are used inside the helper. Pretty annoying :)

I proposed a while ago that most of the core helpers should actually extend or at least use libs which contain the relevant functionality. This way we can use the libs in the controller and we can use their methods in the view via helper wrappers. But as of right now this is not yet planned for Cake2.0 or higher.

Update 2012-04-03

For Cake2.0 and above please see the updated article on this topic!

 
No Comments

Posted in CakePHP