| 1 |
= ysfDimensionsPlugin = |
|---|
| 2 |
|
|---|
| 3 |
ysfDimensionsPlugin allows you to customize the behavior of your symfony application based on any runtim factors. You can adjust the configuration, template selection, and action behavior based on a combination of dimensions chosen by you. For instance, you can have a different navigation structure based on the country of the user or a cobrand, or a different logo URL based on the current skin. These factors that affect the behavior (in this case, the country of the user and the skin selected) are the dimensions, and you may define as many as you wish. |
|---|
| 4 |
|
|---|
| 5 |
Dimensions work by adding another level of cascading configuration, as well as by altering the location of the template or altering the name of the action class. All of this is made easy due to the forward thinking flexibility of symfony. |
|---|
| 6 |
|
|---|
| 7 |
== Getting Started == |
|---|
| 8 |
|
|---|
| 9 |
'''For setting up ysfDimensionsPlugin for use with symfony 1.0 projects, please see http://trac.symfony-project.com/browser/plugins/ysfDimensionsPlugin/branches/1.0/README.''' |
|---|
| 10 |
|
|---|
| 11 |
=== Requirements === |
|---|
| 12 |
|
|---|
| 13 |
'''ysfDimensionsPlugin uses APC by default and it should be enabled in the command line.''' |
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
=== Installation === |
|---|
| 17 |
|
|---|
| 18 |
'''1. Install the plugin via the symfony cli''' |
|---|
| 19 |
|
|---|
| 20 |
{{{ |
|---|
| 21 |
symfony plugin:install ysfDimensionsPlugin |
|---|
| 22 |
}}} |
|---|
| 23 |
|
|---|
| 24 |
'''2. Clear symfony cache''' |
|---|
| 25 |
|
|---|
| 26 |
{{{ |
|---|
| 27 |
symfony cache:clear |
|---|
| 28 |
}}} |
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
=== Configuration === |
|---|
| 32 |
|
|---|
| 33 |
'''1. Configure your project and application configuration to use this plugin''' |
|---|
| 34 |
|
|---|
| 35 |
Open project/config/ProjectConfiguration.class.php in your favorite text editor, then: |
|---|
| 36 |
|
|---|
| 37 |
Add: |
|---|
| 38 |
{{{ |
|---|
| 39 |
// manually require class since not part of symfony core |
|---|
| 40 |
require_once(dirname(__FILE__).'/../plugins/ysfDimensionsPlugin/lib/config/ysfProjectConfiguration.class.php'); |
|---|
| 41 |
}}} |
|---|
| 42 |
|
|---|
| 43 |
Then change the parent class from sfProjectConfiguration to '''ysfProjectConfiguration'''. |
|---|
| 44 |
|
|---|
| 45 |
{{{ |
|---|
| 46 |
// manually require class since not part of symfony core |
|---|
| 47 |
require_once(dirname(__FILE__).'/../../../plugins/ysfDimensionsPlugin/lib/config/ysfApplicationConfiguration.class.php'); |
|---|
| 48 |
}}} |
|---|
| 49 |
|
|---|
| 50 |
Edit apps/example/config/exampleConfiguration.class.php changing the parent class from sfApplicationConfiguration to '''ysfApplicationConfiguration'''. |
|---|
| 51 |
|
|---|
| 52 |
Now that the dimensions hooks are in place, you need to configure your application, by following the steps below: |
|---|
| 53 |
|
|---|
| 54 |
'''2. Defining Available Dimensions''' |
|---|
| 55 |
|
|---|
| 56 |
First we need to define the different dimensions a page may have and define all allowed values. |
|---|
| 57 |
The ysfDimensionsPlugin is configurable via the dimensions.yml configuration file in project/config/dimensions.yml. |
|---|
| 58 |
|
|---|
| 59 |
{{{ |
|---|
| 60 |
allowed: |
|---|
| 61 |
culture: [en, fr, it, de] |
|---|
| 62 |
theme: [classic, corporate] |
|---|
| 63 |
}}} |
|---|
| 64 |
|
|---|
| 65 |
'''3. Setting the Current Dimension''' |
|---|
| 66 |
|
|---|
| 67 |
Edit apps/example/config/exampleConfiguration.class.php |
|---|
| 68 |
{{{ |
|---|
| 69 |
/** |
|---|
| 70 |
* Configure the symfony application |
|---|
| 71 |
*/ |
|---|
| 72 |
public function configure() |
|---|
| 73 |
{ |
|---|
| 74 |
/** |
|---|
| 75 |
* |
|---|
| 76 |
* Logic for determining dimensions |
|---|
| 77 |
* |
|---|
| 78 |
* Remember this file is called very early on in the symfony bootstrap process |
|---|
| 79 |
* so there are no symfony utilities available, |
|---|
| 80 |
* |
|---|
| 81 |
*/ |
|---|
| 82 |
|
|---|
| 83 |
// for now - static values |
|---|
| 84 |
$culture = (!empty($_REQUEST['culture'])) ? $_REQUEST['culture'] : 'en'; |
|---|
| 85 |
$theme = (!empty($_REQUEST['theme'])) ? $_REQUEST['theme'] : 'classic'; |
|---|
| 86 |
|
|---|
| 87 |
// setup dimensions before calling parent::configure(); |
|---|
| 88 |
$this->setDimension(array('culture' => $culture, 'theme' => $theme)); |
|---|
| 89 |
|
|---|
| 90 |
parent::configure(); |
|---|
| 91 |
} |
|---|
| 92 |
}}} |
|---|
| 93 |
|
|---|
| 94 |
For now we're just setting the theme and culture based on a request parameter. |
|---|
| 95 |
You will likely not want to base the dimension off raw user input, but of |
|---|
| 96 |
something else, like the host name or stored user preferences. |
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 |
== Examples == |
|---|
| 100 |
|
|---|
| 101 |
So what's really going on here? We need to vary the site behavior based upon |
|---|
| 102 |
various parameters. Above, we're using the culture of the request as the |
|---|
| 103 |
parameter, but we can use other settings like theme or colo as parameters as |
|---|
| 104 |
well. We can then specify several aspects of the symfony experience by means |
|---|
| 105 |
of these dimensions. For instance, we can specify different site logos |
|---|
| 106 |
depending on the theme, or different servers depending on the culture, or |
|---|
| 107 |
different page content based on the culture (for example, language translations |
|---|
| 108 |
of a page). We can also inherit functionality between settings, so if culture |
|---|
| 109 |
=> fr is almost the same as the generic configuration, it can inherit the |
|---|
| 110 |
settings and just specify the changes it needs. We will go more into how this |
|---|
| 111 |
works later, first lets explain the dimension configuration. |
|---|
| 112 |
|
|---|
| 113 |
{{{ |
|---|
| 114 |
allowed: |
|---|
| 115 |
culture: [en, fr, it, de] |
|---|
| 116 |
theme: [classic, corporate] |
|---|
| 117 |
}}} |
|---|
| 118 |
|
|---|
| 119 |
Our dimensions.yml file describes two levels of dimensions. The first named |
|---|
| 120 |
'culture' that has four possible values: 'en, fr, it, de', and a second |
|---|
| 121 |
dimension named 'theme' that has two possible values 'classic' and 'corporate'. |
|---|
| 122 |
|
|---|
| 123 |
Any request can be for any combination of the dimensions culture and theme. |
|---|
| 124 |
Here we have configured eight posssible unique dimensions (en_classic, |
|---|
| 125 |
en_corporate, fr_classic, fr_corporate, it_classic, it_corporate, de_classic, |
|---|
| 126 |
de_corporate). Dimensions function by looking in special directories for |
|---|
| 127 |
configuration or template files. For example, if the current dimension was set |
|---|
| 128 |
as culture => en, theme => classic, then symfony would form the dimension |
|---|
| 129 |
string 'en_classic'. When symfony looks for a configuration or template file it |
|---|
| 130 |
will insert a new order of precedence: first it will load files from |
|---|
| 131 |
'en', then 'corporate', and then 'en_corporate'. Regardless of the dimension |
|---|
| 132 |
all of these will look in the generic (non dimension-specific) location for |
|---|
| 133 |
settings last. This means that if you don't want to specialize your behavior |
|---|
| 134 |
at all, you can put settings in the same locations in symfony as you did before |
|---|
| 135 |
using the ysfDimensionsPlugin. |
|---|
| 136 |
|
|---|
| 137 |
There are three parts of the system that are configured by these settings: |
|---|
| 138 |
configuration, templates, and actions. All dimensions-specific files live |
|---|
| 139 |
underneath a dimension-specific directory. For app-level configuration, the |
|---|
| 140 |
directory is in apps/<app>/modules/[config, templates]/<dimension>/*. For |
|---|
| 141 |
module-level configuration, it's in apps/<app>/modules/<module>/[actions, |
|---|
| 142 |
config, templates, validate]/<dimension>/* |
|---|
| 143 |
|
|---|
| 144 |
Let's talk a little about each of these. |
|---|
| 145 |
|
|---|
| 146 |
|
|---|
| 147 |
=== Extending Configuration === |
|---|
| 148 |
|
|---|
| 149 |
symfony makes great use of configuration files to set up your web site. These |
|---|
| 150 |
files live in various config/ directories. The settings largely end up in the |
|---|
| 151 |
sfConfig object where you can fetch them from your application. |
|---|
| 152 |
|
|---|
| 153 |
We handle the settings.yml, app.yml, routing.yml, databases.yml, module.yml, view.yml, |
|---|
| 154 |
security.yml, mailer.yml, cache.yml, i18n.yml, and validate/*.yml as well. |
|---|
| 155 |
|
|---|
| 156 |
You should put the dimension-specific files in a dimension-specific |
|---|
| 157 |
subdirectory. For instance, we would put the 'app.yml' for the dimension |
|---|
| 158 |
'culture => en, theme => corporate' in apps/frontend/config/en_corporate/app.yml, |
|---|
| 159 |
and module.yml in apps/frontend/modules/demo/config/en_corporate/module.yml. |
|---|
| 160 |
|
|---|
| 161 |
As with all of these settings, configurations are searched for in the order |
|---|
| 162 |
specified by the original dimensions.yml file. Any setting not specified in |
|---|
| 163 |
one file will cause us to look in the subsequent files down the list. The order |
|---|
| 164 |
is determined by applying a cartesian iteration, thus culture => en, theme => |
|---|
| 165 |
corporate ends up as a dimension 'en_corporate'. The search order will be from |
|---|
| 166 |
most specific to generic: 'en_corporate', 'corporate', 'en', generic. If |
|---|
| 167 |
multiple configuration files are found in multiple paths, they will be merged |
|---|
| 168 |
with the most specific values having precedence. |
|---|
| 169 |
|
|---|
| 170 |
Let's try this. Let's create a setting 'site' that we'll echo for a new 'test' |
|---|
| 171 |
action. Put this into apps/frontend/modules/demo/templates/testSuccess.php |
|---|
| 172 |
(the generic location): |
|---|
| 173 |
|
|---|
| 174 |
{{{ |
|---|
| 175 |
<h1>demo:test</h1> |
|---|
| 176 |
<p>We are in the test template now. Site setting is <?php echo $site ?>.</p> |
|---|
| 177 |
}}} |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
Now add the action: |
|---|
| 181 |
|
|---|
| 182 |
apps/frontend/modules/demo/actions/actions.class.php |
|---|
| 183 |
|
|---|
| 184 |
|
|---|
| 185 |
{{{ |
|---|
| 186 |
<?php |
|---|
| 187 |
class demoActions extends sfAction |
|---|
| 188 |
{ |
|---|
| 189 |
public function executeTest() |
|---|
| 190 |
{ |
|---|
| 191 |
$this->site = sfConfig::get('app_site'); |
|---|
| 192 |
} |
|---|
| 193 |
} |
|---|
| 194 |
?> |
|---|
| 195 |
}}} |
|---|
| 196 |
|
|---|
| 197 |
|
|---|
| 198 |
Now we just need to establish the setting itself. This is an app-level setting |
|---|
| 199 |
(app_) so it belongs in the app.yml file. Let's create a base value. Create |
|---|
| 200 |
apps/frontend/config/app.yml: |
|---|
| 201 |
|
|---|
| 202 |
|
|---|
| 203 |
{{{ |
|---|
| 204 |
all: |
|---|
| 205 |
site: base |
|---|
| 206 |
}}} |
|---|
| 207 |
|
|---|
| 208 |
|
|---|
| 209 |
Now clear the cache (for now you'll need to do this whenever you add new action |
|---|
| 210 |
code) and load the page at http://example.com/demo/test (change the |
|---|
| 211 |
hostname for your box). You'll see the base setting. Now hit |
|---|
| 212 |
http://example.com/demo/test?culture=fr. The setting is still base even |
|---|
| 213 |
though you're in the fr culture. Let's make a fr-specific setting. Create |
|---|
| 214 |
apps/frontend/config/fr/app.yml: |
|---|
| 215 |
|
|---|
| 216 |
|
|---|
| 217 |
{{{ |
|---|
| 218 |
all: |
|---|
| 219 |
site: fr |
|---|
| 220 |
}}} |
|---|
| 221 |
|
|---|
| 222 |
|
|---|
| 223 |
Now clear your configuration cache (symfony cc) and reload the last page. What about |
|---|
| 224 |
a theme? Try this URL: |
|---|
| 225 |
http://example.com/demo/test?culture=fr&theme=corporate. It still says fr |
|---|
| 226 |
because the fr_corporate site inherits the fr settings. Let's override this |
|---|
| 227 |
value again. Create apps/frontend/config/fr_corporate/app.yml: |
|---|
| 228 |
|
|---|
| 229 |
|
|---|
| 230 |
{{{ |
|---|
| 231 |
all: |
|---|
| 232 |
site: fr_corporate |
|---|
| 233 |
}}} |
|---|
| 234 |
|
|---|
| 235 |
|
|---|
| 236 |
Clear the cache, and reload. There you go. If you go back to the previous |
|---|
| 237 |
URLs pages you'll see that they each show the appropriate value, overriding the |
|---|
| 238 |
base where necessary. |
|---|
| 239 |
|
|---|
| 240 |
For all configuration files, symfony will load all of the dimension-specific configuration files given |
|---|
| 241 |
the specialization path specified by dimensions.yml (e.g. en_corporate, corporate, en, |
|---|
| 242 |
generic) with the earlier files' settings overriding the later ones. Otherwise, the |
|---|
| 243 |
configurations work just as they did before the dimension-specific specialization. See |
|---|
| 244 |
[http://www.symfony-project.org/book/trunk/05-Configuring-Symfony the symfony book] for |
|---|
| 245 |
more information about these files. |
|---|
| 246 |
|
|---|
| 247 |
=== Extending Views === |
|---|
| 248 |
|
|---|
| 249 |
Dimension-specific templates are placed in the templates/<dimension>/ |
|---|
| 250 |
directory. They are searched for in the order specified by the dimensions.yml |
|---|
| 251 |
file, and the first found of the appropriate name is used. |
|---|
| 252 |
|
|---|
| 253 |
Let's add some dimension-specific templates for our new action. |
|---|
| 254 |
apps/frontend/modules/demo/templates/fr/testSuccess.php: |
|---|
| 255 |
|
|---|
| 256 |
|
|---|
| 257 |
{{{ |
|---|
| 258 |
<h1>demo:test for fr</h1> |
|---|
| 259 |
<p>We are in the test/france template now. Site setting is <?php echo $site ?>.</p> |
|---|
| 260 |
}}} |
|---|
| 261 |
|
|---|
| 262 |
apps/frontend/modules/demo/templates/fr_corporate/templates/testSuccess.php: |
|---|
| 263 |
|
|---|
| 264 |
|
|---|
| 265 |
{{{ |
|---|
| 266 |
<h1>demo:test for fr/corporate</h1> |
|---|
| 267 |
<p>We are in the test/france/corporate template now. Site setting is <?php echo $site ?>.</p> |
|---|
| 268 |
}}} |
|---|
| 269 |
|
|---|
| 270 |
|
|---|
| 271 |
Now try the URLs from above, for base, fr, and fr_corporate settings. You |
|---|
| 272 |
should see all three templates, each showing the setting from the previous |
|---|
| 273 |
section as well. |
|---|
| 274 |
|
|---|
| 275 |
=== Extending Controllers === |
|---|
| 276 |
|
|---|
| 277 |
Dimension-specific actions are placed in the actions/<dimension>/ directory. |
|---|
| 278 |
Since actions are specified in classes and classes need to have unique names, |
|---|
| 279 |
you need to append the standard class names with the dimension. So |
|---|
| 280 |
demoActions for the en_corporate configuration would be |
|---|
| 281 |
demoActions_en_corporate. This class would go in the |
|---|
| 282 |
actions/en_corporate/actions.class.php file. The same is true for individual |
|---|
| 283 |
action classes as well. fooAction_en_corporate class would go in the |
|---|
| 284 |
actions/en_corporate/actions/fooAction.class.php file. Actions can inherit |
|---|
| 285 |
functionality from other actions and don't need to 'require' them. You could |
|---|
| 286 |
have '''demoActions_en_corporate extends demoActions''' if you want to |
|---|
| 287 |
share some behavior between all sites but override a specific action for |
|---|
| 288 |
'en_corporate' dimension. |
|---|
| 289 |
|
|---|
| 290 |
For example, let's just override the action for the all sites with the |
|---|
| 291 |
dimension 'fr_corporate'. |
|---|
| 292 |
|
|---|
| 293 |
apps/frontend/modules/demo/actions/fr_corporate/actions.class.php: |
|---|
| 294 |
|
|---|
| 295 |
{{{ |
|---|
| 296 |
<?php |
|---|
| 297 |
|
|---|
| 298 |
require_once(dirname(__FILE__).'/../actions.class.php'); |
|---|
| 299 |
class demoActions extends baseDemoActions |
|---|
| 300 |
{ |
|---|
| 301 |
public function executeTest() |
|---|
| 302 |
{ |
|---|
| 303 |
parent::executeTest(); |
|---|
| 304 |
$this->site = 'override('.$this->site.')'; |
|---|
| 305 |
} |
|---|
| 306 |
} |
|---|
| 307 |
?> |
|---|
| 308 |
}}} |
|---|
| 309 |
|
|---|
| 310 |
|
|---|
| 311 |
You'll need to clear the cache. Now reload the 'fr_corporate' url from above |
|---|
| 312 |
and you will see that we've overridden the site value on this page, while |
|---|
| 313 |
inheriting the behavior of the base action. |
|---|
| 314 |
|
|---|
| 315 |
|
|---|
| 316 |
== Performance == |
|---|
| 317 |
|
|---|
| 318 |
Installing the ysfDimensionsPlugin adds little overhead to your project. The only overhead |
|---|
| 319 |
comes from looking for the same configuration files in multiple places. This is minimized |
|---|
| 320 |
significantly as the configuration files are still compiled and cached. |
|---|
| 321 |
|
|---|
| 322 |
|
|---|
| 323 |
== Tests == |
|---|
| 324 |
|
|---|
| 325 |
For a complete example of how to test applications with dimensions, |
|---|
| 326 |
please see the functional test project in plugins/ysfDimensionPlugin/test/fixtures/project. |
|---|
| 327 |
|
|---|
| 328 |
|
|---|
| 329 |
== License == |
|---|
| 330 |
|
|---|
| 331 |
Please see the packaged LICENSE file for the details of the MIT license. |
|---|
| 332 |
|
|---|
| 333 |
|
|---|
| 334 |
== Todo == |
|---|
| 335 |
|
|---|
| 336 |
* Namespacing for controller class names (extend execution filter) |
|---|
| 337 |
* A tutorial binding change culture event to culture dimension |
|---|