An idea for loading helpers in the view

Published on and tagged with cakephp  helper  ideas  view

In the comments to my previous article, “An alternative to requestAction“, it was mentioned that it is illogical to define helpers in the controller but to use them in views. (Thanks to Rafael Bandeira and Tarique Sani)

That’s indeed a bit illogical if you think about it. It would be more logical to define the helpers directly where they are needed, in the views.

After some experimenting and digging in the source of the View class I found a relatively clean way to accomplish that by using a custom view class.

Ok, here is what I did. First I created a custom view class called “AppView” in app/views/app.php. It contains one method to load the helpers, which is later used in the views. I also had to override the constructor to support “global” helpers (i.e. they can be used in all views). So instead of defining them in the AppController they are now defined in the $helpers array of this custom view. FormHelper, HtmlHelper, and SessionHelper do not have to be defined, they are automatically loaded by CakePHP.

// app/views/app.php
class AppView extends View {
    public $helpers = array('Navigation');
	
    public function __construct(&$controller, $register = true) {
        $globalHelpers = $this->helpers;
        parent::__construct($controller, $register);
        $this->loadHelpers(array_merge($this->helpers, $globalHelpers));
    }
	
    public function loadHelpers($helperNames) {
        $helpers = $this->_loadHelpers($this->loaded, $helperNames);

        foreach ($helpers as $helper) {
            $name = str_replace('Helper', '', get_class($helper));
            $this->$name = $helper;
        }
    }
}

To use this custom view class, I have to tell the AppController to do so by setting the $view property:

// app/app_controller.php
class AppController extends Controller {
    public $view = 'App';
    ...
}

And finally I can load and use the desired helpers in the views like:

<?php 
$this->loadHelpers(array('CamelCase')); 
echo $this->CamelCase->getSomething();
echo $this->Form->create('MyModel');
...
?>

As you can see from the example above, the usage of the helpers is different from what you are used to. Instead of $form->create() I used $this->Form->create(). It’s more to write, but I think it is more consistent with the rest of the framework.

What do you think about this approach?

Update (2008-08-28): Thanks to Rafael Bandeira’s idea it is no longer necessary to call the loadHelpers() method in your views, just copy the following method to the AppView and it will auto-load the helpers when they are needed:

public function __get($property) {
    $this->loadHelpers(array($property));
    return $this->{$property};
}

23 comments baked

  • Jonathan Snook

    I definitely like this approach. More and more, I’ve been moving where I set page and layout variables in the view. For example, I no longer set pageTitle in the controller, but try to set it in the view. I think having an easy way to load helpers in the view makes the most sense.

  • David Golding

    In some instances, I’ve had to really think about how to name my custom helper classes because of conflicts in the view with other arrays and variables. With this method, having the helper object accessible under $this->HelperName improves semantic issues, I think. Like how models classes are called in the controller, etc. At least, I think this is one benefit of the method you’re proposing here. I agree with Jonathan that a little more could be done to move view methods into the view, but this always has to be done with caution; beginners could really misunderstand and begin doing too much logic in the view (many do that already, I suppose).

  • Neil Crookes

    The other thing to consider, I guess, is who is gonna be working on your views. If its you, cool, but if its some muppet-markup-monkey(tm) that just adds wrapper divs, spans and CSS class names around your functionality to make it look pretty, you may not want too much “code” in there that might confuse them. Personally, I like it though.

    P.S. No offence muppet-markup-monkeys.

  • rafaelbandeira3

    @Neil Crookes : lol … not so friend of webdesign ppl… too bad ;-)

    @cakebaker : nice… but besides it’s too long, it gives me the feelling that i’m work in old php code, where you can always see those annoying and ugly includes… one nice thing would be this, firstly View should be overloaded, I don’t know if it is already… might be… anyway – to the code :

    function get__($property)
    {
         $this->loadHelpers(array($property));
         return $this->{$property};
    }
  • Tarique Sani

    Daniel – you are my hero for the day!!! This is a prime example of BDD – not Behaviour but Blog Driven Development ;)

    Will play around with this today and see how it goes.

  • cakebaker

    @all: Thanks for your comments!

    @Jonathan: Yes, setting the page title in the view makes sense.

    @David: Hm, what do you mean with “move view methods into the view”?

    @Neil: I like the term “muppet-markup-monkeys” ;-) Yes, it’s probably confusing for them, but with a little bit of teaching it shouldn’t be a problem.

  • cakebaker

    @rafael: You mean the magic __get() method? Yes, that could work, I have to test it. Thanks for the tip!

    @Tarique: You are welcome :)

  • rafaelbandeira3

    @cakebaker : that’s what I meant but before writing I took a quick look to their Overloadable class and it used {$method}__()… and as said I didn’t tested… but __call worked for me already… but is used as call__() in Model class… gotta read about it…

  • Daniel

    I’ve been using this on a new project and I love it. It makes much more sense in my head. Thanks!

  • Joe Siao

    Hi,

    your code will help lots of programmers. thank you for sharing!

    Joe(phpcurious)

  • cakebaker

    @Rafael: The Overloadable class cannot be used for this purpose as it is not the base class of View… But using the __get() method works like a charm (at least with PHP5) :) Thanks for the tip!

    @Daniel, Joe: You are welcome!

  • rafaelbandeira3

    Hey Daniel… just thought about it while writing for my blog and them I got this little buggy feelin and realized that this method would break lots of things as it just ignores helper’s beforeRender and, depending on the case, beforeLayout callbacks… well it’s real that core helpers won’t suffer, but you should definetly implement support for that ;-) – beforeRender is specially important as it is the first moment that your helper will have access to it’s own helpers and them can take action and define options based on them.

  • cakebaker

    @Rafael: Yes, you are right with the before- and afterRender() callback methods, those methods are not called (before- and afterLayout() seem to work fine). But especially for beforeRender() I don’t see a solution at the moment, as you only know what helpers are used when you effectively render the respective view…

  • nao

    Hi,

    Someone find solution for afterRender() calling when loading helpers in the view ?

  • cakebaker

    @nao: In the loadHelpers() method you have to add the following statement:
    $this->helpers = $helperNames;
    And then you have to override the _render() method (I’m not sure whether this works 100%):

    public function _render($___viewFn, $___dataForView, $loadHelpers = true, $cached = false) {
        parent::_render($___viewFn, $___dataForView, $loadHelpers, $cached);
        foreach ($this->helpers as $helper) {
            $this->{$helper}->afterRender();
        }
    }

    Hope it helps!

  • leo

    Sorry to revive an old conversation, but wouldn’t it be better if the __get method checked if the property was there, and only loaded the helpers if it wasn’t found? Seems something like:

    function get__($property) {
         if(!property_exists($this, $property)) {
            $this->loadHelpers(array($property));
         }
         return $this->{$property};
    }

    would be slightly better, no? I’m a little bit new at PHP OOP, so forgive me if I’m wrong.

  • cakebaker

    @leo: Thanks for your comment!

    Yes, you can do it in that way and it is probably a better solution than what I have shown (though in View::_loadHelpers(), called by loadHelpers(), there is also a check to prevent loading the same helper twice).

  • mark

    an old discussion – but somewhat still a hot topic these days.
    especially as the helpers are now properly attached to the view class.
    $this->Helper->method() is now the default way to use helpers (in cake1.3)

    so did someone already open a ticket for this nice and clean idea to load helpers where they are needed (lazy) instead of loading them far away in the controller methods?

    for backwords comp. you could just “enhance” the current view – loading all controller helpers + the ability to load the rest via
    $this->loadHelpers(‘MyHelper’);
    and
    $this->loadHelpers(array(‘MyHelper’, …));

    currently loadHelpers() does not seem to be used in that way.

  • cakebaker

    @mark: Thanks for your comment!

    According to CakePHP’s roadmap lazy loading of helpers (and models & components) is planned for CakePHP 2.0, though I don’t know how they intend to implement it.

  • keymaster

    CakeBaker,

    Just read this post, almost 2.5 years after you wrote it, and it is still totally relevant and forward thinking.

    Hopefully soon you’ll consider blogging again on a regular basis about cakephp.

    All the best.

  • cakebaker

    @keymaster: Thanks for your comment!

    Well, I probably have to disappoint you… I’m no longer using CakePHP very often, so there isn’t much to write about. Plus CakePHP feels “dated” to me with no PHP5.3 version on the horizon…

    Anyway, I hope to get back to blog more regularly, I have become too lazy on the blogging front ;-)

  • mark

    well, cake2 is just around the corner
    including php5.3 ;) so, whats the problem?^^

  • cakebaker

    @mark: Thanks for your comment!

    Well, unless the plans have changed Cake2 is targeted for PHP 5.2.x (see the announcement of cake2-dev). While it will work on PHP 5.3, it can’t use any of the new features available in PHP 5.3…

    And this leads to the same “problem” we have today with CakePHP’s PHP4-compatibility: it limits the possibilities of what the framework devs can accomplish. I don’t know how often I wished the devs would have used PHP5 features like interfaces and abstract classes. It would have made my life much easier. And I think the same is true for PHP 5.3: you will wish the framework devs would have used some of its features.

    I hope this explains what the problem is ;-)

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License