読者です 読者をやめる 読者になる 読者になる

k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

Silex製アプリケーションをSlimフレームワークで書き換えてみた

系図管理システム(仮)というものをSilex + SQLiteで作成していたんですが、SlimというSilex同様のREST APIを提供するマイクロフレームワークに書き換えてみました。
(SilexでもDoctrine2ではなくRedbean、TwigではなくPHPTALを使ってたので、フレームワークの利用範囲的にほぼ等価交換できるのではないかとの思いつきです)

Slimの公式サイト http://www.slimframework.com/ には、Feature Listとして以下の項目が挙げられています。

  • HTTP routing
  • Named routes
  • Route passing
  • Route redirects
  • Route halting
  • Middleware & Hooks
  • Custom views
  • HTTP caching
  • Signed cookies
  • Custom 404 page
  • Custom 500 page
  • Error handling
  • Logging

Logging以外はSilexと同じような感じでしょうか?
なお、動かしたのはstable版ではなくdevelop版です。http://www.slimframework.com/documentation/develop

Silex版のコード。いわゆるCRUDいっこぶんです。(DはUとほとんど同じなので省略)

Slim版のコード。

Silexの場合はデフォルトのオートローダとしてSymfonyコンポーネントのUniversalClassLoaderが利用されるんですが、Slimにはそういった機能がないので、自作のPSR-0対応クラスローダを利用しました。
これ https://gist.github.com/1707998

同等機能の別名メソッド(上がSilex, 下がSlim)

全てのリクエストメソッドにマッチ

$app->match()
$app->map()

特定のリクエストメソッドのみ受け付ける

$app->method('GET|POST')
$app->via('GET', 'POST')

URLパラメータの書式指定

$app->assert('id', '\d+')
$app->conditions(array('id' => '\d+'));

GETパラメータ取得

$request->query->get('name')
$request->get('name')

POSTパラメータ取得

$request->request->get('name')
$request->post('name')

全てのPOSTパラメータを配列で取得

$request->request->all()
$request->post()

Silexと比べて、不便に感じたところ。

  • クラスファイルの命名規則(というか配置)がPSR-0に準拠していない。Slimクラス(Slim/Slim.php) Slim_Http_Requestクラス(Slim/Http/Request.php) Slimクラス以外は問題ないんだけど…
  • リクエストパラメータに値があるかどうかを返すメソッドがない
  • いわゆるフラッシュメッセージ機能がViewクラス前提で書かれており、ユーザースクリプトから参照できない。このためにViewクラスを継承したPhpTalViewクラスを定義しました。

PhpTalViewクラス。たったこれだけのコードなんですが。

Slimコンストラクタのオプション引数viewにViewクラス名を、templates.pathにテンプレートファイル設置ディレクトリを指定することで、Slim::render()実行時にViewクラスが利用されます。


その他、いくつか気になるところのソースを追ってみました。

リクエスト変数について

リクエスト変数は Slim::request()で取得できるSlim_Http_Requestクラスが扱います。
Slim_Http_Request::get()やSlim_Http_Request::post()といったメソッドを使うわけですが、これらのメソッドでは$_POSTや$_GETを扱わず、Slim_Environmentクラスを通じて提供されるQUERY_STRING変数やslim.input変数が利用され、get()の場合はslim.request.form_hash変数、post()の場合はslim.request.query_hash変数としてSlim_Environmentが管理する配列にセットされます。
これらの元データはSlim_Environment::prepare()メソッドで設定されますが、QUERY_STRINGは見ての通り$_SERVERから、slim.inputはphp://inputストリームで取得できる、いわゆる生のPOSTデータから設定されています。
Slim_Environment::mock()を使って配列からセット可能となっており、テスタビリティの観点から興味深い実装ですが、後述のMiddlewareでリクエスト変数へのフィルタ処理(不要な制御コード削除など)を行う場合は、この辺の仕様を把握しておく必要がありそうです。

Slim_Environment https://github.com/codeguy/Slim/blob/develop/Slim/Environment.php
Slim_Http_Request https://github.com/codeguy/Slim/blob/develop/Slim/Http/Request.php

フックについて

Slim::hook()メソッドを使って、用意された6箇所にフック処理を登録できます。
フックはslim.before → slim.before.router → slim.before.dispatch → slim.after.dispatch → slim.after.router → slim.after の順に処理されます。
(試していませんが、Slim::hook()の第3引数に数値で優先順位を指定することで、同じポイントに複数のフックを登録できるようです)
上記サンプルコードでは、slim.before.dispatchで全画面共通のテンプレート変数(というかただの配列ですが…)を設定しています。

詳しくは公式ドキュメント http://www.slimframework.com/documentation/develop#hooks にて

Middlewareについて

develop版からはRubyのRackに習ったMiddleware http://www.slimframework.com/documentation/develop#middleware というフィルタ機能?の仕組みが用意されています。
Slim_Middleware_Interfaceを実装したクラスを定義し、Slim::add()でクラス名を指定すればインスタンスがmiddlewareスタックに登録され、Slim::run()実行時に呼ばれます。
Middlewareはコンストラクタの第1引数でMiddlewareを受け取り、call()メソッドで引数から受け取ったenvironment配列を、コンストラクタで受け取ったMiddlewareのcall()に渡して実行し、第1要素にHTTPステータスコード、第2要素にSlim_Http_Header、第3要素にレスポンスボディの文字列がセットされた配列を返すことで、Slim::run()から連鎖的にMiddlewareのcall()メソッドが実行される仕組みとなっています。
(Slim自身もSlim_Middleware_Interfaceは継承していないものの、コンストラクタで自身をmiddlewareスタックに登録し、同様のcall()メソッドを実装している)

要するに、Middlewareが扱うのは、環境変数(Slim_Environmentが管理するstaticな配列)、HTTPステータスコード、HTTPヘッダ(Slim_Http_Headerクラス)、レスポンスボディということです。
(後述のDebugging機能の他、Flash MessagingやSecure Sessionsといった機能もMiddlewareとして実装されている)

公式ドキュメント http://www.slimframework.com/documentation/develop#examplemiddleware には、例としてレスポンスボディを大文字変換するMiddlewareのコードが紹介されています。

エラー・例外ハンドラとdebugフラグについて

PHPエラーは、Slimのコンストラクタで登録されるエラーハンドラによって、ErrorExceptionに変換されてスローされます。
Silexと同様、例外ハンドラはSlim::error()で登録できますが、debugフラグが有効な場合、Slim::run()メソッド実行後にスローされた例外は例外ハンドラで処理されず、Slim_Middleware_PrettyExceptions(Slimコンストラクタで強制的に有効にされるMiddleware)によってキャッチされ、整形表示されます。

公式ドキュメント http://www.slimframework.com/documentation/develop#settings-debug より

If true, Slim will display debugging information for errors and exceptions.
If false, Slim will instead invoke the default or custom error handler, passing the exception into the handler as the first and only argument.

開発用エラー画面か一般向けエラー画面かを一目で分かるように、といった配慮でしょうか。
set_exception_handler()を使って例外トレースを自作関数で整形表示しようとしたところ無視されてしまったので、ちょっと慌ててしまいました。
フレームワーク使ってるんだしこの辺は自分でするなってことでしょうか…Exception::getTraceAsString()が呼ばれてるだけなので、メソッドの引数が省略されて嫌なんですが)

また、Silexではルーティングに失敗した場合やURLパラメータの書式チェックに弾かれた場合、またリクエストハンドラ内でApplication::abort()が呼ばれた場合など、HttpExceptionがスローされ全てApplication::error()で定義したエラーハンドラが呼ばれます。
一方、Slimではルーティングに失敗した場合はSlim::error()で定義したハンドラではなくSlim::notFound()という専用ハンドラが呼ばれ、リクエストハンドラ内でエラーハンドラを呼びたい場合はSlim::error()を読んで引数に例外クラスのインスタンスを与える仕様になっていて、ちょっと分かりづらいです。


色々と不満も挙げましたが、ドキュメントはきちんと整備されてますし(英語ですが)、Cookieの暗号化やHTTPキャッシュといった便利機能も標準でサポートされています。
PHP5.2環境でSilex的に作りたい場合は良い選択肢になるんじゃないでしょうか。