Buy

Using a Layout: Template Inheritance

Using a Layout: Template Inheritance

If we view the HTML source of our project so far, we’ll see just the HTML tags and printed variables from our homepage.twig file. So far, there’s no HTML layout, head or body tags. but since our project has been ugly long enough, let’s add these.

To add the layout, there’s nothing technically wrong with including it right in homepage.twig. This is perfectly straightforward and has nothing to do with Twig. But let’s setup a second page: /contact. I’ll tweak our setup script to make this work:

// index.php
// ...

case '/contact':
    echo $twig->render('contact.twig', array(
        'pageData' => array(
            'title' => 'Find us in the south pole!',
        )
    ));
    break;

Create the new contact.twig file in the templates/ directory and add /contact to the end of your URL:

{# templates/contact.twig #}

<h1>{{ pageTitle }}</h1>

<p>Make some penguin noises, we're listening...</p>
http://localhost/twig/index.php/contact

Ok, so now that we have two pages, if we put the layout code in homepage.twig, it wouldn’t be available on the contact page. Since that would be a bummer, let’s learn how to use a layout across multiple templates.

Template Inheritance

The key is template inheritance, which is a lot less scary than it sounds. Before we get into the details, let’s create a new Twig template that will hold the base layout. I’ll paste in some starting HTML, which you can get from the code download. I’m including some CSS and JS files, but this is pretty boring overall.

Actually, there’s only one small piece of Twig code: a block tag. This defines a block called “body” and its purpose will make more sense in a moment:

{# templates/layout.twig #}
<!DOCTYPE html>
<html lang="en">
{# ... more stuff ... #}

{% block body %}{% endblock %}

{# ... the rest ... #}

Start by adding another Twig tag via the “do something” syntax called extends. After extends, type layout.twig.

{# templates/homepage.twig #}
{% extends 'layout.twig' %}

{# ... the rest of the template #}

This tells Twig that we want to be “dressed” in layout.twig, or said differently, that we want to use layout.twig as the layout for the homepage. We also need to surround all of our content with a {% block body %} tag and a closing {% endblock %}.

This looks just like what we have in the layout file, except with our content inside.

{# templates/homepage.twig #}
{% extends 'layout.twig' %}

{% block body %}
    <div class="hero-unit">
        {# ... #}
    </div>

    <div class="row">
        {# ... #}
    </div>
{% endblock %}

When we refresh the page, it works! By viewing the source, we can see the HTML layout with the content of the homepage.twig file right in the middle where we expect it.

Teamwork: extends and block

This works because of a great team effort between the extends and block tags. When we use extends, it says that this template should be placed inside of layout.twig. But Twig is a bit dumb: it doesn’t really know where to put the content from the homepage. The block tag fixes that. By putting a block body in the layout and a block body around our homepage content, Twig knows exactly where the content should live in the layout.

Using Multiple Blocks

We can even use multiple blocks. Let’s add a title block to the layout:

{# templates/layout.twig #}

<title>{% block title %}{% endblock %}</title>

{# ... #}

If we refresh, the title is blank. But now, we can add a title block to our homepage:

{# templates/homepage.twig #}
{% extends 'layout.twig' %}

{% block title %}50% off of Bow Ties{% endblock %}

{% block body %}
    {# ... #}
{% endblock %}

The order of the blocks doesn’t matter, whatever lives in the title block will be placed in the title block of the layout. The same is true of any block. Even the names title and body aren’t special. If we rename body to content, we just need to also rename the block in any other templates.

Note

If you want to add to the content of the parent block instead of completely replacing it, use the parent function:

{% block title %}
    Contact us | {{ parent() }}
{% endblock %}

Common Mistake: Content outside of a Block

Let’s try to write something outside of a block in homepage.twig:

{# templates/homepage.twig #}
{% extends 'layout.twig' %}

Where should this text be placed in the layout?

{% block title %}50% off of Bow Ties{% endblock %}

{% block body %}
    {# ... #}
{% endblock %}

When we refresh, we see a nasty error:

Uncaught exception ‘Twig_Error_Syntax’ with message ‘A template that extends another one cannot have a body in “homepage.twig” at line 4.’

Twig knows that we want it to take the content from the body tag of homepage and put it where the body tag is in the layout. But when it sees this new text, it doesn’t know what to do with that or where to put it! The error is saying that if we extend another template, everything must live in a block so that Twig knows where to put that content in the layout.

Adding the Layout to the Contact Page

Our homepage looks great, but the contact page still needs a layout. To give it one, just add the extends tag, then surround the content with a block called body, since that’s the name of the block in our layout:

{# templates/contact.twig #}
{% extends 'layout.twig' %}

{% block body %}
    <h1>{{ pageTitle }}</h1>

    {# ... #}
{% endblock %}

And just like that, we have a real page!

Default Content in a block

Of course the contact page doesn’t have a title. We could add a title block just like we did on the homepage. Instead, in the layout, we can put some content inside of the title block:

{# templates/layout.twig #}
{# ... #}

<title>{% block title %}Penguin Swag{% endblock %}</title>

This becomes the default page title, which is used on the contact page since we don’t have a title block in contact.twig. But when we go to the homepage, we still see the title from the title block in homepage.twig.

Template Inheritance, a Summary!

Phew! Let’s review everything we just learned:

  • Initially, a Twig template doesn’t render anything other than what’s actually in that template file.
  • To use a layout, we use the extends tag at the top of the template and then surround all of our content in a block. Because the template and the layout have blocks with the same names, Twig takes the content from each block and puts it into the layout to build the whole page.
  • In the layout, a block can also have some default content. Because contact.twig doesn’t have a title block, the default text is used.

Leave a comment!

  • 2016-01-13 DevDonkey

    ah cool many thanks. I hate it too, being a minimalist-purist whenever possible. It just seems to create more work in debugging. I just wanted to check as I'm helping a few Padawan learners get started and didn't want to tell them the wrong stuff.

    thanks again and also thanks for the great work.

    Matt

  • 2016-01-13 weaverryan

    Ah, it's cool - it will hopefully be a good explanation to *someone* who is wondering all of that :).

    In short: there is no point to the extra "middle" level "layout.twig" file. Early in Symfony, we tended to over-engineer things, and it was really common to (A) have multiple bundles and (B) to give each bundle its own "layout.html.twig" file that all the templates in that bundle would extend. Why? Because if you add that "middle" layer, it allows you to have different "sections" if your site that have a *slightly* different layout. For example, imagine your site has a top nav. But in one section of your site, the top nav is gone (or drastically different). In this case, you create a "layout.twig" that extends "base.twig" and overrides the top nav to remove/change it. Now, you can have 5 different templates from this section extend "layout.twig" to get that new "section-specific" layout.

    So, the extra "middle layer" has a good purpose. But it's not needed in most cases and you tend to see people add it when it's not needed at all. I hate that extra layer :).

    Cheers!

  • 2016-01-13 DevDonkey

    many thanks for that. I'm not sure I worded my question correctly. I fully see why you would extend base.twig but...

    I see everywhere, a base.twig file, then a layout.twig, which extends the base. Then all other templates extend the layout. this is what I dont really understand, it feels like making things more complicated for no reason.

    Sorry to make you write all that!

  • 2016-01-13 weaverryan

    Hey!

    If you *don't* extend a base layout but want a page with full HTML markup (e.g. <head>,<body>, etc) - then you can certainly just put all of that markup right inside your one template (e.g. homepage.twig). But as soon as you want to share that base layout between two templates (like contact.twig), then using a base layout allows you to do that without duplication.

    However, this all assumes that you're using Twig in a system where your job in Twig is to generate the *full* page. That's not always the case. For example, in Drupal, when you use Twig for theming, you're just building little "pieces" of the page - the full layout is constructed elsewhere. In this case, it does *not* make sense to extend a base layout like this. However, even in that case, extends has some really cool uses! For example, imagine you're theming two different node types that are *almost* equivalent, but not exactly the same. Instead of duplicating all of your markup between the 2 templates, you could create a base template with all of the shared parts, surround the part that needs to change in a block, then extend that base template from both children templates and override the block. In this case, the "base template" is not a "base layout" like in this tutorial (i.e. it does not have the base <body> & <head> tags), but it serves the same purpose: to allow multiple templates to share some structure without duplication.

    Phew! Does that help? Interesting question :)

  • 2016-01-13 DevDonkey

    something Ive always been slightly confused about.

    Why would you use a base and a layout? I dont see the benefit.