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:.*}
とします。 -
ルートコールバック:
ルートにマッチした場合の処理用コールバックルーチン。
any
とmap
を使ったルーティングの例です。
// 全ての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です。
セッションも作ってるみたいで、レポジトリは存在します。けど、まだ空っぽです。
ビュー
ビュー、あるいはテンプレートだと、
がありました。