Yeah 🙂 I was waiting for this day for quite a long time. And pretty happy that my proposal from last year is finally in the live code.
Cake2.1 is now fully, really and completely MVC – and officially 200% more awesome, of course!
MCV?
Model - View - Controller.
Mainly the basic principle of many modern frameworks that helps to code in a clean way. Details here
What’s the big deal?
It had been bugging me for years that we had to do something like this in our controllers and models:
# hold on tight - bad part - start (already cake 2.0 style):
App::uses('TimeHelper', 'View/Helper');
App::uses('View', 'View');
$Time = new TimeHelper(new View(null));
$message = 'Your flier expired on ' . $Time->nice($flier['Flier']['expires']); # flash message or email title etc
# bad part - end
We had to import a View and the Helper only to keep it dry and reuse the methods we already have.
With Cake2.1 we can now do:
App::uses('CakeTime', 'Utility');
$message = 'Your flier expired on ' . CakeTime::nice($flier['Flier']['expires']);
Same thing with NumberHelper and CakeNumber:
App::uses('CakeNumber', 'Utility');
$filesizeString = CakeNumber::toReadableSize($filesize);
instead of the helper overhead similar to above.
Last but not least the TextHelper methods have also been moved to the String class:
$shortext = String::truncate($longtext);
Only the autoLink() methods still remain in the helper. Mainly because they contain markup transformation (adding HTML) which belongs to the View layer.
And for the View layer itself nothing changes. The helpers work as before, only they now use the lib classes internally.
Going one step further {#aliasing}
You can even pass in your own engines (lib classes) for the view helpers.
For example by passing it into the helper settings:
public $helpers = array(..., 'Time'=>array('engine'=>'Tools.TimeLib'));
I use this approach:
class TimeExtHelper extends TimeHelper {
public function __construct($View = null, $settings = array()) {
$settings = Set::merge(array('engine' => 'Tools.TimeLib'), $settings); // instead of core CakeTime lib
parent::__construct($View, $settings);
}
}
It will then use /APP/Plugin/Tools/Lib/Utility/TimeLib.php
instead.
Then I alias it back to "Time" (same for Number and Text) and everything is like it was before, only that it uses my enhanced lib classes:
public $helpers = array(..., 'Time'=>array('className'=>'Tools.TimeExt'));
This way you can even enhance the helper itself (add some more functionality etc).
But if…
…if you still need to use helpers in the controller or model context?
Well, then you are obviously still doing something wrong.
There is absolutely no need for it anymore. And now no excuses anymore, either 🙂
You should move your code to the Lib folder as a package. Then use it in your controller as Lib class and in your View call the helper which then calls the Lib class. This way you keep it dry and simple. Just look how it is done in those classes above in the core.
Example: MathHelper.php
We might need its basic methods in the controller somewhere. The helper only adds markup or View related goodies.
So we have a MathLib class in APP/Lib/Math
(the Math
package can also be any other name you want).
And a MathHelper class in APP/View/Helper
which uses the MathLib.
Either the lazy way:
public function __call($method, $params) {
return call_user_func_array(array($this->Math, $method), $params);
}
Or the verbose way for every single method:
public function calculate($value) {
return $this->MathLib->calculate($value);
}
The latter has the advantage of type-hinting in your IDE.
And in the controller we do (let’s say we put the code in a "Tools" plugin):
App::uses('MathLib', 'Tools.Math'); # would then be in /APP/Plugin/Tools/Lib/Math/
$newValue = MathLib::calculate($value);
And in the view we can still use the helper as always:
$this->loadHelper('Tools.Math'); # would be in /APP/Plugin/Tools/View/Helper/
$newValue = $this->Math->calculate($value);
PS: I like to suffix my lib classes with Lib
to avoid naming conflicts with Models or other cakephp or php classes. This is not required, of course. But I would recommend it as long we cannot use namespaces (which will be Cake3.0 and is still quite a few miles away).
Happy coding!