Using Twig with Fat-Free Framework: Why and How

Having used Fat-Free Framework 2.x for a little while now, I’m still generally quite happy with it… aside from one thing.

The built-in templating engine’s syntax doesn’t offer many advantages over raw PHP, it’s more verbose in certain common cases, and, as Fabien Potencier wrote when he took over maintainership for Twig, template languages have evolved a lot since PHP began its life as one in 1995. …so I decided to use Twig.

Why Twig?

It does help that I also work in Django sometimes but it’s not as big a factor as you might think. The main reason is that Twig is both the fastest, lightest templating engine I know (benchmark) and also one of the most featureful. Both good reasons to use FatFree and Twig together.

Here are a few of the reasons I switched:

  • Fully extensible
  • Auto-escaping for cleaner, safer code and less stressful coding.
  • Let’s me choose my preferred mix of template inheritance and template includes.
  • Supports macros for tidying up repeated code snippets like form fields.
  • Has clever constructs like for/else to clean up common cases like “List results or display a ‘no results’ message”
  • Syntax is easier for me to read and write in data-heavy templates with many nullable fields.
  • Generally more amenable to the kinds of factoring-out of duplicated boilerplate that I want to do.

Setup Instructions

Unsurprisingly, I couldn’t get integration with F3::get() as concisely as in F3 templates, but I did pretty well, all things considered.

The simplest way I’ve found to set this up is to add something along these lines to the top of your index.php:

require_once __DIR__ . '/lib/Twig/Autoloader.php';
Twig_Autoloader::register();

$twig_loader = new Twig_Loader_Filesystem(__DIR__ . '/templates');
$twig = new Twig_Environment($twig_loader, array(
    'cache' => __DIR__ . '/twig_cache',
    'auto_reload' => true,
));

$twig->addFilter('f3', new Twig_Filter_Function('F3::get'));
$twig->addGlobal('is_ajax', Web::isajax());

This will set up the following:

Template Location
Templates will be stored in a folder named templates so they don’t conflict with any templates you continue to use with the default F3 templates. (Assuming you’re still using the default ui folder you probably kept from the sample code)
Template Caching
Twig templates will be cached in a folder named twig_cache to avoid conflicts and the cache will be automatically regenerated when the templates change
F3::get()
The f3 filter gives you 'foo'|f3 as a slightly inelegant but not overly verbose equivalent to @foo in templates. Things like 'result'|f3.title will work as desired.
Also, if you’re only using F3::get to expose things to the templates, you can use Twig globals, which let you use {{ foo }} instead of {{ @foo }}. The syntax for doing so is as follows:

$twig->addGlobal('foo', $foo);
is_ajax
This global allows you to do the switching between full templating and minimal responses for AJAX requests entirely in the templates using this snippet of code (source):

{% extends is_ajax ? "base_ajax.html" : "base.html" %}
Rendering
global $twig;
echo $twig->render('page.html', array('bar' => $bar));

Important: Compatibility Fix

The one caveat is that there’s a little incompatibility between the current versions of Twig and Axon. When using {% if my_hydrated_axon.my_null_valued_field %}, you’ll get

Undefined method Axon->my_null_valued_field()

The problem is:

  1. Twig’s dot operator uses isset() to determine whether a property exists, then fails over to assuming you wanted a method call with no arguments.
  2. Axon.__isset() uses isset() on its internal array of fields.
  3. isset() on an array returns false if the field exists but has a value of NULL.
  4. Even with strict_variables set to false, current Twig has a bug that makes it error out rather than producing an empty value when accessing axon properties.

You have two options. First, you can use my_hydrated_axon.cast.my_null_valued_field everywhere (Axon.cast() returns an ordinary associative array, which doesn’t have this problem) or you can make a small patch to  FatFree’s lib/db.php and change this…

function __isset($field) {
 return isset($this->fields[$field]) || isset($this->adhoc[$field]);
}

…to this…

function __isset($field) {
 return (is_array($this->fields) && array_key_exists($field, $this->fields)) ||
  (is_array($this->adhoc) && array_key_exists($field, $this->adhoc));
}

The only caveat with this approach is that, in your PHP code, if (!$axon->property) won’t work properly.

CC BY-SA 4.0 Using Twig with Fat-Free Framework: Why and How by Stephan Sokolow is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

This entry was posted in Geek Stuff. Bookmark the permalink.

11 Responses to Using Twig with Fat-Free Framework: Why and How

  1. Renato says:

    Hey, i did not get how to do the render part, could you explain a little more?

    Thanks in advance.

    BTW, I’m starting to use fat-free right now and i’m loving it (:

    • What, specifically, are you having trouble understanding?

      I’d be happy to go into more detail, but I’m so used to working with this sort of thing that I’m not sure where someone might have trouble.

  2. Eman says:

    G’Day fellow coder Stephan

    Just found your blog while glooking for
    F3 diety framework examples

    And follow’d to your great Gender-Bending Fiction Index

    Nowadays, today, on which framework is
    your Index build on ?
    Looks like it’s made with Yii,
    (exact. same icons & lists styles)
    or build on F3?

    If so, do you have any links to
    libraries to handle clever lists
    to help me kick-start with F3

    Thanks in adv ance for your help Stephan!

    Eman @ Switzerland

    • It’s still based on F3. The icons are from the Silk icon theme as linked in the site footer but I’m not sure which lists you’re talking about.

      The sidebar is just an adjusted jQuery UI Tabs widget and the “Newest Entries” and search result lists were things I cobbled together myself using CSS.

      I’d be happy to help you, but you’ll have to clarify what you mean by “handle clever lists”.

      • Eman says:

        Hi Stephan and Thanks for replying

        Actually, there are a bunch of great-looking PHP frameworks here around.

        Project is to develop a similar Index as yours

        But among all these easy-fast-great looking framework,
        it’s a pitty examples are so sparse…

        Reinventing the wheel,
        again & again …

        Any links to F3 backoffice samples are
        GREATLY welcomed!!!

        Thanks in advance for your precious time
        Eman @ Switzerland

        • There’s still a lot of stuff in the GBIndex codebase that predates my switch to F3 and those parts can be pretty messy. (and I haven’t yet got an admin UI that works with the current SQL schema version)

          I’m planning to open-source the site, but not until I can get that stuff out of the way.

          However, if you’ve got a BitBucket account or an e-mail address which can take a 5MiB attachment, I don’t mind sending you the F3-based codebase in its current state.

  3. Have you managed to figure out how to get this working with named routes (https://github.com/bcosca/fatfree#named-routes) yet? – Rob

    • Unfortunately, my life has been very busy in my final year of university and I’ve had to put the F3-based project on hold. However, worse case scenario, you should just be able to write a simple Twig filter which wraps the F3 code and then call it something like this:

      {{ 'foo'|route }}

  4. David says:

    I know that this is an old post, but I was just trying to use the info for adding Twig to F3 and it no longer works. Between the newer version of F3 and Twig on 2.0 now, this is not working. Is there any way to update it to work?

    • Unfortunately, I haven’t worked with F3 or Twig in a long time, so I can’t give any specific advice.

      That said, I’d be very surprised if it wasn’t simple for someone to make them work together, just as a side-effect of how components are put together.

      Can you share more details on what “not working” looks like for you? I might be able to at least give a bit of advice then.

Leave a Reply to David Cancel reply

Your email address will not be published. Required fields are marked *

By submitting a comment here you grant this site a perpetual license to reproduce your words and name/web site in attribution under the same terms as the associated post.

All comments are moderated. If your comment is generic enough to apply to any post, it will be assumed to be spam. Borderline comments will have their URL field erased before being approved.