Welcome to Abdul Malik Ikhsan's Blog

Using routed PSR-15 RequestHandlerInterface in Slim 4

Posted in Slim 4 by samsonasik on September 2, 2019

Slim 4 already support PSR-15, and we can use class implements Psr\Http\Server\RequestHandlerInterface that produce response and register to the routes. First, we can install Slim-Skeleton to get started:

$ composer create-project slim/slim-skeleton

After it, for example, we have the following AboutPage class:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;

class AboutPage implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $response = new Response();
        $response->getBody()->write('about page');

        return $response;
    }
}

We can register above to routes configuration in app/routes.php as follow:

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

use App\Application\Page\AboutPage;
use Slim\App;

return function (App $app) {
    // ...
    $app->get('/about', AboutPage::class);
};

To test, we can run PHP Development Server:

$ php -S localhost:8080 -t public

Then open it in the web browser as http://localhost:8080/about :

How about injecting service(s) to it?

We can supply the service(s) into its __construct and it will automatically injected with the service(s), eg: we need to inject service named Psr\Log\LoggerInterface which its service definition already registered in app/dependencies.php:

<?php
// src/Application/Page/AboutPage.php
// ...
use Psr\Log\LoggerInterface;
// ...
class AboutPage implements RequestHandlerInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    // ...
}

How about add template?

For example, we want to use slim/twig-view package for it, we can first require it:

$ composer require slim/twig-view

After it, we can create cache and templates directories for it:

# create var/cache directory
$ mkdir -p var/cache

# ignore cached data from git repo (in case use git as version control) 
$ touch var/cache/.gitignore && (echo '*' && echo '!.gitignore') > var/cache/.gitignore

# create templates directories
$ mkdir -p templates/page

By above directory structure, we can register Slim\Views\Twig service in app/dependencies.php as follow:

<?php
// app/dependencies.php
// ...
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
            ]);
        },
        // ...
    ]);
};

After it, we can prepare the templates with the following structure:

├───templates
│   │   layout.html.twig
│   └───page
│           about.html.twig

Next, apply templates/layout.html.twig:

<!-- templates/layout.html.twig -->
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Slim App{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

Next, apply templates/page/about.html.twig:

<!-- templates/page/about.html.twig -->
{% extends "layout.html.twig" %}

{% block body %}

About Page.

{% endblock %}

Finally, in the AboutPage class, we can inject Slim\Views\Twig instance and use it to render the twig view as instance of Slim\Psr7\Response:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Views\Twig;

class AboutPage implements RequestHandlerInterface
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return $this->view->render(new Response(), 'page/about.html.twig');
    }
}

How about getting request attribute value?

To do this, we need to enable it in the routing by apply Slim\Handlers\Strategies\RequestHandler as its router invocation strategy, so, for example, we need to pass optional “id” parameter, we can do in app/routes:

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

use App\Application\Page\AboutPage;
use Slim\App;
use Slim\Handlers\Strategies\RequestHandler;

return function (App $app) {
    // ...
    $app->get('/about[/{id:[0-9]+}]', AboutPage::class)
        ->setInvocationStrategy(new RequestHandler(true));
};

We can also set default invocation strategy by apply it before register the route(s) that the pages implements RequestHandlerInterface:

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

use App\Application\Page\AboutPage;
use Slim\App;
use Slim\Handlers\Strategies\RequestHandler;

return function (App $app) {
    $routeCollector = $app->getRouteCollector();
    // ... before setDefaultInvocationStrategy placement,
    // ... it will compatible with RequestResponse strategy    
  
    $routeCollector->setDefaultInvocationStrategy(new RequestHandler(true));
    // ... after setDefaultInvocationStrategy placement,
    // ... it will compatible only with selected default invocable strategy

    $app->get('/about[/{id:[0-9]+}]', AboutPage::class);
};

So, from now on, we can get attribute in the handle() method and passed parameter to the view:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;

class AboutPage implements RequestHandlerInterface
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $id = $request->getAttribute('id');

        return $this->view->render(
            new Response(),
            'page/about.html.twig',
            [
                'id' => $id
            ]
        );
    }
}

So, in the view, we can do:

<!-- templates/page/about.html.twig -->
{% extends "layout.html.twig" %}

{% block body %}

About Page with id {{ id }}

{% endblock %}

and when open http://localhost:8080/about/1 , we can get:

That’s it!

Tagged with: , , , , ,