ミドルウェア

Slim アプリケーションの前後において、必要に応じて Request オブジェクトと Response オブジェクトを操作するコードを実行できます。これはミドルウェアと呼ばれます。なぜこれを行う必要があるのでしょうか?おそらく、クロスサイトリクエストフォージェリからアプリケーションを保護したい、あるいはアプリケーションの実行前にリクエストを認証したいという場合があるでしょう。ミドルウェアは、これらのシナリオに最適です。

ミドルウェアとは何か?

ミドルウェアは、ウェブアプリケーションにおいてクライアントリクエストとサーバーレスポンスの間に位置するレイヤーです。アプリケーションパイプラインを通過する HTTP リクエストとレスポンスをインターセプト、処理し、変更する可能性があります。

ミドルウェアは、認証、認可、ロギング、リクエストの変更、レスポンスの変換、エラー処理など、さまざまなタスクを処理できます。

各ミドルウェアは、それぞれの機能を実行し、次にチェーン内の次のミドルウェアに制御を渡します。これにより、ウェブアプリケーションにおけるクロスカット関心の処理に対して、モジュール式で再利用可能なアプローチが可能になります。

ミドルウェアの動作

異なるフレームワークでは、ミドルウェアの使い方が異なります。Slim は、コアアプリケーションを囲む同心円状のレイヤーとしてミドルウェアを追加します。各新しいミドルウェアレイヤーは、既存のミドルウェアレイヤーを囲みます。同心円構造は、追加のミドルウェアレイヤーが追加されるにつれて外側に拡大します。

最後に追加されたミドルウェアレイヤーが最初に実行されます。

Slim アプリケーションを実行すると、Request オブジェクトは外側から内側に向かってミドルウェア構造をトラバースします。まず最外層のミドルウェアに入り、次にその次のミドルウェアに入り、(以下同様)最終的にSlim アプリケーション自体に到着します。Slim アプリケーションが適切なルートをディスパッチした後、結果として得られる Response オブジェクトはSlim アプリケーションを出て、内側から外側に向かってミドルウェア構造をトラバースします。最終的に、最終的な Response オブジェクトは最外層のミドルウェアを出て、生の HTTP レスポンスにシリアル化され、HTTP クライアントに返されます。ミドルウェアのプロセスフローを示す図を以下に示します。

Middleware architecture

ミドルウェアの書き方

ミドルウェアは、Request オブジェクトと RequestHandler オブジェクトの2つの引数を受け取る呼び出し可能オブジェクトです。各ミドルウェアは Psr\Http\Message\ResponseInterface のインスタンスを返さなければなりません。

クロージャミドルウェア

この例では、ミドルウェアはクロージャです。

<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;

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

$app = AppFactory::create();

$beforeMiddleware = function (Request $request, RequestHandler $handler) use ($app) {
    // Example: Check for a specific header before proceeding
    $auth = $request->getHeaderLine('Authorization');
    if (!$auth) {
        // Short-circuit and return a response immediately
        $response = $app->getResponseFactory()->createResponse();
        $response->getBody()->write('Unauthorized');
        
        return $response->withStatus(401);
    }

    // Proceed with the next middleware
    return $handler->handle($request);
};

$afterMiddleware = function (Request $request, RequestHandler $handler) {
    // Proceed with the next middleware
    $response = $handler->handle($request);
    
    // Modify the response after the application has processed the request
    $response = $response->withHeader('X-Added-Header', 'some-value');
    
    return $response;
};

$app->add($afterMiddleware);
$app->add($beforeMiddleware);

// ...

$app->run();

呼び出し可能クラスミドルウェア

この例では、ミドルウェアはマジックメソッド __invoke() を実装する呼び出し可能クラスです。

<?php

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Message\ResponseInterface as Response;

class ExampleBeforeMiddleware
{
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        // Handle the incoming request
        // ...

        // Invoke the next middleware and return response
        return $handler->handle($request);
    }
}
<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class ExampleAfterMiddleware
{
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        // Invoke the next middleware and get response
        $response = $handler->handle($request);

        // Handle the outgoing response
        // ...

        return $response;
    }
}

PSR-15 ミドルウェア

PSR-15 は、HTTP サーバーリクエストハンドラーとミドルウェアコンポーネントの共通インターフェースを定義する標準です。

Slim は PSR-15 ミドルウェアを組み込みでサポートしています。

主要なインターフェース

  • Psr\Http\Server\MiddlewareInterface: このインターフェースは、ミドルウェアが実装しなければならない process メソッドを定義します。
  • Psr\Http\Server\RequestHandlerInterface: HTTP レスポンスを生成するために HTTP リクエストを処理する HTTP リクエストハンドラー。

PSR-15 ミドルウェアクラスを作成するには、MiddlewareInterface を実装する必要があります。

以下は、シンプルな PSR-15 ミドルウェアの例です。

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class ExampleMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandler $handler): Response
    {
        // Optional: Handle the incoming request
        // ...

        // Invoke the next middleware and get response
        $response = $handler->handle($request);

        // Optional: Handle the outgoing response
        // ...

        return $response;
    }
}

受信リクエストは、認証、認可、ログ記録、検証、または変更できます。

発信レスポンスは、ログ記録、変換、圧縮、または追加ヘッダーの追加を行うことができます。

PSR-15 ミドルウェアでの新しいレスポンスの作成

Psr\Http\Message\ResponseFactoryInterface を使用して新しいレスポンスを作成できます。これは、新しいレスポンスオブジェクトを作成する createResponse() メソッドを提供します。

新しいレスポンスを作成する PSR-15 ミドルウェアクラスの例を以下に示します。

<?php

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ExampleMiddleware implements MiddlewareInterface
{
    private ResponseFactoryInterface $responseFactory;

    public function __construct(ResponseFactoryInterface $responseFactory)
    {
        $this->responseFactory = $responseFactory;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // Check some condition to determine if a new response should be created
        if (true) {
            // Create a new response using the response factory
            $response = $this->responseFactory->createResponse();
            $response->getBody()->write('New response created by middleware');
            
            return $response;
        }

        // Proceed with the next middleware
        return $handler->handle($request);
    }
}

レスポンスはデフォルトで 200 OK ステータスコードで作成されます。HTTP ステータスコードを変更するには、目的のステータスコードを createResponse メソッドの引数として渡すことができます。

$response = $this->responseFactory->createResponse(201);

レスポンスファクトリは、ミドルウェアに注入する必要がある依存関係であることに注意してください。Slim の
DI コンテナ(例:PHP-DI)が Psr\Http\Message\ResponseFactoryInterface のインスタンスを提供するように適切に構成されていることを確認してください。

例: slim\psr7 パッケージを使用する PHP-DI 定義

use Psr\Container\ContainerInterface;
use Slim\Psr7\Factory\ResponseFactory;
// ...

return [
    // ...
    ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(ResponseFactory::class);
    },
];

例: nyholm/psr7 パッケージを使用する PHP-DI 定義

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Container\ContainerInterface;
// ...

return [
    // ...
    ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },
];

ミドルウェアの登録

ミドルウェアを使用するには、Slim の $app、ルート、またはルートグループに各ミドルウェアを登録する必要があります。

// Add middleware to the App
$app->add(new ExampleMiddleware());

// Add middleware to the App using dependency injection
$app->add(ExampleMiddleware::class);

// Add middleware to a route
$app->get('/', function () { ... })->add(new ExampleMiddleware());

// Add middleware to a route group
$app->group('/', function () { ... })->add(new ExampleMiddleware());

ミドルウェアの実行順序

Slim は、Last In, First Out (LIFO) の順序でミドルウェアを処理します。つまり、最後に追加されたミドルウェアが最初に実行されます。複数のミドルウェアコンポーネントを追加する場合、それらは追加された逆順に実行されます。

$app->add(new MiddlewareOne());
$app->add(new MiddlewareTwo());
$app->add(new MiddlewareThree());

この場合、MiddlewareThree が最初に実行され、次に MiddlewareTwo、最後に MiddlewareOne が実行されます。

ルートミドルウェア

ルートミドルウェアは、そのルートが現在の HTTP リクエストメソッドと URI と一致する場合にのみ呼び出されます。ルートミドルウェアは、Slim アプリケーションのルーティングメソッド(例:get() または post())を呼び出した直後に指定されます。各ルーティングメソッドは \Slim\Route のインスタンスを返し、このクラスは Slim アプリケーションインスタンスと同じミドルウェアインターフェースを提供します。Route インスタンスの add() メソッドを使用して、Route にミドルウェアを追加します。この例では、上記のクロージャミドルウェアの例を追加します。

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;

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

$app = AppFactory::create();

$middleware = function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write('World');

    return $response;
};

$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write('Hello ');

    return $response;
})->add($middleware);

$app->run();

これにより、次の HTTP レスポンスボディが出力されます。

Hello World

グループミドルウェア

全体的なアプリケーションと標準ルートに加えて、ミドルウェアを受け入れることができるのは、group() マルチルート定義機能です。また、内部的に個々のルートも許可します。ルートグループミドルウェアは、そのルートがグループから定義された HTTP リクエストメソッドと URI のいずれかと一致する場合にのみ呼び出されます。コールバック内でミドルウェアを追加するには、group() メソッドの後に add() をチェーンして、グループ全体のミドルウェアを設定します。

URL ハンドラーのグループでコールバックミドルウェアを使用するサンプルアプリケーション。

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;

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

$app = AppFactory::create();

$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write('Hello World');
    return $response;
});

$app->group('/utils', function (RouteCollectorProxy $group) {
    $group->get('/date', function (Request $request, Response $response) {
        $response->getBody()->write(date('Y-m-d H:i:s'));
        return $response;
    });
    
    $group->get('/time', function (Request $request, Response $response) {
        $response->getBody()->write((string)time());
        return $response;
    });
})->add(function (Request $request, RequestHandler $handler) use ($app) {
    $response = $handler->handle($request);
    $dateOrTime = (string) $response->getBody();

    $response = $app->getResponseFactory()->createResponse();
    $response->getBody()->write('It is now ' . $dateOrTime . '. Enjoy!');

    return $response;
});

$app->run();

/utils/date メソッドを呼び出すと、次のような文字列が出力されます。

It is now 2015-07-06 03:11:01. Enjoy!

/utils/time にアクセスすると、次のような文字列が出力されます。

It is now 1436148762. Enjoy!

しかし、/ (ドメインルート)にアクセスすると、ミドルウェアが割り当てられていないため、次の出力が生成されると予想されます。

Hello World

ミドルウェアからの変数の受け渡し

ミドルウェアから属性を渡す最も簡単な方法は、リクエストの属性を使用することです。

ミドルウェアで変数を設定する

$request = $request->withAttribute('foo', 'bar');

ルートコールバックで変数を取得する

$foo = $request->getAttribute('foo');

利用可能なミドルウェアの検索

ニーズを満たす PSR-15 ミドルウェアクラスが既に記述されている場合があります。検索する非公式なリストをいくつか示します。