エラーミドルウェア

物事はうまくいかないことがあります。エラーを予測することはできませんが、予期することはできます。各 Slim Framework アプリケーションには、キャッチされないすべての PHP 例外を受け取るエラーハンドラーがあります。このエラーハンドラーは、現在の HTTP リクエストオブジェクトとレスポンスオブジェクトも受け取ります。エラーハンドラーは、HTTP クライアントに返される適切なレスポンスオブジェクトを準備して返す必要があります。

使用法

<?php

use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * The routing middleware should be added earlier than the ErrorMiddleware
 * Otherwise exceptions thrown from it will not be handled by the middleware
 */
$app->addRoutingMiddleware();

/**
 * Add Error Middleware
 *
 * @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
 * @param LoggerInterface|null  $logger -> Optional PSR-3 Logger  
 *
 * Note: This middleware should be added last. It will not handle any exceptions/errors
 * for middleware added after it.
 */
$errorMiddleware = $app->addErrorMiddleware(true, true, true);

// ...

$app->run();

カスタムエラーハンドラーの追加

任意のタイプの Exception または Throwable に対してカスタムハンドラーをマップできるようになりました。

<?php

use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// Optional: Define custom error logger
$logger = new Logger('error');
$logger->pushHandler(new RotatingFileHandler('error.log'));

// Define Custom Error Handler
$customErrorHandler = function (
    ServerRequestInterface $request,
    Throwable $exception,
    bool $displayErrorDetails,
    bool $logErrors,
    bool $logErrorDetails
) use ($app, $logger) {
    if ($logger) {
        $logger->error($exception->getMessage());
    }

    $payload = ['error' => $exception->getMessage()];

    $response = $app->getResponseFactory()->createResponse();
    $response->getBody()->write(
        json_encode($payload, JSON_UNESCAPED_UNICODE)
    );

    return $response;
};

// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);
$errorMiddleware->setDefaultErrorHandler($customErrorHandler);

// ...

$app->run();

エラーロギング

Slim に付属のデフォルトの ErrorHandler にカスタムエラーロギングをパイプしたい場合は、2つの方法があります。

最初の方法では、ErrorHandler を拡張して logError() メソッドをスタブするだけで済みます。

<?php
namespace MyApp\Handlers;

use Slim\Handlers\ErrorHandler;

class MyErrorHandler extends ErrorHandler
{
    protected function logError(string $error): void
    {
        // Insert custom error logging function.
    }
}
<?php

use MyApp\Handlers\MyErrorHandler;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// Instantiate Your Custom Error Handler
$myErrorHandler = new MyErrorHandler($app->getCallableResolver(), $app->getResponseFactory());

// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorMiddleware->setDefaultErrorHandler($myErrorHandler);

// ...

$app->run();

2番目の方法では、人気のある Monolog ライブラリの1つなど、PSR-3 標準に準拠したロガーを提供できます。

<?php

use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use MyApp\Handlers\MyErrorHandler;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// Monolog Example
$logger = new Logger('app');
$streamHandler = new StreamHandler(__DIR__ . '/var/log', 100);
$logger->pushHandler($streamHandler);

// Add Error Middleware with Logger
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);

// ...

$app->run();

エラー処理/レンダリング

レンダリングは、最終的に処理から切り離されました。これは、ErrorRenderers の助けを借りて、コンテンツタイプを検出し、適切にレンダリングします。コアの ErrorHandler は、完全にリファクタリングされた AbstractErrorHandler クラスを拡張します。デフォルトでは、サポートされているコンテンツタイプに対して適切な ErrorRenderer を呼び出します。コアの ErrorHandler は、次のコンテンツタイプのレンダラーを定義します。

  • application/json
  • application/xml および text/xml
  • text/html
  • text/plain

任意のコンテンツタイプに対して、独自のエラーレンダラーを登録できます。まず、\Slim\Interfaces\ErrorRendererInterface を実装する新しいエラーレンダラーを定義します。

<?php

use Slim\Interfaces\ErrorRendererInterface;
use Throwable;

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

次に、コアエラーハンドラーにそのエラーレンダラーを登録します。以下の例では、text/html コンテンツタイプに使用するレンダラーを登録します。

<?php

use MyApp\Handlers\MyErrorHandler;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true);

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

// ...

$app->run();

エラーレンダリングに特定のコンテンツタイプを強制する

デフォルトでは、エラーハンドラーはリクエストの Accept ヘッダーを使用してエラーレンダラーを検出しようとします。エラーハンドラーに特定のエラーレンダラーを使用するように強制する必要がある場合は、次のように記述できます。

$errorHandler->forceContentType('application/json');

新しい HTTP 例外

アプリケーション内に名前付きの HTTP 例外を追加しました。これらの例外は、ネイティブレンダラーと適切に連携します。ネイティブ HTML レンダラーが呼び出されたときに、少し詳しい情報を提供するために、それぞれに description および title 属性を持たせることもできます。

基本クラス HttpSpecializedExceptionException を拡張し、次のサブクラスが付属しています。

  • HttpBadRequestException
  • HttpForbiddenException
  • HttpInternalServerErrorException
  • HttpMethodNotAllowedException
  • HttpNotFoundException
  • HttpNotImplementedException
  • HttpUnauthorizedException

基本リポジトリで提供しないその他の応答コードが必要な場合は、HttpSpecializedException クラスを拡張できます。たとえば、ネイティブのもののように動作する 504 ゲートウェイタイムアウト例外が必要な場合は、次のようにします。

class HttpGatewayTimeoutException extends HttpSpecializedException
{
    protected $code = 504;
    protected $message = 'Gateway Timeout.';
    protected $title = '504 Gateway Timeout';
    protected $description = 'Timed out before receiving response from the upstream server.';
}

HTTP 例外をスローするには、次のコードを使用します。

use Slim\Exception\HttpNotFoundException;
// ...

throw new HttpNotFoundException($request);

例外をスローするときは、必ず $request オブジェクトを渡してください。