Slim 3 Frameworkリリースされたので、ざっと紹介

  • 106
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Slim 3 Framework

PHPのSlimフレームワークのバージョン3がリリースされたので、簡単に紹介してみます。

【2016/01/16:主にルーティングについて追記・変更しました】

SlimフレームワークはPHPのマイクロ・フレームワークのひとつです。今回のバージョン3は、

  • PSR-7とミドルウェア構造の採用、
  • インターフェースに依存することによる拡張性の高さ、
  • DIコンテナの利用(デフォルトはPimple)、
  • でも簡単に使い始められる

のが特徴です。

何やら難しそうな単語が特徴に並んでますが、Slimのユーザーガイド読めば、すぐ使えるぐらい簡単です。なので難しそうな特徴を中心に紹介してみます。

簡単に使える

本当に簡単に使えます。Slimのユーザーガイドからコードを貼り付けて、ちょっとコードを削っただけですが、これだけのコードで動きます。

<?php
require dirname(__DIR__).'/vendor/autoload.php';

$app = new Slim\App();
$app->get('/hello/{name}', function ($request, $response, $args) {
    $response->getBody()->write("Hello, ".$args['name']);
    return $response;
});
$app->run();

設定などの置き場所

とは言うものの、プロダクションとして使う場合には設定やルーティングがあります。そのためにSlim Framework 3 Skelton Applicationがあります。最初から簡単な設定ファイルがあるので、ここ開発を始めるのが楽だと思います。

始め方ですが、ドキュメントに書いてある通りです。

php composer.phar create-project slim/slim-skeleton [my-app-name]

[my-app-name]のところに、アプリケーションを作るディレクトリ名を入れて下さい。

  • logs/ディレクトリにサーバーの書き込み権限をつける。
  • public/をルートディレクトリと指定する。

で動くはず。

ルーティング

ルーティングの基本は、ユーザーガイドのRoutingに詳しく乗っているので、サラリと紹介します。

$app->{メソード名}('ルートパターン', {ルートコールバック});
  • メソード名
    get, postなどのHTTPメソード。anyを使うと全てのメソッドでマッチします。複数のメソードにマッチさせたい場合はmapを使います。
  • ルートパターン
    マッチするルートパターン。最初のコードのようにネームホルダーを使えます。デフォルトでは、(スラッシュを除いて)何にでもマッチするので、文字種を絞りたい場合は'/hello/{id:[0-9]+}'とできます。逆にスラッシュも含める場合は/hello/{path:.*}とします。
  • ルートコールバック
    ルートにマッチした場合の処理用コールバックルーチン。

anymapを使ったルーティングの例です。

// 全てのHTTPメソッドにマッチ。
$app->any('/any/method', 'AnyMethodHandler');

// マッチするHTTPメソッドを配列指定。
$app->map(['get', 'post'], '/get/or/post', 'GetOrPostHandler');

Slim3では、PHPでは結構有名なnikic/fastrouteをルーターとして使ってます。ルート数が増えても、マッチング処理のスピードが(ほぼ)変わらない、という特徴を持ってます。

ルート名

ルートに名前をつけて、後で呼び出すことも出来ます。

// ルートに名前をつけておいて、
$app->get('/hello/{name}', function ($req, $res, $args) {
    echo 'Hello ', $args['name'];
})->setName('hello');

// 後で名前でパスを呼び出す。
$app->router->pathFor('hello', ['name' => 'slim']);

詳細はユーザーガイドのRoute Namesにあります。コード補完が効きそうですね。

Groups

複数のルートをグループとして設定することが出来ます。

$app->group('/users/{id:[0-9]+}', function () {
    $this->map([], '', function ($req, $res, $next) {
        // ユーザーに対して何か処理をする。
    });
    $this->get('/reset-password', function ($req, $res, $next) {
        // パスワードリセット処理
        // ルートは /user/####/reset-pasword
    });
});

こちらも詳細はユーザーガイドのRoute groupsにあります。

ルートコールバック

ルーティングがマッチしたら、リクエストを処理するルーチンのことを「Route Callbacks」と呼んでます。

PSR-7オブジェクト

コールバックは、PSR-7のリクエストとレスポンスのオブジェクトを受け取ります。レスポンスに必要な情報を設定します。リダイレクトの例を書くと、

$app->get('/redirected', function($req, $res, $args) {
    return $res
        ->withStatus(302)
        ->withHeader('Location', '/back');
});

設定ができたら、最後にレスポンスオブジェクトを返します。

ドキュメントを読むと、ハンドラーからHTMLなどを出力するには、

  • いきなりechoするか、
  • 内容を書き込んだレスポンスを返す、

とありましたが、ちゃんとレスポンスを返したほうがテストしやすいと思います。

コールバックで受け取る変数の並びです。標準では、次のようになってます。

function(
    \Psr\Http\Message\ServerRequestInterface $request, 
    \Psr\Http\Message\ResponseInterface $response, 
    array $args);

最後の$argsには、ネームホールダーにマッチした値が入ってきます。'/user/{name}'の「name」のところです。

コンテナを使う場合

ルートコールバックをコンテナを使って生成することもできます。例えばMyHandler::classという名前で、ファクトリをコンテナに設定しておきます。

# dependencies.phpなどで
$app->getContainer()[MyHandler::class] = 
    function(ContainerInterfrace $c) {
        return new MyHandler(new MyDependency);
    };

ルートを設定するときに、同じくMyHandler::class

# routes.phpなどで
$app->get('/my/handler', MyHandler::class);

すると、MyHandlerクラスを生成してから__invokeメソッドを呼び出します。

コールバック関数の引数を変更する方法

実は、ユーザーガイドには引数を変更する方法が書いてあります。Slim\Interfaces\InvocationStrategyInterfaceを実装すれば、自分好みの動きに変更できるとあります。

例えば、こう。

class MyInvocation implements InvocationStrategyInterface
{
    public function __invoke(
        callable $callable,
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $routeArguments
    ) {
        // ネームホールダーはクエリとして扱う。
        $query = $request->getQueryParams();
        $query = array_merge($query, $routeArguments);
        $request = $request->withQueryParams($query);

        return call_user_func($callable, $request, $response);
    }
}

こんなクラスを作っておきます。これを$app作成時に、

$app = new \Slim\App([
    'foundHandler' => function() {
        return new MyInvocation();
    } 
]);

とすると、先の例だとこうなります。

$app->get('/hello/{name}', function ($req, $res) {
    echo 'Hello ', $req->getQueryParams()['name'];
});

コードを見比べると少々バカバカしい例ですが、すでにコントローラーがある場合などに便利かもしれません。

コールバックに何が使えるか?

実はコールバックには色々なパターンが使えます。標準でコールバックとして使えるパターンを調べました。

Slim3では、文字列からオブジェクトに解決するのは、CallableResolverというクラスを使っています。コードを見ると、結構複雑で、色々なパターンに対応しているのが分かります。まとめると、こんなところでしょうか。

  • 実行可能なオブジェクト:
    • クロージャー、
    • __invoke可能なオブジェクト、
  • 文字列:
    • コンテナから取り出す文字列(コンテナ名)、
    • クラス名、
    • コンテナ名:メソッド名の文字列、
    • クラス名:メソッド名の文字列、

が使えます。文字列の場合は、コンテナから取り出してみて、無ければクラス名としてnewする感じですね。つまりクラス名の場合は

class MyCallback {
    function __construct(ContainerInterface $c) {
        $this->service = $c->get('MyService'); // SL!!!
    }
}

と書けます!

コンテナーが入ってくるので、何でもできちゃいますね。この例はサービスロケーターという使い方なので、あちこちから突っ込まれる方法です。

CallableResolverInterface: 自作で解決

一方、自作のResolverを使うことも可能です。CallableResolverInterfaceを実装したクラスを作ります。

class MyResolver implements CallableResolverInterface {
    public function resolve($toResolve) {
        return $toResolve::forge();
    }
}
$app = new Slim\App([
    'callableResolver' => function() {
        return new MyResolver();
    } 
]);

newする代わりにスタティックのforgeメソッドを使ってインスタンスを生成します。

ミドルウェア

Slim3の特徴の一つが、PSR-7のミドルウェアを使っていることによる拡張性の高さです。PSR-7を使っているので、フレームワークに限定されずに様々なミドルウェアを使うことが出来ます。

ミドルウェアの構造

PHPのPSR-7のミドルウェアといえば、この形です。

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

function __invoke(Request $request, Response $response, callable $next) {
    /**
     * 必要な処理を行う。
     */
    $response = $next($request, $response); // 次のミドルウェアを呼び出す。
    /**
     * 戻ってきたレスポンスを使った処理も可能。
     */

    return $response; // レスポンスは「必ず」返しましょう。
};

もう標準化してしまえばいいのに…

ミドルウェアとして使えるのは、クロージャー、invoke可能なオブジェクト、クラス名… あれ?どこかで見たことのあるリストですね。先のルートコールバックと同じく、ミドルウェアもResolverを使って解決しています。

実は__invokeを実装してなくても動く、はず(実証まだです)。

指定できる箇所

ミドルウェアを指定できるのは、Slimアプリケーション、ルート、あるいはルートグループ、となります。

// アプリ全体のミドルウェア。
$app->add(new MyMiddleware);

// 特定のルートのみでミドルウェアを実行する。
$app->get('/middle', function($req, $res) {return $res;})
    ->add(new MyRouteMiddleware);

// 特定のルートグループでミドルウェアを実行する。
$app->group(function() {})
    ->add(new MyGroupMiddleware);

実行順

複数のミドルウェアを使う場合、最後に登録したミドルウェアから順番に実行されます。

$app = new Slim\App();
$app->add(function($req, $res, $next) {
    $res->getBody()->write("middleware #1\n");
    return $next($req, $res);
});
$app->add(function($req, $res, $next) {
    $res->getBody()->write("middleware #2\n");
    return $next($req, $res);
});
$app->get('/hello/Slim', function ($request, $response, $args) {
    $response->getBody()->write("Hello, Slim");

    return $response->withHeader('Content-Type', 'text/plain');
});

の場合、出力は

middleware #2
middleware #1
Hello, Slim

となります。これは、ちょっと自分の感覚と逆なので注意が必要です。

マイクロ・フレームワーク

力尽きてきたので、ここらへんで切り上げます。

基本はマイクロ・フレームワークなので、必要な機能は自分で追加することになります。が、SlimPHPのレポジトリには、基本的な機能がいくつか存在します。

アドオン・ミドルウェア

  • Slim-Csrf :
    クロスサイトリソースフォーリッジ対策用のトークンの生成とバリデーションを行うミドルウェアです。

  • Slim-HttpCache :
    HttpのEtagを使ったレスポンスキャッシュを実装しています。ただし、まだ開発中のようで、(2012/12/14現在で)バージョン0.3.0です。

セッションも作ってるみたいで、レポジトリは存在します。けど、まだ空っぽです。

ビュー

ビュー、あるいはテンプレートだと、

がありました。