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!