Code snippets for symfony 1.x

Navigation

One template, multiple themes

Some applications want to offer various look-and-feels for each page. The online documentation already explains how to change the CSS from the server, and a bit of JavaScript will allow you to do it from the client side.

But sometimes this is not enough, and the need arises to have several sets of templates for each action. Something like:

mymodule/
  templates/
    indexSuccess.php    # Template for index action, default theme
    theme1/
      indexSuccess.php  # Template for index action, theme1
    theme2/
      indexSuccess.php  # Template for index action, theme2

Thanks to the MVC architecture, this is very easy to do.

We'll suppose that theme is an attribute of the sfUser object. It contains a simple string. The way to determine the preferred theme for a given user will be left to your sagacity, as well as the way to extend the sfUser class to deliver this attribute. The interesting thing is to modify the view class to have it use the theme when the time comes to look for templates and layouts.

Create a myView.class.php file in the lib/ directory of your application, and write in:

<?php
 
class myView extends sfPHPView
{
  public function configure()
  {
    parent::configure();
 
    // Grab the theme from the user (of from anywhere else)
    $theme = $this->getContext()->getUser()->getTheme();
 
    // If there is a theme and if the theme feature is enabled
    if($theme && sfConfig::get('app_theme'))
    {
      // Look for templates in a $theme/ subdirectory of the usual template location
      if (is_readable($this->getDirectory().'/'.$theme.'/'.$this->getTemplate()))
      {
        $this->setDirectory($this->getDirectory().'/'.$theme);
      }
 
      // Look for a layout in a $theme/ subdirectory of the usual layout location
      if (is_readable($this->getDecoratorDirectory().'/'.$theme.'/'.$this->getDecoratorTemplate()))
      {
        $this->setDecoratorDirectory($this->getDecoratorDirectory().'/'.$theme);
      }
    }
  }
 
}

To force symfony to use this view class instead of the default sfPHPView, create a module.yml in the application's config/ directory, and write in it:

all:
  view_class: my

The new view will look for themes only if you enabled the feature in your app.yml:

all:
  theme: on

Clear the cache, and the theme feature is ready.

It works like this: when a user has a defined theme, symfony will look for templates and layouts in a subdirectory named by this theme. For instance, if a user has a foobar theme, and that it requests the mymodule/myaction action, symfony will look for the template in:

apps/myapp/modules/mymodule/templates/foobar/myActionSuccess.php

and for the layout in:

apps/myapp/templates/foobar/layout.php

The beauty of this modification is that if the themed template doesn't exist, symfony will use the normal template as a fallback.

It must be pretty easy to package this into a plugin, so if you feel like doing it...

by Francois Zaninotto on 2006-11-21, tagged template  theme  view 

Comments on this snippet

gravatar icon
#1 Colin Williams on 2006-11-24 at 06:43

This should definitely be in the main book, when you can find time to add it in. This seriously rocks.

gravatar icon
#2 Pierre Minnieur on 2006-12-06 at 03:00

That snippet is cool. I modified that a bit to have real themes (decorator, action templates and stylesheets).

Take a look at it here: http://www.entropy-project.com/browser/trunk/lib/view/enThemePHPView.class.php

gravatar icon
#3 John Diamond on 2007-06-11 at 11:35

This is definitely a cool snippet, but it doesn't seem to support partials. Anyone got any idea how to accomplish that?

gravatar icon
#4 Rob Henley on 2009-03-03 at 06:46

For anyone looking to support partials the following worked for me (on a Symfony 1.0.19 app):

http://trac.symfony-project.org/changeset/11482

gravatar icon
#5 acetous on 2011-07-08 at 11:34

I have developed a solution where the themes are stored in one place. This makes it easier to support 3rd party themes. My solution is shipped with an easy setup and full support for Symfony 1.4 (including partials ;) ).

You can download and contribute at https://github.com/acetous/Symfony-Themed

gravatar icon
#6 Khan Klatt on 2011-09-20 at 07:04

I am working to update a legacy 1.0 codebase to 1.1 (baby steps, 1.2 and 1.3 forthcoming) and a block of code that was using this snippet was failing. In the interest of helping others encountering the same problem, I offer this solution.

Problem: When accessing the legacy app, an error was thrown "Call to undefined method [blah]View::getContext."

The stacktrace showed the problem (2nd error in the dev view): at sfView->_call('getContext', array()) in SF_ROOT_DIR/lib/psView.class.php line 26 ... ...
$in_canvas = $this->getContext()->getUser()->getAttribute('in_canvas', false); ...

(this was clearly adapted from the code in this snippet, $theme = $this->getContext()->getUser()->getTheme();)

Solution: Replacing this with

$in_canvas = sfContext::getInstance()-&gt;getUser()-&gt;getAttribute(&#039;in_canvas&#039;, false);
 

fixed the problem. Thanks to the post by weaverryan at http://oldforum.symfony-project.org/index.php/t/577/.

gravatar icon
#7 Khan Klatt on 2011-09-20 at 07:06

For some reason the line of code got mangled by the comment system.

Perhaps it will work better without indentation:

$in_canvas = sfContext::getInstance()->getUser()->getAttribute('in_canvas', false);