Don’t (ab)use the AppHelper to extend the core helpers

Published on and tagged with cakephp  helper

Yesterday I stumbled upon a “tip” about using the AppHelper to extend the functionality of the core helpers. The author missed some functionality in the HtmlHelper, and as you shouldn’t modify any files in the “cake” folder, he created the AppHelper class (in app/app_helper.php) and added the new method there.

On a first glance this might look like it is a good idea: it works fine and you can access the method with $html->myMethod() in the views. Perfect. Only, on a conceptual level this approach is wrong (and CakePHP shouldn’t allow you to do such things).

To see why it is wrong, let’s do a little thought experiment with a less abstract example. We have a Dog. Dog is an Animal. A Dog can run and follow commands, we can say they are its “methods”. For some reason the guy who created the Dog forgot that a Dog can bark, and so our Dog doesn’t bark. But we want a barking Dog! We only have the power to modify Animal but not the Dog, so we add the bark feature to the Animal. Now our Dog barks. Fine. However, what’s that? A barking cow. Barking birds. Hm, something went wrong…

Ok, I think you see what the problem is ;-)

There are two possible solutions to help you to avoid this problem: you either use inheritance to create a new type of Dog or you use composition to create a new type of Animal that uses a Dog (weird example, I know).

Applied to the helper situation I mentioned at the beginning of this article, it means you either create your own helper which extends the HtmlHelper or you create your own helper which extends AppHelper and uses internally the HtmlHelper.

Happy baking :)

38 comments baked

  • Andreas

    There’s a third option:
    Copy the helper from /cake/libs/view/helpers to app/helper and modify whatever you want.
    (In case of updates you have to take care to update “your” helper too…)

  • cakebaker

    @Andreas: Thanks for your comment!

    Yes, you are right, that’s an additional option. But as you mention, it comes with a sync “problem” due to the code duplication. And so I wouldn’t recommend to use this option (unless you have a really good reason to do so).

  • Abhimanyu Grover

    I’ve always thought about this problem with helpers before as well. And I had already discussed it. I remember long time back somebody suggested me to use AppHelper for the same reason, and I would give him very similar weird example.

    Best idea has always been creating your own helper which extends from base helper. But I just dont like HtmlHelper to call SomePrivateNamePerPersonHtmlHelper… just an example. ;)

  • David Persson

    One idea would be to allow aliasing Helpers:

    class AppController ... {
        var $helpers = array('MyHtml' => 'Html', 'RequestHandler', ..);
        ....

    or
    class AppController ... {
        var $helpers = array('MyHtml' => array('alias' => 'Html'), 'RequestHandler', ..);
        ....

    This would solve the above problems but may create a new ones when e.g. MyHtml is aliased to Html in AppController and Html is included in a PostsController. This would create a conflict.

  • Derick

    I will think extending the default helpers seem like the best way to get things working. I have a habit to extend each and every CakePHP helper even if I have no need for it yet. E.g. I would have AppFormHelper extends FormHelper, AppHtmlHelper extends HtmlHelper and so on..

    Right at the start of each project, I will use $appForm->method() religiously. Reason is that I often find the need to override certain default actions either so I can fix a certain bug by overriding or simply add functionality to the default methods. Works really good in my opinion.

  • grigri

    This has bugged me for a while too.

    It’s frequently the form helper that I need to extend, but others too

    It would be great if the cake helpers were really named ‘BaseHtmlHelper’ with an empty class ‘HtmlHelper’ which is loaded from the cake folder if not present in the app folder.

    I’ve written a very hacky work-around which I’m ashamed to say that I use on a few live websites.

    code: http://bin.cakephp.org/saved/38630

  • Rafael Bandeira

    @Abhimanyu Grover: why not use only App{HelperName}Helper ?

    @DavidPerson && grigri : That’s definetly not a good idea… I had implemented it before, but in the view layer, ovewriting core helpers with ‘App’ prefixed ones, but it turns everything in a mess if you end up creating new methods or changing methods behaviors, because to maintain your code the person would have to know deeply inside every step, and would be very confused seeing $html->myCustomMethod(); but if you use $appHtml->myCustomMethod(); he would say, “hum gotta checkup on this custom class” – and he would be fine.

    @cakebaker: I gotta admit that I didn’t like your pragmatism saying: “on a conceptual level this approach is wrong (and CakePHP shouldn’t allow you to do such things)” – it has definetly no use to say something like this! Extending AppHelper can be usefull, I got my own – skinny – setuped AppHelper, that was even adopted in my company. It makes no harm by overriding Helper::addClass() and Helper::_parseAttributes() to allow params to be passed as arrays… so one could do:

    $html->tag('P', __($this->here . '/greetings', true), array('class' => array('message', 'greetings', 'foo', 'bar')));
    $html->link('www.orkut.com', array('rel' => array('service', 'external')));

    It also tries to use Helper::loadConfig() on beforeRender() based on helper name, and overrides it to use a default path deep inside the CONFIG.

    Not so problematic, huh? I think the case is that: "use, but actually, know how to use it..." - but that's just ask too much this days...

  • Abhimanyu Grover

    @Rafael Bandeira when you do like 2-3 projects a month, you wouldn’t want to have new helpers for every project, with different name.

    I think if I could get alias working, that’ll be best.

  • rafaelbandeira3

    @Abhimanyu Grover: I don’t get it… isn’t exactly this what I proposed?

  • nate

    There are some core helper behaviors (like URL construction) that are only possible to modify this way. The idea behind AppHelper is only to allow you to override elements of the base helper class or easily replace tag sets.

    Saying that “this approach is wrong (and CakePHP shouldn’t allow you to do such things)” simply shows your ignorance on the correct usage. As Rafael says, I guess proper education is just too much to ask.

  • Henrik

    Come on Nate, whats the hazzle and weird tone all about.. gd be polite this is a blog and he is right to have his meaning of things.. And if u dosent have anything nice to say then SHUT THE HELL UP!

    ps. dont care who u are

  • rafaelbandeira3

    @Henrik : hehhehhe… come on, no place for rudeness, you’re right Henrik but we have to coupe with different styles of speaking, Daniel is oftenlly polite and concise, nate is somewhat arrogant but I am too, and well, I believe he isn’t doing in a “mocking” way… he is just like this…

    @cakebaker : there’s another utility for the AppHelper, as nate said about tag sets – and I tried to post this afternoon but the annoying validation of your blog erased my comment as i gave my openId and only erased my email and website – looks like it prefers normal auth to openId, anyway… one can do the following to map it’s own methods and FormHelper::input() tags:

    class AppHelper extends Helper {
        var $map = array(
            // 'fieldType' => 'formTag'
            'datetime' => 'text'
            ,'enum(\'Y\', \'N\')' => 'checkbox'
            ,'time' => 'text'
        );
    }

    now, generated inputs for datetime fields won’t have cake’s bizarre selects as form tag, but a simple text input, and so on…
    and that’s a good use too!

  • Henrik

    @rafaelbandeira3 yeah but for some comments know i have seen the same style from him, its like a personal hertz against him..

  • cakebaker

    @all: Thanks for your comments!

    @Abhimanyu: Yes, finding a good name is not that easy :|

    @David: Yes, aliasing could solve that problem, but I’m not sure whether the additional complexity is desired. Personally I would prefer an approach as described by grigri.

    @Derick: Nice approach! grigri’s approach without hacky workaround ;-)

    @grigri: I like the idea, but your workaround is a bit too hacky for me ;-)

    @Rafael: Well, the core helpers all extend the base helper class (Helper), i.e. they expect that the methods defined there (the “API”) work in a certain way. But with the AppHelper it is now possible to break those “contracts”, I can do things there which could break the core helpers (without me being aware of it). And that’s wrong to me.

    Your example sounds to me like you are trying to solve a design “problem” at the code level. I don’t know how your implementation looks like (and whether I understood your example correctly), but if you allow the $class param of Helper::addClass() to be an array, then you are actually adding functionality to Helper::addClass() which should be in a addClasses() method. addClass() is about adding one class at a time, not about adding multiple classes at a time.

    @Henrik: That’s nate’s style…

    @nate: Hm, I doubt that there are no other ways. Let’s take the example of the URL construction you mention. Is this some functionality that must be in the Helper? No, I don’t think so. This functionality could be moved to a UrlBuilder class. Each helper which has to construct URLs can then use this class. And to make it more flexible (if really necessary), you can allow the framework user to set the UrlBuilder which the respective helper should use. Such a solution seems to me to be more elegant than the current solution.

    @Rafael: Functionality for a single method in one helper doesn’t belong to the AppHelper ;-) Probably a better solution would be to put it in a subclass of the FormHelper.

  • grigri

    @cakebaker: I agree, I’d like to see what Derick described in the core, as I described in my comment. My hacky workaround is just for the meantime!

  • rafaelbandeira3

    @cakebaker: You have already read about it on my blog, and might have noticed in my comment, that changing a method behavior without changing it’s name is not considered a good design by me, so overrided methods will keep follow the same behavior, they will only accept arrays, and when it happen, they’ll join them with spaces. Well actually it doesn’t make Helper::addClass() to accept more than one class, it checks if the value wich is going to receive the class is an array, and if it is, it will turn it in a string before trying to concatenate it.

    @cakebaker: Elegance is a matter of taste, for me it’s a way more complex and noise. AppHelper::url() allows you to specify persistent url params in application wide, without touch the core, wich is the main goal of every sw/framework/tool. Again your pragmatism would open the question: “so AppModel and AppController are meant for what?” – It’s all about RAD, yes they could make you create your own workaround for this – basic inheritance and we are just fine, but it’s all about making it nice and making it fast.

    @cakebaker: hum… that’s true, the $map thing…

    @grigri: “in the core”? what do ou mean? the actual implementation allows it with no problem, is the approach most of the people use – I do at least.

  • cakebaker

    @grigri: Yeah, that would be nice to have in the core!

    @Rafael: Hm, somehow I don’t see why you would want to change the behavior of the url method. Do you have any use case?

    AppController and AppModel are meant for functionality all controllers resp. all models have in common.

  • rafaelbandeira3

    @cakebaker @grigri: what is it? I think I didn’t understand… seriously… Derick’s suggestion is perfectly applicable, you anly have to extend a core helper and put it’s name on the controller:

    App::import('Helper', 'Html');
    class AppHtmlHelper extends HtmlHelper {
    	function tag($name, $text = null, $attributes = array(), $escape = false) {
    		if(is_array($text)) {
    			$text = join("\n", $text);
    		} elseif($text === '') {
    			$text = ' ';
    		}
    		if(is_string($attributes)) {
    			$attributes = array('class' => $attributes);
    		}		
    		if(strpos($name, '.') !== false) {
    			preg_match_all('/\.([a-z0-9\_\-]+)/i', $name, $classes);
    			$name = str_replace($classes[0], '', $name);
    			if(!isset($attributes['class'])) {
    				$attributes['class'] = array();
    			} elseif(!is_array($attributes['class'])) {
    				$attributes['class'] = array($attributes['class']);
    			}
    			$attributes['class'] = join(' ', array_merge($attributes['class'], $classes[1]));
    		}
    		if(strpos($name, '#') !== false) {
    			preg_match('/#([a-z0-9\_\-]+)/i', $name, $id);
    			$name = str_replace($id[0], '', $name);
    			$attributes['id'] = $id[1];
    		}
    		return parent::tag(strtolower($name), $text, $attributes, $escape);
    	}
    }
    // in the controller
    class AppController extends Controller {
        var $helpers = array('AppHtml');
    }
    //in the view
    echo $appHtml->tag('P.greetings', __('hello dear programmer!'));

    what’s needed to implement?

    @cakebaker : here’s is something I did once

    function url($url, $full = false) {
        if (!empty($this->_persistentParams)) {
            if (is_array($url)) {
                $url['?'] = $this->_persistentParams;
            } else {
                $url .= Router::queryString($this->persistentParams);
            }
        }
        return parent::url($url);
    }

    Another use case I had was to make it auto set extra params as named params, to make them be parsed with no problems:

    function url($url, $full = false) {
        if (is_array($url)) {
            $Router=& Router::getInstance();
            $default = $Router->named['rules'] + array(
                'controller' => true, 'action' => true, '?' => true, 'ext' => true
            );
            $extra = array_diff_key($url, $default);
            if (!empty($extra)) {
                Router::connectNamed($extra, array('default' => true, 'reset' => false, 'greedy' => true));
            }
        }
        return parent::url($url);
    }

    and last but not least, I used it to take action based on ‘model’ param passed on url array – but I was trying to reinvent the weel creating a more generic way of mapping actions based on models, as some models were not directly associated to any controller

    Cheers daniel

  • cakebaker

    @Rafael: I think there is a bit of confusion ;-) We are talking about grigri’s idea (Derick’s approach is the implementation of it without touching the core). The idea is to rename the core helpers to BaseXXXHelper (e.g. BaseHtmlHelper) and to have empty helpers (e.g. HtmlHelper) extending those helpers. If you want to “extend” the HtmlHelper, you simply create a HtmlHelper in the helpers folder of your app, and make the change there. So you can always use $html->methodName() for html-related stuff.

    Regarding your use cases, I think they show a design smell. In principle you want to override Router::url(), but as it is not thought to be extended you have to use an alternative approach, which is to override the wrapper method for Router::url() in the helper.

  • rafaelbandeira3

    @cakebaker, @grigri: oh, ok… clarified… not sure I would like it very much, but that’s nice.

    @cakebaker: no. I didn’t want to extend/override Router::url(), I didn’t wan’t this behaviors to be present on controller’s url dealings, nor on default url handlings… I wanted to use some per helper persistent params on urls, so that urls spitted by that helper had those params, and so on….
    AppHelper is to helpers as AppModel is to models, just a way to override some basic configs, add basic functionalities, and add generic approaches.
    Now giving a thought on RAD… Yes, Helper::$map should be only on FormHelper, Helper::value() should be more generic and less FormHelper focused… and many other stuff… but keeping it on AppHelper makes it so easy to use scaffolded views! You can easily override some basic scaffold behaviors and appearance, in one place. Yes, it’s not bullet proof design… but remember: “CakePHP : The RAD framework” ;-)

  • rafaelbandeira3

    oh, I forgot to ask excuses for all the noisy code… I misunderstood grigri’s approach, and misbehaved… sorry …

  • Robin

    “composition to create a new type of Animal that uses a Dog”

    Like a parasite?

    Say you have a parasite that causes your dog to wag it’s tail faster than it normally does, the parasite doesn’t override the original wagging of the tail, it just allows the Dog to wag it’s tail faster by using a different action (left to right, as oppose to up and down) :)

  • rafaelbandeira3

    @cakebaker : I’m here to ask your opinion, as we talked about core level and app level classes… what do you think about the class prefixing system of CI (http://tinyurl.com/5fzxad)?

    I don’t think it’s a great idea, as I said that it makes lots of confusion to see undocumented methods – customizeds – being called by objects that should be from the core… and welll you already know…

    so what say you? regards

  • cakebaker

    @Rafael: The “CI”-prefix makes sense to me whereas the “MY” prefix looks a bit strange. However, as you can change the prefix, you can probably set it to an empty string if you want to have “normal” class names.

  • rafaelbandeira3

    @cakebaker: Naah… I was actually asking about what do you think of the concept behind the implementation … The concept of having overridable core classes. i.e. Making cake load my own Set class, or making it load my own Router class instead of the core one.

  • cakebaker

    @Rafael: Sorry, I misunderstood your comment.

    The concept makes sense to me, and it is the reason why there is the concept of interfaces. If you program against interfaces, it no longer matters what implementation you use.

    Of course, it doesn’t make sense to define an interface for every framework class, as not every class has to be replaceable. To take your example, it probably doesn’t make much sense to have a Set interface, as the current Set class is a utility class. On the other hand it could be useful to have a Router interface, so you could implement your own routing strategy.

    Hope that answers your question :)

  • John

    Hi Guys

    I’ve been reading this post and all the comments with a great deal of interest, as you’ve all seem to have put a great deal of thought into this.

    For my current project I’m really getting to start from bare bones and as such am trying to take the opportunity to make something as perfect as possible, and have have manged to map out exactly what I want to do – so anyway this afternoon I get to the next step and something I assumed would be pretty straight forward turns out to be a lot more complex than I imagined so I’m looking for a bit of advice / clarification and I would really appreciate any thoughts or pointers.

    What I was seeking to do was to overwrite some of the core helper methods, but I don’t want to alter any core files, or copy the core helper over to app/helpers and modify it, as suggested by Andreas in the 1st comment – due to the sync problem. Now obviously I could either extend one of the core helpers or create a brand new helper to use – which both seem valid solutions.

    BUT I don’t want to do it this way because I really don’t want to have to (for instance) replace $form->input(‘somefield’) with say $myform->input(‘somefield’) – I know that seems picky but it as you move between projects it is another thing to change isn’t it? For my current set of projects I’m using a custom bake templates, so it would be very easy to update those to generate everything with $myform-> or $myhtml-> but thats not always the case. So do you think this is possible or I am I just barking up the wrong tree?

    By way of actual examples two of the things I want to do are as follows:

    1. Override the default $html->link method to and connect it to Auth – so for example somebody using an application would not see links to actions they did not have the permissions to actually carry out (avoiding somebody clicking a link and then being told they don’t have permission). This isn’t really hard to do, but I was thinking it would make a good shortcut / safety net if I could apply this checking to any link without having to change any exiting code in the views.

    2. I don’t want multiple selects – I just want checkboxes – is there some way I can make this the default behaviour (without editing views or core files)?

    Cheers John.

  • John

    @Rafael – btw forgot to say thanks for showing how you can put your own $map array in AppHelper.

    Cheers

  • cakebaker

    @John: Thanks for your comment!

    Well, an idea to preserve the $form->xy() scheme in the view is to create a custom view class (i.e. a class which extends View) and to override the _loadHelpers() method. In this method you then replace the instance of the FormHelper with an instance of your custom helper (which extends the FormHelper).

    The code might look like:

    class ExampleView extends View {
        public function &_loadHelpers(&$loaded, $helpers, $parent = null) {
            $loaded = parent::_loadHelpers($loaded, $helpers, $parent);
    		
            if (isset($loaded['Html'])) {
                App::import('Helper', 'Test');
                $loaded['Html'] = new TestHelper();
            }
    		
            return $loaded;
        }
    }

    You probably have to copy some code from View::_loadHelpers() to make it work to 100%.

    Hope that helps!

  • An idea for hacking core helpers - cakebaker

    […] a recent comment by John I got asked whether there is a way to override a method of a core helper like […]

  • John

    @cakebaker

    Hi – thanks – I really wouldn’t have thought of that idea.

    I’m going to go away and do a bit of experimenting – I’ll try and post something back if / when I get a nice simple solution.

    Over the last few days I’ve left the $link-> issue on hold, but in terms of the other things having a checkbox multiple I’ve just been using custom bake files e.g.

    if(!empty($associations['hasAndBelongsToMany'])) {
    				foreach ($associations['hasAndBelongsToMany'] as $assocName => $assocData) {
    					echo "\t\techo \$form->input('{$assocName}', array('multiple' => 'checkbox'));\n";
    				}
    			}

    – not really as elegant but as long you take your bake files with you from project to project It’ll do – but at work were putting a lot of time building our new system on top of cake so not really a problem !

    Cheers

  • cakebaker

    @John: See my latest article: An idea for hacking core helpers.

    Hope it helps!

  • Abhimanyu Grover

    @Daniel I finally had a chance to review your idea:
    http://cakebaker.42dh.com/2008/11/07/an-idea-for-hacking-core-helpers/

    I’m still not convinced if its the best way available.

  • cakebaker

    @Abhimanyu: Well, it is one possible way, and not necessarily the best. It’s possible there are better solutions, depending on your requirements.

    For example, the approach described by Derick in a earlier comment is in my opinion better than the idea I described in the linked article, though it comes with the “disadvantage” that you have to use a different name to access the helper. If it’s ok for you to use, for example, $appForm instead of $form throughout your application, then go for it!

  • CakePHP AJAX Forms | dMarkWeb-Web Design Company

    […] extend core helpers like this, but use a similar method similar to this (comment by grigri): http://cakebaker.42dh.com/2008/10/18/dont-abuse-the-apphelper-to-extend-the-core-helpers/#comment-11… ) and overwrite create() method like this: PLAIN TEXT […]

  • Fixing and improving the TextHelper | CakePHP Articles

    […] Note that you can not use the helper with adding “Text2″ to the $helpers array inside a controller. Unfortunately you cannot globally specify to always use the Text2Helper instead of the TextHelper! The only thing you can do is to add the following to your view: $this-Text = $this-Text2; I would like to see a functionality to alias helpers! This would make the job easier and prettier. (http://cakebaker.42dh.com/2008/10/18/dont-abuse-the-apphelper-to-extend-the-core-helpers/#comment-11…) […]

  • powtac

    In Cake 2.1:

    Create your OwnHelper which extends HtmlHelper, in AppController specify

    $helpers = array(‘Html’ => array(‘className’ => ‘OwnHelper’));

  • cakebaker

    @powtac: Thanks for the hint, didn’t know this is possible.

Bake a comment




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

© daniel hofstetter. Licensed under a Creative Commons License