錯誤中介軟體

事與願違。你無法預測錯誤,但可以預期錯誤。每個 Slim Framework 應用程式都有一個接收所有未捕捉到的 PHP 例外的錯誤處理程式。這個錯誤處理程式也會收到目前的 HTTP 要求和回應物件。錯誤處理程式必須準備並傳回一個適當的 Response 物件,傳回到 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,有兩種方法可以做到。

使用第一個方法,你可以直接延伸 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();

使用第二個方法,你可以提供符合 PSR-3 標準 的記錄器,例如來自熱門 Monolog 函式庫的記錄器。

<?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/xmltext/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 例外。這些例外非常適合原生繪製器。它們各自可以有 descriptiontitle 屬性,當呼叫原生 HTML 繪製器時也能提供更多見解。

基底類別 HttpSpecializedException 延伸 Exception,並附帶以下子類別

  • 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 物件。