Welcome to Abdul Malik Ikhsan's Blog

Apply Twig Extension via Middleware in Slim 4

Posted in Slim 4 by samsonasik on October 28, 2019

If we use Slim 4, we can use Twig for template engine, and there is slim/twig-view for that. However, when dealing with extension, especially routing and uri related stuff, we can’t use a way like in Slim 3, at least, by default, at least, what I know right now. There is an alternative way to add twig extension, which is via Middleware!

First, I assume that the Twig service already registered in app/dependencies.php:

<?php
// app/dependencies.php
declare(strict_types=1);

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Slim\Views\Twig;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        // ...
        Twig::class => function (ContainerInterface $c) {
            return new Twig(__DIR__ . '/../templates', [
                'cache' => __DIR__ . '/../var/cache',
                'auto_reload' => true
            ]);
        },
        // ...
    ]);
};

Now, we need to add extension, which consume Slim\Routing\RouteCollector and Slim\Psr7\Uri instance. Now, we can create middleware on the fly in app/middleware.php, eg: via anonymous class that implements Psr\Http\Server\MiddlewareInterface.

<?php
// app/middleware.php
declare(strict_types=1);

// ...
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\App;
use Slim\Routing\RouteCollector;
use Slim\Views\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
// ...

return function (App $app) {

    // ...
    $container = $app->getContainer();
    $twig      = $container->get(Twig::class);
    $router    = $app->getRouteCollector();

    $app->add(new class ($twig, $router) implements MiddlewareInterface {

        private $twig;
        private $router;

        public function __construct(Twig $twig, RouteCollector $router)
        {
            $this->twig   = $twig;
            $this->router = $router;
        }

        public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
        {
           $uri = $request->getUri();
           $this->twig->addExtension(new class ($this->router, $uri) extends AbstractExtension {

                private $router;
                private $uri;

                public function __construct(RouteCollector $router, UriInterface $uri)
                {
                    $this->router = $router;
                    $this->uri    = $uri;
                }

                public function getFunctions()
                {
                    return [
                        new TwigFunction('base_path', function () : string {
                            return $this->router->getBasePath();
                        }),
                        new TwigFunction('full_url_for', function (string $routeName, array $data = [], array $queryParams = []) : string {
                            return $this->router->getRouteParser()->fullUrlFor($this->uri, $routeName, $data, $queryParams);
                        }),
                    ];
                }

           });
           return $handler->handle($request);
        }
    });
    // ...
};

In its __construct, we inject with Slim\Views\Twig and Slim\Routing\RouteCollector instance. In its process() function, we call Slim\Views\Twig::addExtension() function to add extension, which we can create another anonymous class that extends Twig\Extension\AbstractExtension, injected with Slim\Routing\RouteCollector and Slim\Psr7\Uri (which pulled from object instance of Psr\Http\Message\ServerRequestInterface), which we can add getFunctions() method to add twig functions.

That’s it, in above example, I added base_path and full_url_for function which now can be used in twig view, eg:

    {# display base path url #}
    {{ base_path() }}

    {# display full url for route with name "postpagedetail" with parameter id = 1 and query parameter action = view  #}
    {{ full_url_for('postpagedetail', { 'id': 1 }, { 'action': 'view' }) }}

You are feeling have too much code in app/middleware.php ? Of course, you can create a separate class for it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: