If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
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>{{ pageData.title }}</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.
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.
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.
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.
Tip
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 %}
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 inhomepage.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.
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>{{ pageData.title }}</h1>
{# ... #}
{% endblock %}
And just like that, we have a real page!
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
.
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.
something Ive always been slightly confused about.
Why would you use a base and a layout? I dont see the benefit.
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 :)
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!
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!
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
// composer.json
{
"require": []
}
hi. When paste
and go to the http://localhost/twig/index.php/contact - we have an error.
need change
<h1>{{ pageTitle }}</h1>
to<h1>{{ pageData.title }}</h1>