Skip to main content

Creating a theme system with Twig and Symfony

What’s Twig

If you use Symfony you also probably use Twig (http://twig.sensiolabs.org/). It is a very flexible layout system and it makes it really easy to:

  • extend a base layout
  • include external sublayouts

Even though Twig requires you to learn a slightly different syntax, I feel it’s well worth it and I am completely happy with it. Let’s take a look on how to handle “themes” with Twig.

The theme system

Recently I was appointed with the task of creating multiple “themes” for the same application.

Simply put, the same codebase is going to be installed on different servers but each of this deploy should have a custom appearance, by means of a different set of Twig layouts.

Since most of the layouts are still the same and we don’t want code duplication, an additional requirement is that we still want to inherit from the same base theme.

In other words, from the dev point of view, only the customized layouts should be part of the custom theme.

Overwrite during deploy

One of my first ideas was to dynamically replace files in the default layout folder during the deploy.

Let’s assume you have the default layout folder in /app/Resources/views (oh BTW you should probably use a different path).

Now you create a separate folder for each different theme, e.g.:

  • /app/Resources/Themes/mycustomtheme1
  • /app/Resources/Themes/mycustomtheme2

and so on. The “Themes” subfolder is not required but it helps keeping things clean.

At this point, you add an optional parameter to the deploy system, so that – after copying everything on the server – it overwrites files in the default layout folder with files from the given custom theme folder.

Let’s see a very basic, dumb, dont-do-this-at-home sample deploy script to explain this. Imagine you execute this from the server where you want to use a custom “ygcoder” theme.

cd /project/root
git pull
if [ -z $1 ]; then
  cp /project/root/app/Resources/themes/$1/* /project/root/app/Resources/views/
fi

For those who’re not familiar with bash, this basically says:

  1. change to the project root folder
  2. pull files from the repository
  3. if there is an additional theme parameter, copy files from that theme folder into default theme folder, overwriting what’s already there

Bonus: we can improve it by removing all custom themes folder afterwards, so nothing is left on this server which should not be here:

cd /project/root
git pull
if [ -n $1 ]; then
    cp /project/root/app/Resources/themes/$1/* /project/root/app/Resources/views/
fi
rm -r /project/root/app/Resources/themes/

This solution could have been ok and I will probably use it again sooner or later (though the deploy script will probably be a little more advanced than this sample… 🙂 )

Twig path search hierarchy

In this case I was after a more integrated solution, possibly controlled by a configuration parameter.

Luckily I was able to do just that, using Twig internal features.

Twig searches for layouts in several paths, with a specific order. When the same file exists in different paths, the latest file found is used. If you don’t configure anything, by default these paths are searched:

  • src/bundle/Resources/views
  • app/Resources/views

You can add to this list by using the “paths” parameter in Twig configuration:

# Twig Configuration
twig:
(...)
paths:
'%kernel.root_dir%/Resources/themes/mycustomtheme': ~

and the files in this path will override all others.

Now, you can also use parameters in this path, e.g.

# Twig Configuration
twig:
(...)
paths:
'%kernel.root_dir%/Resources/themes/%theme_folder%': ~

which means that you can define the theme folder in your parameters.yml file, thus making it specific to each server and even to each environment if you want.

Conclusions

Most tools in the Symfony ecosystem are highly configurable; before implementing a custom system for advanced feature, it’s worth to look carefully in the documentation to see if there is something already that can help you out.

In this case, using themes in Twig was just one little configuration line away.