tanaka's Programming Memo

プログラミングについてのメモ。

SlimPHP:ミドルウェアの使い方

SlimPHPの公式サイトで、ミドルウェアの使い方を確認しました。

ミドルウェア

SlimアプリケーションのRequestとResponseオブジェクトを処理する前後にコードを実行できます。そのコードのことをミドルウェアと呼びます。これを利用する理由としては、クロス・サイト・リクエスト・フォージェリからサイトを守ったり、認証することが挙げられます。

ミドルウェアとは?

ミドルウェアは3つの引数を受け取って呼び出します。

  1. \Psr\Http\Message\ServerRequestInterface - PSR7リクエストオブジェクト
  2. \Psr\Http\Message\ResponseInterface - PSR7レスポンスオブジェクト
  3. 次のミドルウェアを呼び出すためのインスタンス

これらの規約が守られていれば、ミドルウェアで何を処理しても構いません。唯一の制約は、ミドルウェアは必ず \Psr\Http\Message\ResponseInterface のインスタンスをreturnする必要があることです。それぞれのミドルウェアは、次のミドルウェアを呼び出して、リクエストとレスポンスオブジェクトを引数に渡すべきです。

ミドルウェアの動作

異なるフレームワークには、個別のミドルウェアを設定します。Slimでは、メインのアプリケーションを挟み込むようにミドルウェアのレイヤーを配置します。新しいミドルウェアレイヤーは、既存のミドルウェアレイヤーを包み込みます。ミドルウェアは外側に拡張されるように追加されていきます。最後に追加したミドルウェアレイヤーが最初に実行されます。

Slimアプリケーションを実行する際には、リクエストとレスポンスオブジェクトが外側のミドルウェアから内側に渡されていきます。一番外側のミドルウェアが最初に呼ばれて、次に一つ内側のミドルウェアが呼ばれて、と続けていき、登録されているミドルウェアが全て呼ばれたらSlimアプリケーションが呼ばれて実行されます。Slimアプリケーションが完了したら、結果をレスポンスオブジェクトで返して、一つ外側のミドルウェアに処理を戻します。一番外側のミドルウェアまで戻ったら、レスポンスがHTTPのRawデータに変換され、それをHTTPクライアントに返します。以下がそのイメージです(Middleware - Slim Frameworkより抜粋。)

f:id:am1tanaka:20160301140307p:plain

ミドルウェアの書き方

ミドルウェアは、リクエストとレスポンス、次のミドルウェアの関数を渡して呼び出します。そして、\Psr\Http\Message\ResponseInterfaceをreturnします。

クロージャミドルウェアの例

以下、参考サイトから転載したクロージャミドルウェア

<?php
/**
 * Example middleware closure
 *
 * @param  \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
 * @param  \Psr\Http\Message\ResponseInterface      $response PSR7 response
 * @param  callable                                 $next     Next middleware
 *
 * @return \Psr\Http\Message\ResponseInterface
 */
function ($request, $response, $next) {
    $response->getBody()->write('BEFORE');
    $response = $next($request, $response);
    $response->getBody()->write('AFTER');

    return $response;
};
呼び出し可能クラスにしたミドルウェアの例

__invoke()メソッドをクラスに実装すると、呼び出し可能クラスとしてミドルウェアを定義できます。以下、参考サイトから抜粋です。

<?php
class ExampleMiddleware
{
    /**
     * Example middleware invokable class
     *
     * @param  \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
     * @param  \Psr\Http\Message\ResponseInterface      $response PSR7 response
     * @param  callable                                 $next     Next middleware
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function __invoke($request, $response, $next)
    {
        $response->getBody()->write('BEFORE');
        $response = $next($request, $response);
        $response->getBody()->write('AFTER');

        return $response;
    }
}

このクラスは、add(new ExampleMiddleware()); を、$appやルート、グループに関数チェインで呼び出すことで設定できます。

ミドルウェアの追加方法

ミドルウェアは、Slimアプリケーション全体、個別のルート、個別のグループにそれぞれ設定することができます。どこに設定する場合でも、ミドルウェアの実装や引数、戻り値は共通です。

アプリケーションミドルウェア

アプリケーションミドルウェアは、すべてのHTTPリクエストに対して発動します。アプリケーションミドルウェアを追加する場合は、Slimアプリケーションのインスタンスにadd()します。以下、参考サイトから転載したクロージャミドルウェアを追加する例です。

<?php
$app = new \Slim\App();

$app->add(function ($request, $response, $next) {
	$response->getBody()->write('BEFORE');
	$response = $next($request, $response);
	$response->getBody()->write('AFTER');

	return $response;
});

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

	return $response;
});

$app->run();

上記の実行結果は以下のようになります。

BEFORE Hello AFTER
追加解説

add()関数の呼び出し時に、クロージャミドルウェア関数をその場で定義して設定しています。仕様通り、引数にリクエスト、レスポンス、次のミドルウェアへの呼び出しを受け取っています。

ミドルウェアは以下のように構成されています。

  • 事前処理
    • アプリの実行前に処理したいコードを書く
    • 省略可能
  • 次のミドルウェアの呼び出し
    • この位置でアプリが実行される
  • 事後処理
    • アプリの実行後に処理したいコードを書く
    • 省略可能
  • レスポンスをreturn

事前処理と事後処理の部分にミドルウェアの処理を書きます。アプリの前に処理したいのか、後に処理したいのかで、どこに書くかを判断してください。どちらか一方があれば、もう一方は省略して構いません。

ルートミドルウェア

ルートミドルウェアは、特定のHTTPリクエストのメソッドURIに一致した時のみに実行したいミドルウェアを設定するものです。ルートミドルウェアは、get()やpost()メソッドなど、ルートパターンとコールバックを設定しているメソッドに対してadd()します。ルートを設定するメソッドは、\Slim\Routeのインスタンスを返します。このインスタンスは、ミドルウェアに対してSlimアプリケーションのインスタンスと同じように振る舞います。以下、参考URLから転載したルートミドルウェアの設定例です。

<?php
$app = new \Slim\App();

$mw = function ($request, $response, $next) {
    $response->getBody()->write('BEFORE');
    $response = $next($request, $response);
    $response->getBody()->write('AFTER');

    return $response;
};

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

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

$app->run();

結果は、先ほどと同じく「BEFORE Hello AFTER」になります。

グループミドルウェア

幾つかのルートパターンをgroup()でまとめて、それに対してミドルウェアを設定することができます。group()でまとめたルートグループの中のいずれかのルートにリクエストが一致した時に、設定したミドルウェアが呼び出されます。グループへのミドルウェアの追加は、group()メソッドにadd()をチェインさせて行います。

以下、参考URLから転載した例です。

<?php

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

$app = new \Slim\App();

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

$app->group('/utils', function () use ($app) {
    $app->get('/date', function ($request, $response) {
        return $response->getBody()->write(date('Y-m-d H:i:s'));
    });
    $app->get('/time', function ($request, $response) {
        return $response->getBody()->write(time());
    });
})->add(function ($request, $response, $next) {
    $response->getBody()->write('It is now ');
    $response = $next($request, $response);
    $response->getBody()->write('. Enjoy!');

    return $response;
});

/utils/date メソッドを呼び出すと、以下のような結果が返ります。

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

/utils/time メソッドを呼び出すと、以下のような結果が返ります。

It is now 1436148762. Enjoy!

ドメインルートである / にアクセスした場合は、ミドルウェアは発動しないので、以下のようになります。

Hello World
追加解説

設定したミドルウェアは、事前処理として「It is now」を出力します。その後、ミドルウェアを呼び出していって、最終的にSlimアプリを実行します。そして、事後処理として「. Enjoy!」を出力します。

ミドルウェアが発動した場合は「It is now」から始まり、「. Enjoy!」で終わる文字列になりますので、/utils/dateと/utils/timeではそれらが追加された文字が出力されます。

それに対して、ミドルウェアが設定されていないドメインルートへのアクセスでは、単にアプリが出力する「Hello World」のみが出力されています。


以上です。