目次

Slim4 コンセプト

Version 4.5.0

y2sunlight 2020-09-23

Slim に戻る

関連記事

本章は以下のサイトの Concepts のセクションを翻訳し若干の補足を加えたのもです。


アプリケーションのライフサイクル

1.インスタンス化

まず、Slim\App クラスをインスタンス化します。これはSlimアプリケーションオブジェクトです。インスタンス化の中で、Slimはアプリケーションの依存関係ごとにデフォルトのサービスを登録します。


2.ルート定義

次に、アプリケーションインスタンスの get()post()put()delete()patch()head()、および options() ルーティングメソッドを使用してルートを定義します。これらのインスタンスメソッドは、アプリケーションのルーターオブジェクトにルートを登録します。各ルーティングメソッドは Route インスタンスを返すため、Route インスタンスのメソッドをすぐに呼び出して、ミドルウェアを追加したり、名前を割り当てたりできます。


3.アプリケーション ランナー

3番目に、アプリケーションインスタンスの run() メソッドを呼び出します。このメソッドは、次のプロセスを開始します。

A. ミドルウェア・スタックに入る

run() メソッドは、アプリケーションのミドルウェア・スタックを内側に横断し始めます。これは、ミドルウェア層の同心円構造で、Slimアプリケーションの実行前(および実行後)に Environment、Request、および Responseオブジェクトを受信(またはオプションで操作)します。Slimアプリケーションは、同心円状のミドルウェア構造の最内層にあります。各ミドルウェア層は、最外層から内側に向かって呼び出されていきます。

B. アプリケーションを起動する

the Not Found or Not Allowed handler is invoked.

run() メソッドが最も内側のミドルウェア層に到達すると、アプリケーションのインスタンスを呼び出し、現在のHTTPリクエストを適切なアプリケーションのルート(route)オブジェクトにディスパッチします。ルート(route)がHTTPメソッドとURIに一致する場合、ルート(route)のミドルウェアと呼び出し可能(callable)オブジェクトが呼び出されます。一致するルートが見つからない場合は、NotFound または NotAllowed ハンドラーが呼び出されます。

C. ミドルウェア・スタックを出る

アプリケーションのディスパッチプロセスが完了すると、各ミドルウェアレイヤーは、最も内側のレイヤーから始めて、外側に向かって制御を取り戻します。

D. HTTPレスポンスを送信する

最も外側のミドルウェア層が制御を引き渡した後、アプリケーションのインスタンスはHTTPレスポンスを準備し、シリアル化し、そして返します。HTTPレスポンスヘッダーはPHPのネイティブな header() メソッドで設定され、HTTPレスポンスボディーは現在の出力バッファーに出力されます。


PSR-7と値オブジェクト

Slimは、Request オブジェクトと Response オブジェクトの PSR-7 インターフェイスをサポートしています。これにより、任意のPSR-7実装を使用できるようになるので、Slimはフレキシブルなものになります。例えば、GuzzleHttp\Psr7\CachingStream のインスタンス、または GuzzleHttp\Psr7\stream_for() 関数によって返される任意のインスタンスを返すことができます。

Slimは、箱から出してすぐに機能するように、独自のPSR-7実装を提供します。但し、SlimのデフォルトのPSR-7オブジェクトをサードパーティの実装に自由に置き換えることができます。アプリケーションコンテナのrequest と response サービスをオーバーライドするだけで、それぞれ Psr\Http\Message\ServerRequestInterfacePsr\Http\Message\ResponseInterface のインスタンスが返されます。

PSR-7の邦訳はこちらにあります。


ミドルウェア

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


ミドルウェアとは何か ?

ミドルウェアは、PSR-15 ミドルウェアインターフェイスを実装します。

  1. Psr\Http\Message\ServerRequestInterface — PSR-7 リクエストオブジェクト
  2. Psr\Http\Server\RequestHandlerInterface — PSR-15 リクエストハンドラオブジェクト

これらのオブジェクトを使えば、適切なことは何でもできます。唯一の難しい要件は、ミドルウェアが Psr\Http\Message\ResponseInterface のインスタンスを返さなければならないことです( MUST )。 各ミドルウェアは、次のミドルウェアをインボークして、引数としてリクエストオブジェクトとレスポンスオブジェクトを渡す必要があります( SHOULD )。

PSR-15の邦訳はここにあります。


ミドルウェアはどのように機能するか ?

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

追加された最後のミドルウェア層が最初に実行されます。

Slimアプリケーションを実行すると、Requestオブジェクトはミドルウェア構造を外側から内側に横断します。最初に最も外側のミドルウェアに入り、そして次に外側のミドルウェアに入り(というように)、最終的にSlimアプリケーション自体に到達します。Slimアプリケーションが適切なルート(route)をディスパッチした後、結果のResponseオブジェクトがSlimアプリケーションを終了し、ミドルウェア構造を内側から外に横断していきます。最後に、最終的なResponseオブジェクトは、最も外側のミドルウェアを出て、生のHTTPレスポンスにシリアル化され、HTTPクライアントに返されます。ミドルウェアのプロセスフローを示す図は次のとおりです。


ミドルウェアを作成するにはどうすればよいか?

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

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

このサンプルのミドルウェアはクロージャです。

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
 
require __DIR__ . '/../vendor/autoload.php';
 
$app = AppFactory::create();
 
/**
 * Example middleware closure
 *
 * @param  ServerRequest  $request PSR-7 request
 * @param  RequestHandler $handler PSR-15 request handler
 *
 * @return Response
 */
$beforeMiddleware = function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();
 
    $response = new Response();
    $response->getBody()->write('BEFORE' . $existingContent);
 
    return $response;
};
 
$afterMiddleware = function ($request, $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write('AFTER');
    return $response;
};
 
$app->add($beforeMiddleware);
$app->add($afterMiddleware);
 
// ...
 
$app->run();

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

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

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
 
class ExampleBeforeMiddleware
{
    /**
     * Example middleware invokable class
     *
     * @param  ServerRequest  $request PSR-7 request
     * @param  RequestHandler $handler PSR-15 request handler
     *
     * @return Response
     */
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        $existingContent = (string) $response->getBody();
 
        $response = new Response();
        $response->getBody()->write('BEFORE' . $existingContent);
 
        return $response;
    }
}
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
 
class ExampleAfterMiddleware
{
    /**
     * Example middleware invokable class
     *
     * @param  ServerRequest  $request PSR-7 request
     * @param  RequestHandler $handler PSR-15 request handler
     *
     * @return Response
     */
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        $response->getBody()->write('AFTER');
        return $response;
    }
}

これらのクラスをミドルウェアとして使用するには、 $app のルートマッピングメソッド get()、post()、put()、patch()、delete()、options()、any()、または group()の後に 関数チェーンとして add(new ExampleMiddleware()); を使用でき、以下のコードでは、これらの内の一つを含んでいます。

<?php
use Slim\Factory\AppFactory;
 
require __DIR__ . '/../vendor/autoload.php';
 
$app = AppFactory::create();
 
// Add Middleware On App
$app->add(new ExampleMiddleware());
 
// Add Middleware On Route
$app->get('/', function () { ... })->add(new ExampleMiddleware());
 
// Add Middleware On Group
$app->group('/', function () { ... })->add(new ExampleMiddleware());
 
// ...
 
$app->run();


ミドルウェアを追加するにはどうすればよい?

ミドルウェアは、全てのSlimアプリケーション、個別のSlimアプリケーションルート、またはルートグループに追加できます。全てのシナリオは同じミドルウェアを受け入れ、同じミドルウェアインターフェースを実装します。

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

アプリケーションミドルウェアは、全ての着信HTTPリクエストに対してインボークされます。Slimアプリケーションインスタンスの add() メソッドを使用してアプリケーションミドルウェアを追加します。この例では、上記のクロージャミドルウェアの例を追加します。

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
 
require __DIR__ . '/../vendor/autoload.php';
 
$app = AppFactory::create();
 
$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();
 
    $response = new Response();
    $response->getBody()->write('BEFORE ' . $existingContent);
 
    return $response;
});
 
$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write(' AFTER');
    return $response;
});
 
$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello World');
    return $response;
});
 
$app->run();

これにより、次のHTTP応答本文が出力されます:

BEFORE Hello World AFTER

ルートミドルウェア

ルートミドルウェアは、そのルートが現在の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();
 
$mw = function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write('World');
 
    return $response;
};
 
$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello ');
 
    return $response;
})->add($mw);
 
$app->run();

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

Hello World

グループミドルウェア

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

ただし、/(domain-root)にアクセスすると、ミドルウェアが割り当てられていないため、次の出力が生成されると思います。

Hello World

ミドルウェアから変数を渡す

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

ミドルウェアでの変数の設定:

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

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

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


利用可能なミドルウェアを見つける

要求を満たすPSR-15ミドルウェアクラスがすでに作成されている場合があります。検索する非公式のリストをいくつか示します。


依存性コンテナ―

Slimは、オプションの依存性コンテナーを使用して、アプリケーションの依存関係を準備、管理、および注入します。Slimは、PHP-DI のような PSR-11 を実装するコンテナーをサポートします。

PSR-11の邦訳はこちらにあります。


PHP-DIでの使用例

依存関係コンテナを必ず提供する必要があるとは限りません。但し、そうする場合は、App を作成する前に、コンテナのインスタンスを AppFactory に与える必要があります。

<?php
use DI\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
 
require __DIR__ . '/../vendor/autoload.php';
 
// Create Container using PHP-DI
$container = new Container();
 
// Set container to create App with on AppFactory
AppFactory::setContainer($container);
$app = AppFactory::create();

そして、コンテナにサービスを追加します:

$container->set('myService', function () {
    $settings = [...];
    return new MyService($settings);
});

次のように、明示的にコンテナからからだけでなく、Slimアプリケーションルートの内からサービスをフェッチできます:

/**
 * Example GET route
 *
 * @param  ServerRequestInterface $request  PSR-7 request
 * @param  ResponseInterface      $response  PSR-7 response
 * @param  array                  $args Route parameters
 *
 * @return ResponseInterface
 */
$app->get('/foo', function (Request $request, Response $response, $args) {
    $myService = $this->get('myService');
 
    // ...do something with $myService...
 
    return $response;
});

コンテナを使用する前にサービスがコンテナに存在するかどうかをテストするには、次のように has() メソッドを使用します:

/**
 * Example GET route
 *
 * @param  ServerRequestInterface $request  PSR-7 request
 * @param  ResponseInterface      $response  PSR-7 response
 * @param  array                  $args Route parameters
 *
 * @return ResponseInterface
 */
$app->get('/foo', function (Request $request, Response $response, $args) {
    if ($this->has('myService')) {
        $myService = $this->get('myService');
    }
    return $response;
});


コンテナを介したアプリケーションの構成

コンテナに依存性が既に定義されている App を作成する場合は、AppFactory::createFromContainer() メソッドが使用できます。

例題

<?php
 
use App\Factory\MyResponseFactory;
use DI\Container;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\Factory\AppFactory;
 
require_once __DIR__ . '/../vendor/autoload.php';
 
// Create Container using PHP-DI
$container = new Container();
 
// Add custom response factory
$container->set(ResponseFactoryInterface::class, function (ContainerInterface $container) {
    return new MyResponseFactory(...);
});
 
// Configure the application via container
$app = AppFactory::createFromContainer($container);
 
// ...
 
$app->run();

サポートされているアプリの依存関係は次のとおりです: