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.
Links by route name
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.
| Function | What 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.