Sometimes i’m not happy with Yii - especially if something is hardcoded to a core class which you can not override. For example i want to use Bootstrap CSS in my project and therefore i need my labels to wrap up my checkboxes or radio buttons. But CHtml is not capable of this. So suddenly rendering a simple form can become a pain as a lot of useful helpers can’t be used. How nice would it be if i could have my own custom CHtml class?
Such a solution should meet some requirements:
[b] 1. It should not alter framework files
- It should be easy to upgrade to another Yii version[/b]
To solve this, i used this approach:
1. Override CHtml
CHtml is a core class which gets autoloaded by Yii. So in order to prevent Yii from using its own class, i just have to load my custom CHtml class before Yii does. Fortunately this is very simple and Yii even provides a way how to do is. The key is to add your custom class to the import configuration:
<?php
'import'=>array(
'application.components.CHtml',
...
So now you can have your custom CHtml class in components/CHtml.php. So far so good. But …
2. Inherit from original CHtml
I don’t want to copy the original CHtml.php from Yii and alter it. I would have to do the same changes again, each time a new version is released and it’s very likely that i miss to redo some of my changes. So i want some kind of inheritance. My first idea was to use namespaces inside components/CHtml.php like this:
<?php
/* BELOW CODE DOES NOT WORK: */
// Load CHtml into yii namespace to have it under yii\Chtml
namespace yii {
include(Yii::getPathOfAlias('system.web.helpers.CHtml').'.php');
}
// Create CHtml in global namespace
namespace yii {
class CHtml extends yii\CHtml { /* override things here */ }
}
But this does not work! When you include a file from inside a namespace, PHP will not use the same namespace for the included file. Or to put it in other words: Every PHP file must define its own namespace. If it does not, then it will always be importet to the global namespace. Yii’s files do not declare a namespace, so every code inside them will be in the global namespace as soon as you include it.
So i ended up with an acceptable workaround to reach my above goals. The pattern goes like this:
<?php
namespace yii {
use \Yii as Yii; // Required for every core class used inside the code below
class CHtml
{
/* copy the original CHtml code here. If Yii gets upgraded, replace the code in here */
}
}
namespace {
class CHtml extends yii\CHtml
{
/* override CHtml methods here */
}
}
The solution is not perfect, but at least it works and still i can upgrade without too much fuss.
If someone has a better idea for how to solve (2) i’m glad to hear it. Maybe we can find the perfect pattern and have a nice wiki article for it.