Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Layout and links in templates

All pages of a site share some parts: the document head, the menu, the footer. To avoid repeating them in every template, the shared markup is moved into a layout — a base template with "windows" that individual pages fill in.

The layout

Create the file templates/layout.twig:

{# templates/layout.twig #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{% block title %}Blog{% endblock %}</title>
</head>
<body>
    <main>{% block content %}{% endblock %}</main>
</body>
</html>

A block is a named window. The title block has default content, the content block is empty.

A page based on the layout

Rewrite the home page template:

{# templates/home.twig #}
{% extends 'layout.twig' %}
 
{% block title %}{{ title }}{% endblock %}
 
{% block content %}
    <h1>{{ title }}</h1>
    <p>The site runs on Slim.</p>
{% endblock %}

The extends line says which layout to take, and the blocks fill its windows. The action does not change: it still renders home.twig. Open the home page and view its source — it now contains the full document markup.

The slim/twig-view package adds the url_for() function to templates — it builds an address from a route name, like urlFor() in code. Add a menu to the layout before the main tag:

{# templates/layout.twig #}
<nav>
    <a href="{{ url_for('home') }}">Home</a>
    <a href="{{ url_for('posts.index') }}">Posts</a>
</nav>

The menu appears on every page that extends the layout. Placeholder values go in the second argument:

{# templates/posts/index.twig #}
{% extends 'layout.twig' %}
 
{% block content %}
    <h1>Posts</h1>
 
    {% for post in posts %}
        <p><a href="{{ url_for('posts.show', {id: post.id}) }}">{{ post.title }}</a></p>
    {% endfor %}
{% endblock %}

Each title becomes a link like /posts/1.

FunctionWhat it returns
url_for('posts.show', {id: 5})The address for a route name
full_url_for('posts.show', {id: 5})The same with the site name
current_url()The address of the current page
is_current_url('posts.index')Whether the current address matches the route

Shared data

A value needed in every template, such as the site name from the settings, is added once — where Twig is created:

// config/container.php
use Psr\Container\ContainerInterface;
use Slim\Views\Twig;
 
Twig::class => function (ContainerInterface $c) {
    $twig = Twig::create(__DIR__ . '/../templates', ['cache' => false]);
 
    $twig->getEnvironment()->addGlobal('site_name', $c->get('settings')['site_name']);
 
    return $twig;
},

Now the site_name variable is available in any template: replace the word "Blog" in the layout's title block with {{ site_name }}.

PHP templates

If a separate template language is not wanted, there is the slim/php-view package: its templates are ordinary PHP files. Output there must be escaped by hand with htmlspecialchars(), which is why the course uses Twig.

Common mistake

In a template with extends, anything written outside blocks causes a Twig error: a child template may only fill the layout's blocks. If a page is empty or fails after adding extends, check that all markup is inside block content.

Thanks for reading!

Donate to the site — we work hard for you.