Welcome to Abdul Malik Ikhsan's Blog

Create templated 404 page in Slim 4 with CallableResolver

Posted in Slim 4, Tutorial PHP by samsonasik on November 8, 2019

In Slim 4 Skeleton, the 404 response handled in App\Application\Handlers\HttpErrorHandler::respond() which check against Slim\Exception\HttpNotFoundException. We can create a templated 404 page for it with utilize Slim\CallableResolver via callableResolver property which can resolve the callable handler. I assume that we are using Twig template engine, and already has setup of Twig service like in my previous post.

We can create a not found handler like the following in src/Application/Handlers/NotFoundHandler.php

<?php
// src/Application/Handlers/NotFoundHandler.php
declare(strict_types=1);

namespace App\Application\Handlers;

use Psr\Http\Message\ResponseInterface;
use Slim\Views\Twig;

use function compact;

class NotFoundHandler
{
    private $view;

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

    public function __invoke(
        ResponseInterface $response,
        string            $message
    ): ResponseInterface {
        return $this->view->render($response, '404.html.twig', compact('message'));
    }
}

We can create a view based on it like the following at templates/404.html.twig:

{# templates/404.html.twig #}

{% extends "layout.html.twig" %}
{% block title '404 - '~parent() %}

{% block body %}

{{ message }}

{% endblock %}

Now, in App\Application\Handlers\HttpErrorHandler::respond(), we can check when $exception instanceof HttpNotFoundException to make a self called resolved NotFoundHandler.

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {
            $response = $this->responseFactory->createResponse($statusCode);
            return ($this->callableResolver->resolve(NotFoundHandler::class)(
                $response,
                $exception->getMessage()
            ));
        }
        // ...
    }
// ...

So, when the Slim\Exception\HttpNotFoundException thrown, it will shown the 404 page with brought the message passed into it like the following:

Bonus

We can create a handling against Request as well, eg: show 404 templated page only when request doesn’t has Accept: application/json or X-Requested-With:XmlHttpRequest header, so, we can modify like the following:

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {            
            $isAppJsonAccept  = $this->request->getHeaderLine('Accept')           === 'application/json';
            $isXmlHttpRequest = $this->request->getHeaderLine('X-Requested-With') === 'XmlHttpRequest';

            if (! $isAppJsonAccept && ! $isXmlHttpRequest) {
                $response = $this->responseFactory->createResponse($statusCode);
                return ($this->callableResolver->resolve(NotFoundHandler::class)(
                    $response,
                    $exception->getMessage()
                ));
            }

            // already return early, no need else
            $error->setType(ActionError::RESOURCE_NOT_FOUND);
        }
        // ...
    }
// ...

So, for example, when called via ajax, it will show like the following:

Create “Abstract Factory” service in PHP-DI with RequestedEntry and Wildcards

Posted in Slim 4, Tutorial PHP by samsonasik on November 6, 2019

If you’re familiar with Zend Framework’s servicemanager, you may already used the abstract factory which acts as limbo when service not registered. When the pattern checked is matched, it will try to create service based on it, automatically.

We can do that in PHP-DI as well. For example, we are experimenting with my previous post on DDD in slim 4 framework:

├───Application
├───Domain
│   ├───DomainException
│   │       DomainException.php
│   │       DomainRecordNotFoundException.php
│   │
│   ├───Post
│   │       Post.php
│   │       PostNotFoundException.php
│   │       PostRepository.php
│
└───Infrastructure
    └───Persistence
        ├───Post
        │       ZendDbPostRepository.php

We are going to create service with pattern “App\Domain\Post\PostRepository” from App\Infrastructure\Persistence namespace with as an object from “App\Infrastructure\Persistence\Post\ZendDbPostRepository”. In PHP-DI, we can combine both RequestedEntry and Wildcards definitions, the service definition will be like the following:

<?php
declare(strict_types=1);

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\Hydrator\ObjectPropertyHydrator;
use DI\Factory\RequestedEntry;

return function (ContainerBuilder $containerBuilder) {

    $containerBuilder->addDefinitions([

        'App\Domain\*\*Repository' => function (RequestedEntry $entry, ContainerInterface $c) {

            // get entity class name, 
            // eg: "Post" by service named "App\Domain\Post\PostRepository" 
            preg_match(
                '/(?<=App\\\\Domain\\\\)([A-Z][a-z]{1,})(?=\\\\\1Repository)/',
                $entry->getName(),
                $matches
            );
            $entity          = current($matches);
            $fullEntityClass = 'App\Domain' . str_repeat('\\' . $entity, 2);
            $fullRepoClass   = 'App\Infrastructure\Persistence' . '\\' . $entity . '\ZendDb' . $entity . 'Repository';

            $tableGateway = new TableGateway(
                $fullEntityClass::TABLE,
                $c->get(AdapterInterface::class),
                null,
                new HydratingResultSet(new ObjectPropertyHydrator(), new $fullEntityClass)
            );

            return new $fullRepoClass($tableGateway);
        },

    ]);
};

That’s it!

Tagged with: