コントローラ
今日は、Lithiumのコントローラのドキュメントを読みました。
昨日は、ソースコードが無かったので概念を理解するだけに努めましたが、今日は手を動かして調べれましたので時間は掛かりましたが楽しかったです。
参照元:03_request_response/01_controllers.wiki
コントローラ
MVCアプリケーションにおいて、コントローラの役割はユーザの入力を受け取り、特定のアクションでリクエストを処理することになります。コントローラのアクションはアクションロジックは記述しますが、リクエストサイクルにおいてはモデルが馬車馬のようになります。
このガイドを読み、実際の機能の多くはモデルやその他のクラスで展開し、コントローラのアクションを整理し続けるように心がけて下さい。(訳者注:skinny controller fat modelってことが言いたいんだと思います)
このガイドはLithiumのコントローラの機能、振る舞い、そしてベストプラクティスを紹介します。
コントローラのアクション
Lithiumのコントローラはapp/controllersディレクトリ内に格納され、lithium\action\Controllerコアクラスを継承します。簡単なコントローラを作成してみましょう。コントローラはそれが管理するオブジェクトの後に名前が付きます。
新しくUsersControllerを作成してみます。下記のようにapp/controllers/UsersController.phpというファイル名で作成して下さい。
ドキュメントでは空のアクションですが、それだと実際にアクションが本当に実行されているのか分からないので、僕はechoで文字列を出力してみました。
ビューが無いと例外を投げますので_renderプロパティのautoをfalseにしています。"render"とか"auto"をgrepしてとりあえず例外を投げないようにしてみましたが、この使い方でいいのかどうか分からないですが、いちおう要件を満たしてますし、サンプルですからよしとします。
vi app/controllers/UsersController.php
<?php namespace app\controllers; class UsersController extends \lithium\action\Controller { public function index() { $this->_render['auto'] = false; echo 'Hello!'; } } ?>
コントローラではpublicな関数がLithiumコアによってルーティング可能なアクションとされます。実際にLithiumのデフォルトルーティングルールはこれらのアクションを即座にブラウザからアクセス可能にします。(この場合は/users/indexになります)
index()アクションは特別なアクションとなります。アクション名が指定されなかった場合、Lithiumはindexアクションを呼び出します。例えば閲覧者がhttp://example.com/usersでアプリケーションにアクセスして来た場合、index()アクションが呼び出されます。他のコントローラのアクションも全て少なくともデフォルトのルーティングによってアクセスされます。
例えば/users/viewというアクセスがある場合には、下記のようにコントローラのアクションを作成することができます。
<?php namespace app\controllers; class UsersController extends \lithium\action\Controller { public function index() { $this->_render['auto'] = false; echo 'Hello!'; } public function view() { $this->_render['auto'] = false; echo 'Goodbye!'; } }
個人的にpublicでない場合はどうなるのか気になったので試してみました。
<?php namespace app\controllers; class UsersController extends \lithium\action\Controller { public function index() { $this->_render['auto'] = false; echo 'Hello!'; } public function view() { $this->_render['auto'] = false; echo 'Goodbye!'; } protected function foo() { $this->_render['auto'] = false; echo 'Protected!'; } private function bar() { $this->_render['auto'] = false; echo 'Private!'; } } ?>
protectedの場合は問題いようですが、privateは例外を投げるようです。ドキュメントで書いてある内容とは少し違うとは思いますが、まあそのうち詳細も分かってくるでしょう。
リクエストパラメータにアクセスする
アプリケーションにおいてコントローラの重要な役割は、正しいレスポンスを表示する為にリクエストデータを処理することです。このセクションでは、コントローラのアクションでデータを取得するのに役に立つ例をいくつか紹介します。
GETパラメータ
データを処理するのにユーザフレンドリーなやり方の一つはURLを通して取得することです。GETリクエストで渡された情報は好みにもよりますが、いろんなやり方で取得できます。
GETデータを取得する最も簡単はやり方は、アクション名の次にあるURLセグメントがコントローラアクションの引数にマッピングされたGETデータを取得することです。例で説明します。
http://example.com/users/view/1 --> UsersController::view($userId); http://example.com/posts/show/using+controllers/8384/ --> PostsController::show($title, $postId);
このやり方で渡されたGET情報はリクエストオブジェクトを介してアクセスすることもできます。
http://example.com/users/view/1 --> $this->request->args[0]
私たちはクリアなURLベースの変数を使うことを推奨しますが、生のクエリストリング変数として渡されたGETパラメータもリクエストの属性として利用できます。
http://example.com/users/view?userId=1 --> $this->request->query['userId']
それではまた、実際にやってみます。
POSTデータ
POSTデータもリクエストオブジェクトから取得します。フォームのフィールドデータを参照しているページのinput要素から収集した後、配列内にあるリクエストオブジェクトをキーで指定します。例えばこのような要素を含むHTMLフォームがあるとします。
<input type="text" name="title" value="How to Win Friends and Influence People" /> <input type="text" name="category" value="Self-Help" />
コントローラにサブミットされた後で簡単にこれらの変数へのアクセスできます。
$this->request->data['title']; $this->request->data['category'];
リクエストのフローを制御する
時々、コントローラアクションは送られてくるリクエストよって転換、再ルート、もしくは自動的にビューレイヤを設定したいことがあります。リクエストフローの処理に役立つ多くのコントローラメソッドがあります。
リダイレクト
コントローラでフローを制御するもっとも簡単な形式はリダイレクトになります。ユーザを別のURLにリダイレクトするとアクションが実行されるのは共通です。この制御はコントローラのredirect()メソッドで行います。リクエストをリダイレクトするコントローラアクションの例は下記の通りです。
public function register() { // Validate and save user data POSTed to the // controller action found in $this->request->data... $this->redirect('Users::welcome'); }
指定されたURLはアプリケーションと相対的にもできますし、外部のリソースを指定することもできます。redirect()関数もHTTPのステータスヘッダーをセットしたりredirectした後でexit()するかどうか決定することができる$optionsパラメータがあります。より詳しい情報はlithium\action\Controller::redirect() の
APIをチェックしてみて下さい。
例外とエラーハンドリング
コントローラのロジックをフィルタリングする
コントローラのロジックをフィルタリングすることは、Lithiumの初心者には少しトリッキーだとは思いますが、ディスパッチサイクルを柔軟でエレガントな方法で実行できます。
リクエストがLithiumアプリケーションに送られて来た時、ディスパッチャはまずどのコントローラをロードするかを決定する為にRouterクラスを使います。目的のコントローラが識別できたら、新しいコントローラオブジェクトを生成し、起動します。
この最後の起動ステップはディスパッチャの_callable()メソッドの中の処理を実行します。この仕組により、コントローラアクションの前後にロジックを注入したいフィルターは_callable()に対してよく行われます。そのようにすることで正しいコントローラインスタンスと同様に、通常利用可能なパラメータをコントローラにアクセスさせることができます。
(注)ディスパッチャのrunメソッドに対していくつかのフィルターを見たことがあるかもしれません。多くの場合、これはうまく動作するはずです。_callable()を使ったやり方との違いはあとでコントローラのインスタンス自体へのアクセスになります。
Lithiumのフォームにあるg11nフィルタは役に立つ列です。説明の為にシンプルに少し編集したバージョンが下記のソースになります。
use lithium\action\Dispatcher;
Dispatcher::applyFilter('_callable', function($self, $params, $chain) {
$request = $params['request'];
$controller = $chain->next($self, $params, $chain);
if (!$request->locale) {
$request->params['locale'] = Locale::preferred($request);
}
Environment::set(Environment::get(), array('locale' => $request->locale));
return $controller;
});
この場合、あなたは、use/asシンタックスを使いたくなるかもしれません。
use lithium\action\Dispatcher as ActionDispatcher;
use lithium\console\Dispatcher as ConsoleDispatcher;
ActionDispatcher::applyFilter(...);
ConsoleDispatcher::applyFilter(...);
Litiumのリクエスト
今日はLithiumのリクエストのドキュメントを読みました。
フレームワークを使い始めたばかりの時にサンプル等や書籍を読んだりするのも良いことですが、フロントコントローラからざっくりと(分からなくてもいいですので)処理をまず追ってみることはフレームワークの思想を理解したり、空気を読む良い習慣だと個人的には思っています。
そういったことをしているうちに「どのようなコーディング基準で書かれているか」とか「軽量なのかどうか」「良いフレームワークなのかそうでないのか」といったことがなんとなく分かってきます。このドキュメントはそういったシチュエーションで役に立つ良いドキュメントだと思います。(個人的にはドキュメントのタイトルは「Lithium Internal」みたいな感じがマッチするのではないかと思っています)
いつものように英語がよく分かってない僕が自分の解釈で適当に読んでますので正確な情報が知りたい方は参照元を読んでください。
参照元:03_request_response / 00_quickstart_typical_request.wiki
Lithiumでアプリケーションを構築するとき、どのようにリクエストが処理されているかを理解していることは重要なことです。それによってシステムに本来備わっている機能を使うことができますし、開発中に起こるかもしれない問題のいくつかをトラブルシューティング出来るかもしれないからです。このドキュメントを読めばどのようにコントローラとビューとコンテキストがLithiumの内部で動作しているかを実際に調べる前にそういったことを俯瞰できるようになります。
初期リクエストとブートストラッピング
Lithiumアプリケーションはいろいろな設定方法がありますが、最も安全なのはアプリケーション内の/app/webrootを公開することです。そのディレクトリはアプリケーションのルートディレクトリになりますが、そうしなくても(app/webrootを公開しなくても)リクエストは/app/webroot/index.phpが処理されるまで転送されます。
このディレクトリのindexファイルは2つのことを行います。まずLithiumのメインboostrapファイル(その中の関連するboostrapファイルも)ロードします。次にディスパッチャをインスタンス化しrun()メソッドの引数にnewしたRequestオブジェクトを渡します。RequestオブジェクトはGET/POST/環境変数データをの全てを収集し、リクエストのライフサイクルを通してそういった情報の情報源となります。
リクエストのディスパッティングとルーティング
ディスパッチャはリクエストを保持しています。ディスパッチャは処理を続ける為にルータに問合せます。この処理は基本的にはそれぞれの設定したルートに対して定義された順にリクエストをマッチさせます。マッチしたらルータはコントローラクラスを探すのに必要なパラメータ情報を返し、それに対するメソッド呼び出しにディスパッチします。
パラメータが渡されるとディスパッチャはそれらを使い正しいコントローラオブジェクトをインスタンス化し、起動します。それぞれのコントローラにおける起動ロジックはレスポンスを処理する為に呼び出されるアクションに情報を渡します。
アクションのレンダリング
それぞれのコントローラのアクションはアプリケーションのインターフェースを作る為に一連のビジネスロジックが記述されます。データをフェッチしたり、バリデート、サニタイズなどの処理をする為に、モデルや他のクラスとのやりとりがあるかもしれません。ビューレイヤに渡すデータが用意できたら、アクションは$this->set()メソッドもしくは連想配列をreturnすることでビューに私ます。レスポンスタイプに関する情報と一緒にMediaクラスへデータが渡されます。
Mediaクラス
Mediaクラスはコンテンツタイプのマッピング(コンテンツタイプとファイル拡張子間のマッピング)、静的なコンテンツの扱いを促進します。また異なるフォーマットでの出力をどのようにフレームワークが処理するかをグローバルに設定します。
コントローラのアクションデータとリクエストがMediaクラスに渡されると正しいレスポンスタイプでリクエストをマッチさせ一致するビューレイヤをトリガーします。一般的には、これはHTMLビューをHTMLレイアウト内にレンダリングします。
Mediaは非常に柔軟でレスポンスをJSONシリアライゼーション、XMLフォーマットにしたり、オーディオファイルのコンテンツを出力バッファに書きこむことができます。
コンテンツタイプのマッピングは通常boostrapファイルにてMedia::type()によって行われます。
Lithiumフィルターガイド
ようやくフィルターまで辿りつけました。Lithiumではこのフィルター機能が主要な機能になっているようですので、ここを理解しないとLithium使っている意味はないと思われます。
しかし今回は難しかったです。概念だけでなく英語も僕にとっては非常に難しくfilterableやapply Filterをどう解釈するのか?その辺がすごい曖昧です。なんどもこのドキュメントは熟読する必要があるように思えます。
アスペクト指向とはいってますが、「アドバイス」だの「ポイントカット」などのようなキーワードは出てきません。システム開発にある問題(横断的関心)を解決する為に概念だけを導入し一般的なアスペクト指向の実装と同じ方法は取っていないということなのだと思います。
参照元:02_lithium_basics/02_filters.wiki
このガイドはLithiumアプリケーションでフィルターを作成する導入コースとなります。フィルターは本質的にはアプリケーションのクラス間のやりとりにイベントドリブンを導入した効果的な方法になります。フィルターは、メインの処理フローにロジックを注入できると同時にAPIをクリーンに保ちと同時にクラス間の結合度を下げることができ、ある種の統制された発行/承認システムになります。
フィルターが必要な時はどんな状況が考えられるでしょうか。いくつか例を上げてみましょう。
- データベースのcreatedカラムに現在の日付を格納するbefore fileter
- あるリクエストに割り込みHTMLレスポンスではなく生のファイルコンテンツを配送する場合
- ディスパッチされたリクエストのプロファイルの計測をラッピングする
- ユーザが認証済みかどうかをチェックする場合
- アプリケーションの様々なイベントをロギングする時
- キャッシュされているかどうかチェックし、キャッシュしていなければバックエンドでキャッシュに書き込みだ後でその結果を返す処理をカプセル化したcacheフィルターの作成
このガイドを最後までよめば、フィルターの背景にある理論とLithiumでフィルター可能なロジックを識別したり、自作のフィルターメソッドを作成したりといったベストプラクティスが分かるようになるはずです。
アスペクト指向プログラミング
Lithiumのフィルターシステムはアスペクト指向プログラミング(AOP)のコンセプトからインスパイアされました。AOPではアスペクトはアプリケーションの至る所に散らばるをロジックの断片のことを意味します。通常、システムと直接関係ないにも関わらずアプリケーションの多くの異なる箇所で使われる一連のロジックになります。
よくあるアスペクトの例としてロギングや認証などが上げられます。それらはどちらもアプリケーションの多くの場所で使われ、うまくまとめることができなければ保守が広範囲でトリッキーになってしまいがちです。それだけでなくこれらのロジックは本質的にはビジネスロジックを作成するコードではありません。言い換えますと「横断的関心」といえます。
フィルターの構造
簡単に言うと、フィルターを適用できるメソッドはメインロジックをクロージャ内にカプセル化することで作成されます。クロージャはそれからフィルターチェーンに管理されるようにObject::_filter()メソッド(スタティックオブジェクトの場合はStaticObject::_filter()メソッド)に渡されます。
カスタムフィルターをフィルターを適用できるメソッドに適用するにはObjectクラスのapplyFilter()メソッドを呼び出すことで行えます。最初の引数はフィルターするメソッドの名前を指定し、2番目の引数はfilterにさせたいことを定義しているクロージャになります。次のセクションでどのようにフィルターを適用し作成するのか理解できる例題を使って説明していきます。
フィルターを認証に使用する
よりフィルターに慣れてもらう為、まずはフィルターを適用してみましょう。
フィルターアプリケーションを配置する場所はフィルタリングするものに依存しますが、bootstrapファイルはよく使われる場所です。
Lithiumアプリケーションにフィルターを追加するケースとしてユーザ認証をチェックすることにしましょう。簡単なフィルターをapp/bootstrap/session.phpファイルに設定します。
(session.phpには既にセッションストレージと認証の設定が既にあることに気づくでしょう)
ここでの想定はディスパッチャがリクエストを受け取る際に認証ロジックを注入したいこととします。Dispacher::runメソッドはフィルターが適用可能ですのでフィルタを適用してみましょう。
use lithium\action\Dispatcher;
Dispatcher::applyFilter('run', function($self, $params, $chain) {
return $chain->next($self, $params, $chain);
});
これはフィルターを適用する基本的な構造になります。クラスもしくはオブジェクトでapplyFilterクラスメソッドを呼び出します。そのメソッドにフィルターしたいメソッド名とフィルターロジックを実装したクロージャを渡します。クロージャで関係のあるパラメータを説明します。
- $self:フィルターがオブジェクトインスタンスに適用された場合は、$selfはそのインスタンスになります。スタティッククラスに適用された場合は、$selfはクラス名(ネームスペースを含んだ)になります
- $params:メソッドに渡されるパラメータの連想配列。次のメソッドの前にこれらのパラメータを編集したり、インスペクトしたりできます
- $chain:lithium\util\collection\Filtersのインスタンスで実行される順にフィルターのリストを保持しています。$chain内の最後のフィルターはオリジナルのメソッド実装したいになります。(後で詳しく説明します)
最初のパラメータである$selfはフィルタリングするオブジェクトで他のメソッドもしくはプロパティを使うことが出来るように用意されています。
次の$paramsは、オリジナルメソッドの実装がアクセスするパラメータへのアクセスを渡します。ロジックの大半はこれらのパラメータにある値にアクセスしたり編集したりします。
最後のパラメータは重要です。上記のように、すぐにクロージャはフィルターチェーンの順に次のロジックの結果を返します。フィルターロジックのどこかでこれをインクルードすることになります。この呼び出しもまた重要です。あるメソッドの後に実行させたいフィルターを作成している場合、カスタムフィルターは実行後next()呼び出しの後に実行されるはずです。
SomeClass::applyFilter('methodName', function($self, $params, $chain) {
$result = $chain->next($self, $params, $chain);
// Custom logic goes here.
});
それでは、これらのパラメータを認証の作成に使ってみましょう。Dispatcher::_callable()のパラメータがあるのでしたら、これらの3つのパラメータを受け取る
フィルターの設計において、フックするメソッドとフレームワークのライフサイクルのどこでフィットするかはっきり理解しておくことは重要です。_callable()メソッドはファプリケーションのルートが処理された後ですぐに起動され、ルーティングパラメータが返されます。
このメソッドの責務はルーティングパラメータを受け取り、それを呼び出し可能なリクエストを処理しレスポンスを返すオブジェクト(コントローラ)に変換することです。
このフィルターの為にコントローラオブジェクトを取得した後とDispatcher::runにレスポンスを返す前の間でメソッドをインターセプトしてみましょう。
use lithium\action\Dispatcher; use lithium\net\http\Router; use lithium\action\Response; use lithium\security\Auth; Dispatcher::applyFilter('_callable', function($self, $params, $chain) { $ctrl = $chain->next($self, $params, $chain); if (Auth::check('default')) { return $ctrl; } if (isset($ctrl->publicActions) && in_array($params['action'], $ctrl->publicActions)) { return $ctrl; } return function() use ($request) { return new Response(compact('request') + array('location' => '/users/login')); }; });
フィルターできるロジックの作成
もし、自分でコードを書いて配布しようとされているでしたら(それでしたらLithiumのLaboratoryで配布して欲しいと我々は望んでいます)、API部分をfilterできるように書きたいと思っているかもしれません。それにより他の開発者がパワフルなLithiumのフィルターシステムを利用できると同時に、作成するコードに余分なコールバックメソッドや設定オプションを書かなくて済むからです。
Lithiumにソーシャルメディアと連携する拡張機能を作成したいとしましょう。ライブラリーは情報を投稿したり収集する為にポピュラーなネットワーキングサイトへ接続する必要があります。この機能を他の開発者に(彼らのアプリケーションで)使ってもらえるようにしたいですので、ロジックのいくつかをフィルターできるようにすれば便利になるでしょう。
例えば、Twitterにコンテンツを投稿するメソッドにフィルターを追加して見ましょう。開発者により他のサービスを使ってURLを短縮するロジックをフィルターしたい、もしくはツィートの最後にハッシュタグを自動でつけたいといった要望が起こることが考えられます。
まず、簡単にやってみます。
class Twitter {
public function tweet($status, $options) {
$defaults = array('responseFormat' => 'json');
$options += $defaults;
// Twitter connection and data transfer logic goes here...
return $result;
}
}
ここでは簡単にメソッドを実装してみました。注目する点は$optionsパラメータになります。LithiumのAPIはパラメータの数をoptionsという連想配列を使って少なくすることを推奨しています。同じコーディング規約でコーディングすると他の開発者に(APIの仕様が)分かりやすくなります。ここではメソッドのメインロジックの前に下処理が実行され、デフォルト値を提供されているoptionsにマージしているだけです。
一旦コアのロジックができましたので、それをフィルター出来るようにするにはわりと簡単です。やろうとしている事は$this->filter()を処理するクロージャでコアの実装をラッピングし、その呼び出し結果を返すことになります。ここではこのように実装しました。
class Twitter extends \lithium\core\Object { public function tweet($status, $options) { $defaults = array('responseFormat' => 'json'); $options += $defaults; $params = compact('status', 'options'); return $this->_filter(__METHOD__, $params, function($self, $params) { // Twitter connection and data transfer logic goes here... return $result; }); } }
filter()メソッドは3つの引数を受け取ります。最初の引数はメソッドの名前、2つ目はオリジナルのメソッドの実装が受け取るパラメータの配列になります。最後のパラメータはオリジナルのメソッド実装が入ったクロージャになります。これだけです。簡単な処理でしたらこれだけで済みます。
$this->_filter()を呼び出す為にクラスはlithium\core\Object(もしくはそのクラスのサブクラス)を継承する必要があることに注意して下さい。
少し違った実装になるいくつかのシチュエーションがあります。1つの例は、メソッドがスタティックにアクセスされた場合です。tweet()メソッドにこの例を適用して説明します(親クラスが変わったことに注意して下さい)
class Twitter extends \lithium\core\StaticObject { public static function tweet($status, $options) { $defaults = array('responseFormat' => 'json'); $options += $defaults; $params = compact('status', 'options'); return static::_filter(__FUNCTION__, $params, function($self, $params) { // Twitter connection and data transfer logic goes here... return $result; }); } }
ほぼ同じですが、$this->_filter()ではなくstatic::_filter()を呼び出しています。
もう一つの例は(少しトリッキーになってしまうかもしれませんが)、もしメインメソッドの実装がプロパティやメソッドを使用する必要が生じた場合、クロージャでラップされたものはスコープ外になってしまうことです。(言い換えればprivateやprotectedメンバー)
例えば、tweetメソッドのロジックがTwitterクラスのプロパティのOAuthというクラスを利用する場合はどうなるでしょう。
クロージャの定義の前にローカルな参照をセットアップし、クロージャ定義内でuse句で利用するだけで可能になります。
class Twitter extends \lithium\core\StaticObject { public static function tweet($status, $options) { $auth = static::$_classes['oauth']; $defaults = array('responseFormat' => 'json'); $options += $defaults; $params = compact('status', 'options'); return static::_filter(__FUNCTION__, $params, function($self, $params) use ($auth) { // Twitter connection and data transfer logic goes here... return $result; }); } }
LithiumとPHP5.3
このドキュメントはPHP5.3の概要を俯瞰でき、それらがLithiumとどう関係しているかが把握できるよいドキュメントだと思います。
参照元: 02_lithium_basics/01_new_in_php_5.3.wiki
LithiumとPHP5.3
LithiumはPHP5.3の新機能を活用した最初のPHPフレームワークです。パフォーマンスの向上の他に、5.3以降では多くのコンセプトがありエレガントな開発テクニックが使えます。
LithiumはPHP.5.3の新しい機能をフルに活用しています。このガイドでこれらの機能をご紹介し、Lithiumのアーキテクチャ内の実装を紹介します。
名前空間
名前空間はクラス名を分離させ名前の衝突を防ぐために使用していますが、高速で簡単なクラスオートローディングを行うためにも使っています。
namespace app;
use app\models\Post;
class PostsController extends \lithium\action\Controller {
public function index() {
$posts = Post:all();
return compact('posts');
}
}
分かりやすいですね。名前空間はアプリケーションのクラスをLithiumコアやサードパーティプラグインから分離し、よく使われるFile, Folderなどのクラス名を同じソースコード中に記述できます。
LithiumのクラスはクラスファイルのオートローディングのためにPHPスタンダードワーキンググループのnamespace標準に準拠しています。これにより乱雑にinclude文を記述することなくクラスファイルをきれいにすることができます。
無名関数とラムダとクロージャ
無名関数はちょっとした簡単なロジックをパッケージ化し、システム内で使い回すのに便利なツールです。PHP5.3では無名関数を使って変数に代入することができます。
$cube = function($value) { return ($value * $value * $value); }; $result = array_map($cube, array(1, 2, 3)); // $result --> array(1, 8, 27) $result = $cube(4); // $result --> int 64
メソッドの引数に無名関数を渡してクロージャを作成することもできます。クロージャのスコープに外部の変数を渡すためにuse()を使っていることに注意して下さい。
$data = 'bad apple';
$result = array_filter(array(1, 2, 'bad apple'), function ($value) use ($data) {
return ($value !== $data);
});
// $result --> array(1, 2)
クロージャはLithiumでは多くの箇所で使われています。下記のようにデータバリデーションをすばやく作成できます。
Validator::add('validRole', function($value, $format, $options) { return(in_array($value, array('user', 'admin', 'editor'))); });
クロージャはフィルダーを作成する為にも使われます。フィルターは無名関数を既存のロジックチェーンに組み込むことでコアの機能を変更することが出来る機能になります。
遅延静的束縛
PHP5.3での新機能に静的遅延束縛があります。この機能はクラス継承にてスマートにスタチッククラスを作成することができます。少ない文章で説明するのは難しいですが新機能のstatic::とself::の違いをコードで説明してみましょう。
<?php class Person { public static function whoAmI() { return "Person"; } public static function testSelf() { return self::whoAmI(); } public static function testStatic() { return static::whoAmI(); } } class Developer extends Person { public static function whoAmI() { return "Developer"; } } echo Developer::testSelf(); // "Person" echo Developer::testStatic(); // "Developer" ?>
Lithiumではモデル等、多くのクラスでスタティックにアクセスされます。このように簡単に継承したクラスのスタティックな状態にアクセスすることが出来るのでステートレスでアプリケーション全体の設計をより良くすることができます。
スタンダードPHPライブラリー(SPL)
現在のPHPでは簡単で、よく使われるタスクに使用できるオブジェクトの標準ライブラリがあります。PHP5.3で開発しているのでしたら、これらのすでに実装され使うことが可能なIterators, 例外、ファイル処理、StackやQueueといったデータ構造クラスの全てになじんでいることでしょう。
PHPのOOPシンタックスの強化
Lithiumのコアデベロッパーも利用している幾つかのOOPのマジック機能があります。1つめは_call()に非常によく似た_callStatic()というマジック関数があります。アクセス可能なメソッドがオブジェクトに必要になった時、スタティックオブジェクトで動作する以外、実行されます。
Lithiumのバリデータクラスはまさにその機能を使った具体的な例になります。VAlidatorクラスはアプリケーションでスタティックにデータバリデーション提供されています。rule()メソッドでこのロジックにアクセス可能することも、__callStatic()を利用したrule名から実装されたメソッドでロジックにアクセス可能になっています。下記2つは同様です。
Validator::rule('email', $email); Validator::isEmail($email);
2つ目は、__invoke()メソッドになります。このメソッドはオブジェクトが呼び出されたとにに何が起こるかを定義します。
Lithiumで効果的に_invoke()を使った例は、コントローラがどのようになっているかを見てみればよいです。リクエストを受け取った後、Dispatcherがリクエストをインスタンス化したコントロールオブジェクトに渡します。コントローラはそれからinvokeされ、正しいアクションがリクエストオブジェクトに渡されたルーティング情報を元に呼び出されます。
PHAR
Lithiumはファイル群を1つのinclude可能なアーカイブにバンドルできるPHPの新機能も利用しています。PHARs(もしくはPHPアーカイブ)はライブラリーを1つのファイルとしてパッケージング化できます。
PHPファイルを圧縮するだけではありません。それらはストリームラッパーを介してアクセス可能ですので、アーカイブ内のファイルをincludeすることができます。
include 'phar:///path/to/archive.phar/file.php';
phar:///すでに見慣れたfile:///と同じような効果があります。これにより便利なファイルシステムのセットアップと同様にpharでfopen()とopendir()を使うことができます。
Lithiumのプラグインはパッケージ化されpharsとして配布されています。これによりリポジトリからより早く、効果的にプラグインをダウンロードでき、同時にコンパクトで配布可能な状態でLithiumを全てのプラグインにアクセスできるようにできます。
詳しくはコンソールで下記を実行してみて下さい。
$ li3 help Library
新しい拡張モジュール: Intl, Fileinfo, Sqlite3, Mysqlnd
ここで取り上げるに値する多くのPHPの新しい拡張モジュールがあります。これらの新しい拡張モジュールはLithiumアプリケーションで使うことができ、コアのフレームワーク自体でも使われています。
Intl
Fileinfo
この拡張モジュールは拡張子とファイルの特定の位置からmagicバイトシーケンスに基づきファイルに関する情報を取得する為に作成されました。これによりこれまで完璧ではなかった、ファイルやストリーム内のデータに関する情報を開発者が知ることが出来るようになりました。
<?php $info = new \finfo(FILEINFO_MIME); $result = $info->file(__FILE__); var_dump($result); // 'text/x-php; charset=us-ascii' $result = finfo_file(finfo_open(FILEINFO_MIME), __FILE__); var_dump($result); // 'text/x-php; charset=us-ascii' ?>
Sqlite3
サーバを用意する必要がなくトランザクションが利用できるデータベースエンジンであるSQLite自体が組み込まれました。もしディスクトップやモバイルソフトウエアを書いたことがあるのでしたらSQLiteの経験があるかもしれません。PHP5.3では標準で搭載されていますので、Lithiumのデータソースアダプタを使うことが期待できます。
Lithiumのディレクトリ構造
参照元:02_lithium_basics/00_architecture.wiki
読んだ後に思いました。Blogチュートリアルの前にまずここを読めばよかったと。
個人的に予備知識の無いままディレクトリ構造や規模等あれこれ調べるのは嫌いではないですが、このドキュメントを読んでいれば時間を節約できたと思います。
しかしここの英語は難しくてよく分かりませんでした。(分からない部分は前後の文から勝手に自己解釈して納得しました。というかそうするしかなかったです。)
ここも正確な情報を知りたい方は参照元を英語で読んだほうがよいかと思われます。
Lithiumの構造
Lithiumを始める前に、アプリケーションの主要フォルダがどのような構造をしていてどこに何があるのかを把握することは重要なことです。リポジトリからcloneしてappフォルダを確認すると下記のフォルダがあるはずです。
- config
- controllers
- extensions
- libraries
- models
- resources
- tests
- views
- webroot
ひとつずつこれらのフォルダを取り上げて、それぞれが何に使われていてどうやって自分のアプリケーションをうまく編成できるかを説明します。
config
configフォルダはbootstrapファイル、コネクション情報、ルート定義といった3つの主要部分から成ります。
Bootstrapping Lithium
configディレクトリの直下にあるbootstrap.phpはメインbootstrapファイルになります。このファイルはリクエストサイクルに置いてPHPが実行する2番目のファイルであり、ユーザ定義のbootstrapファイルにしたがってLithiumフレームワークをロードします。アプリケーションの初期化設定を配置する重要な場所になります。
bootstrapのベストプラクティスは独自のbooststrapファイルをconfig/booststrap/filename.phpのように記述して、メインbootstrapファイルからインクルードすることです。
// Adding my custom configuration or initialization... require __DIR__ . '/bootstrap/myconfig.php';
connection
configフォルダにあるconnection.phpファイルは外部データソース(一般的にはデータベース)に必要なコネクションになります。
このファイルにリストされているコネクションは下記のようにConnections::add()で呼び出します。
Connections::add('default', array( 'type' => 'database', 'adapter' => 'MySql', 'host' => 'localhost', 'login' => 'root', 'password' => '', 'database' => 'my_blog' )); Connections::add('couch', array( 'type' => 'http', 'adapter' => 'CouchDb', 'host' => '127.0.0.1', 'port' => 5984 )); Connections::add('mongo', array('type' => 'MongoDb', 'database' => 'my_app'));
controllers
アプリケーションのコントローラはこのフォルダに格納し、コントローラの命名規則はキャメルケースになっています。
extensions
extensionsフォルダはカスタムヘルパーやアダブター、コンソールコマンドそしてdataクラスなどの自作した拡張クラスをおく場所になります。
Adapter
多くのLithiumクラスはlithium\core\Adaptableスタティッククラスを拡張しています。このクラスは多くの違ったアプローチを受けることができ簡単にアプリケーションのタスクによって実装を切り替えることができます。認証、セッション処理、キャッシュなどはadaptableを実装したLithiumの機能の例になります。
Adapterを実装したいのならこれらのソースを嫁ということでしょうか。
Command
カスタムコンソールアプリケーションはcommandフォルダ内に置きます。作成したコマンドは/lithium/console/commandで見られる実装と同じに構築します。
カスタムメールキューの実装を例にとるとアナウンスメールをキューに貯め、送信処理をトリガーするコマンドを作成する必要があります。lithium\console\Commandを継承したQueueFillerというクラスを新規に作成し、app/extensions/command/QueueFiller.phpというパスに配置します。
コマンドラインから下記のコマンドを 実行すると書いたとおりのロジックをトリガーすることでしょう。
$ li3 QueueFiller
Data
コアクラスでまだサポートされていないストレージエンジンで動かす貯めにカスタムデータクラスが必要になる場合もあるかと思います。もしプロプライアタリなデータベースを使う必要になるのならresultオブジェクトやクエリ構造はたくさんカスタマイズする必要があると思います。その際にlithium\dataにそういったクラスを拡張し、アプリケーションで使用する為にこのフォルダに配置して下さい。
libraries
librariesフォルダはLithiumアプリケーションもしくはプラグインを配置します。自作したアプリケーションやlaboratoryからダウンロードしたプラグイン
サードパーティやLithiumのライブラリでないものもここ置いて下さい。もしFacebookと統合したいのであればZend Frameworkのライブラリーをここに配置しても構いません。
models
アプリケーションのモデルは命名規則をキャメルケースしここに配置します。
resources
アプリケーションのデータの為にWEBからはアクセス不可なストレージとしてLithiumアプリケーションによって使用されます。デフォルトでは国際化ファイルや一時ファイルがここに作成されます。アプリケーションを構築時にresourcesフォルダはカスタムキャッシュファイルのようなアプリケーションデータやSQLiteデータベースやアップロードファイルの一時格納場所の為に使用されます。
tests
testsフォルダは単体テストフレームワークを使って実装したテストケースを入れます。Lithium自体のテストは別の場所にありますが、ここにあるサブフォルダをひとつづつ説明します。
cases
アプリケーションのMVCクラスの単体テストロジックの全てはcasesフォルダに格納します。サブフォルダが既にcases内に存在しているはずですので、テストケースをプロンプトで実行してみて下さい。
integration
2つ以上のクラスの結合テスト抽象クラスの実装テストケースを入れます。
views
viewsフォルダはアプリケーションのビューを格納します。ここいある2つの主要なフォルダはelementsとlayoutsになります。他のフォルダはコントローラ名のフォルダにビューを配置します。
elements
elementsは複数のビューで共有される部品になります。共通のナビゲーションエレメントやフォームはこのフォルダにelementとして作成されることでしょう。elementファイルは複合拡張子がサフィックスとして月ます。最初はビューの形式になりその次が.phpになります。
nav.html.phpやanimateLink.js.phpやheader.xml.phpといった感じになります。
layouts
ビューはlayouts内にレンダリングされます。アプリケーションのlayoutsはelementと同じ命名規則でこのフォルダに格納します。コントローラで特に指定が無ければビューのレンダー時にdefaultというlayoutが使われます。
コントローラのビューフォルダ
elementsとlayouts以外に、コントローラに特化したビューフォルダはここに配置します。例えばpagesフォルダはPagesControllerのビュー全てを配置します。新しコントローラとビューを作成したらここに新しくコントローラ名と同じ名前のフォルダを作成します。
webroot
webrootフォルダは唯一の(理想では)公開ディレクトリとなります。index.phpファイルはLithiumのbootstrapファイルを呼び出し、残りのフォルダとファイルは静的コンテンツとして扱われます。
JavaScriptファイルや画像、Flash、動画といったコンテンツはWebサーバがアクセスしクライアントに配送する必要があるためここに配置します。
LithiumをApacheで動かす
参照元:01_getting_started/web_servers/apache.wiki
かんたんなセットアップ
クリーンなURLでLithiumアプリケーションを提供するために、LithiumはApacheでURL書き換えを行う.htaccessファイルのセットを搭載しています。
デフォルトでは、これらのファイルはhttp.confでAllowOverrideと記載されている場所を見つけて設定をAllにして下さい。Mac OS Xではセットアップは下記のようになっています。
<Directory "/Library/WebServer/Documents"> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny Allow from all </Directory>
以前に作成した例題アプリケーションを使ってLithiumをドキュメントルート/Library/WebServer/Documentsに配置すればアプリケーションはhttp://localhost/lithiumというURLでアクセス可能になります。
本番環境のセットアップ
本番環境ではAllowOverrideはNoneに設定することがセキュリティ上よいかと思いますので、VirtualHost設定でwebrootディレクトリをrewriteルールを記載し公開します。例えば/var/www/html/your_appにアプリケーションを配置しているのでしたら、次の設定になります。
<VirtualHost *:80> ServerName example.com DocumentRoot "/var/www/html/your_app/webroot" ErrorLog "logs/error_log" <Directory "/var/www/html/your_app/webroot"> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !favicon.ico$ RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] </Directory> </VirtualHost>
この場合はアプリケーションにhttp://example.comというURLでアクセスできます。
とりあえずざっと公式ドキュメントの環境構築周りを読んで意訳してみました。CakePHPで既に運用とかしている等のスキルがあれば特にMongoDB以外は目新しいことは無いと思います。
MongoDBのセットアップ
Blogチュートリアルをする前にすでに読みましたが一応、訳を記載しておきます。
参照元:01_getting_started/data_sources/mongodb-setup.wiki
MongoDBはドキュメント指向のNoSQLデータベースです。ドキュメント指向データベースは典型的なリレーショナルデータベースよりハイパフォーマンスや簡単にスケールできる等の多くのメリットがあります。多くの開発者は必要な時にレコードにフィールドを追加できるスキーマレスを好んでいます。
でも、MongoDBがリレーショナルデータベースの代替という訳ではないということは理解しておかないといけません。リレーショナルデータベースは複雑なリレーションがあった場合にパワフルなクエリを利用できます。
MongoDB自体のセットアップの説明はMongoDBの公式サイトで確認してください。MongoDBのインストールが終わったら、下記の設定をして下さい。