Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Twig templates

So far HTML was assembled from strings inside actions. That is awkward and dangerous: it is easy to forget to escape someone else's text. A template is a separate file with markup that the action passes data into. Slim has no template engine; Twig is the usual choice.

Installation

composer require slim/twig-view

Where templates live

Create the templates folder by hand in the project root — next to config and src. Template files have the .twig extension.

Twig in the container

The template engine object is defined in config/container.php, so that actions receive it through the constructor:

// config/container.php
use Slim\Views\Twig;
 
return [
    // ... earlier entries
 
    Twig::class => fn () => Twig::create(__DIR__ . '/../templates', ['cache' => false]),
];

The first argument is the templates folder. The cache is off for now; it is turned on at deployment.

Then add the package's middleware in config/middleware.php, above the three built-in ones:

// config/middleware.php
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
 
$app->add(TwigMiddleware::createFromContainer($app, Twig::class));

The middleware is needed for the route link functions to work in templates.

The first template

{# templates/home.twig #}
<h1>{{ title }}</h1>
<p>The site runs on Slim.</p>

Double curly braces print the value of a variable. The first line is a Twig comment; it does not reach the page.

Rendering from an action

// src/Action/HomeAction.php
namespace App\Action;
 
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;
 
final class HomeAction
{
    public function __construct(private Twig $twig) {}
 
    public function __invoke(Request $request, Response $response): Response
    {
        return $this->twig->render($response, 'home.twig', [
            'title' => 'My blog',
        ]);
    }
}

The render() method takes the response, the file name relative to the templates folder and an array of data. It writes the finished page into the response and returns it. Remove the HomeAction::class definition from the settings lesson from config/container.php: the action is now created by autowiring. Open the home page — it shows the heading "My blog".

Twig syntax

NotationPurpose
{{ title }}Print a value
{{ post.title }}An array element or an object property
{% if ... %}, {% for ... %}A condition and a loop
{# ... #}A comment

A list in a template

Let us move the list of posts into a template. Create the templates/posts folder and a file in it:

{# templates/posts/index.twig #}
<h1>Posts</h1>
 
{% for post in posts %}
    <p>{{ post.title }}</p>
{% else %}
    <p>No posts yet.</p>
{% endfor %}
// src/Action/ListPostsAction.php
public function __invoke(Request $request, Response $response): Response
{
    return $this->twig->render($response, 'posts/index.twig', [
        'posts' => $this->posts->all(),
    ]);
}

private Twig $twig is added to the action's constructor. The /posts address shows the list; the loop's else branch runs when the array is empty.

Common mistake

Twig escapes HTML in printed values by itself — a protection against foreign scripts. The raw filter turns it off: {{ comment.text|raw }} prints someone else's <script> tag as is. Apply raw only to markup you created yourself.

Thanks for reading!

Donate to the site — we work hard for you.