gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

Slim4で「404のログ」だけ消したい(&任意のエラー画面出したい)

Slim4です。
エラー周りの基本処理で。

 * @param bool $displayErrorDetails -> Should be set to false in production
 * @param bool $logErrors -> Parameter is passed to the default ErrorHandler
 * @param bool $logErrorDetails -> Display error details in error log
 * which can be replaced by a callable of your choice.
 * @param \Psr\Log\LoggerInterface $logger -> Optional PSR-3 logger to receive errors

ってなコメントもございますので、おいちゃんは大概

    $errorMiddleware = $app->addErrorMiddleware($container->get('settings')['displayErrorDetails'], true, true, $container->get('logger'));

って書いておくでございます。
ただ、これだけだと「404もエラーログに書かれるので結構ウザい」でございます。

いや「Slimのエラーを一通りログに出さない」んなら

    $errorMiddleware = $app->addErrorMiddleware($container->get('settings')['displayErrorDetails'], true, true);

でよいのですが、「404以外のエラーは念のために補足しておきたい」んですよねぇ、という乙女心*1

まぁ色々調べてみるわけなのですが。

まず「エラー時に任意の画面を出したい」場合、
https://www.slimframework.com/docs/v4/middleware/error-handling.html

// Get the default error handler and register my custom error renderer.
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorHandler->registerErrorRenderer('text/html', MyCustomErrorRenderer::class);

ってな記述があるのでございます。
なお上述クラスは

<?php
use Slim\Interfaces\ErrorRendererInterface;

class MyCustomErrorRenderer implements ErrorRendererInterface
{
    public function __invoke(Throwable $exception, bool $displayErrorDetails): string
    {
        return 'My awesome format';
    }
}

こんな風に実装する感じでございます。

さて。
とりあえずgetDefaultErrorHandler()てのがあるので、軽くgrepります*2
"vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php"

    public function getDefaultErrorHandler()
    {
        if ($this->defaultErrorHandler === null) {
            $this->defaultErrorHandler = new ErrorHandler(
                $this->callableResolver,
                $this->responseFactory,
                $this->logger
            );
        }

        return $this->callableResolver->resolve($this->defaultErrorHandler);
    }

getがあるんならsetもあるんじゃなかろうか。

    public function setDefaultErrorHandler($handler): self
    {
        $this->defaultErrorHandler = $handler;
        return $this;
    }

DefaultがあるんならDefault無しもあるんじゃなかろうか。

    public function setErrorHandler($typeOrTypes, $handler, bool $handleSubclasses = false): self
    {
        if (is_array($typeOrTypes)) {
            foreach ($typeOrTypes as $type) {
                $this->addErrorHandler($type, $handler, $handleSubclasses);
            }
        } else {
            $this->addErrorHandler($typeOrTypes, $handler, $handleSubclasses);
        }

        return $this;
    }

ここからちゃんとコードを確認してもよいのですが……面倒なんでvar_dumpで調査します。
つまり
・setErrorHandler()の先頭に var_dump($typeOrTypes); exit; を仕込んで
・404をわざと発生させます
まぁ予想通り Slim\Exception\HttpNotFoundException でございます(Slim4、この辺は例外のクラスで判別するので)。

ってことは

    public function getErrorHandler(string $type)
    {
        if (isset($this->handlers[$type])) {
            return $this->callableResolver->resolve($this->handlers[$type]);
        } elseif (isset($this->subClassHandlers[$type])) {
            return $this->callableResolver->resolve($this->subClassHandlers[$type]);
        } else {
            foreach ($this->subClassHandlers as $class => $handler) {
                if (is_subclass_of($type, $class)) {
                    return $this->callableResolver->resolve($handler);
                }
            }
        }

        return $this->getDefaultErrorHandler();
    }

これも大体予想通り。

なので、ざっくりと実装してみます。

    $errorMiddleware->setErrorHandler(\Slim\Exception\HttpNotFoundException::class, function($request, $e) use($app){
        $response = $app->getResponseFactory()->createResponse(404);
        $response->getBody()->write('めっからないよん??');
        return $response;
    });

確認……うん、OK。

というわけで、備忘録を兼ねて、メモ。

*1:なにゆえに乙女?

*2:ぐれぷります、とかお読みいただければ幸いです