MVC(Model-View-Controller)はそれぞれ自分の担当する役割をきっちりこなすプロの職人トリオです。 「M(モデル)」は、データの出し入れのプロ 「V(ビュー)」は、レンダリング(HTMLページを描画すること)のプロ 「C(コントローラ)」は、リクエストを一手に受け付けて、必要な仕事を適切な職人に依頼して最終的なHTMLページを完成させてレスポンスするプロ このプロ集団の中核を成す(中心になって引っ張って行ってくれる)のが、『コントローラ』になります。 Zend Framework(以下 ZF)ではZend_Controllerコンポーネントがこれに当たります。 このZend_Controller、飲み会で言ったら”幹事役”と言ったことろでしょう おにぎりの具て言ったらおかか、 果物で言ったら梨、 インスタントらーめんで言ったらサッポロ一番みそラーメン、 AKB48で言ったら峯岸みなみということになります。 ZFはコンポーネント(Zend_Fooの形式で、処理機能ごとに分かれているクラス)の集まりです。 Zend_ControllerがあるわけですからZend_Viewもちゃんと用意されています。 じゃあZend_Modelも当然のことながら用意されているかと思いきや、無いんですねこれが・・うっかり作り忘れたんでしょうかね。 いや、きっとモデルはアプリケーション開発に自由度を与えるためにあえて用意していないんでしょう。 よくモデルの説明でビジネスロジックということが言われます。種々のビジネスに沿ったロジックはおまかせしますということでしょうね。 もし用意したら使い方を事細かく覚える手間とのトレードオフがありますからね。その代わりZend_Dbなどの強力なコンポーネントが用意されています。 では処理の流れになりますが、一般のサーバサイド処理(CGIやPHP、サーブレットなど)と同様『リクエスト』⇒『処理』⇒『レスポンス』の流れをたどります。 このうちの『処理』の部分をMVCアプリケーションとして開発していくことになります。 まず特徴的なのはCが『フロントコントローラ』と『アクションコントローラ』の二重構造になっていることです。 すべての処理は『フロントコントローラ』がリクエストを受け付けるところから始まります。 企業・ホテルのフロント(受け付け係)といったところでしょうか いきなりメインの処理を『アクションコントローラ』に引き渡して、処理が戻ってきたらレンダリング(レスポンス用HTMLページの描画)をVに依頼して返ってきたページをレスポンスします。 要するに『フロントコントローラ』はリクエストがきたら、メインの処理を『アクションコントローラ』に丸投げして結果をレスポンスしてるだけで、アプリケーションとしての実質的な処理は完全に『アクションコントローラ』がやってることになります。 こうやってアプリケーションの処理を『フロントコントローラ』と完全に分けることで、開発したアプリケーションをモジュールとして簡単に分離することができるわけですね。 ディレクトリ構造を見てみると application/ (↓非公開ディレクトリ↓) +- models/ M関連置き場 +- views/ V関連置き場 | +- scripts/ | +- index/ | +- index.phtml (レンダリング用ビュースクリプト) +- controllers/ C関連置き場 +- IndexController.php (アクションコントローラ) htdocs/ (↓公開ディレクトリ↓) +- index.php (フロントコントローラ) +- .htaccess アプリケーション関連のファイルが『フロントコントローラ』とまったく別の非公開の場所(ネット越しに直接アクセスできない場所)に分けて置かれてるのがよく分かります。 公開ディレクトリには『フロントコントローラ』である"index.php"が置かれているだけです。 アプリケーション関連ファイルを公開ディレクトリに置かないのはセキュリティを上げる意味でも良さそうです。 さて、『フロントコントローラ』にすべてのリクエストを集中させる仕組みですが、Apacheに組み込まれたモジュールmod_rewriteの機能を使って力技で書き換えて行います。 『フロントコントローラ』の設置場所に".htaccess"というApacheのアクセス制御ファイルを用意して下記のように設定します。 ▼ .htaccess記述例 RewriteEngine on RewriteBase / RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php この設定により、 http://サーバ名/ http://サーバ名/index.html http://サーバ名/path/to/another/file.html http://サーバ名/noexist/path/ http://サーバ名/bar.cgi?action=delete といったリクエストをしても、全部 http://サーバ名/index.php に行くことになります。 RewriteBase /foo 設定であれば http://サーバ名/foo/path/to/another/file.html といったリクエストをしても http://サーバ名/foo/index.php に行くことになります。 この場合ドキュメントルートがhtdocsだとすると htdocs/ (↓公開ディレクトリ↓) +- foo/ +- index.php (フロントコントローラ) +- .htaccess というディレクトリ構造になります。 これが『フロントコントローラ』にすべてのリクエストが行くからくりです。 もちろん『フロントコントローラ』のファイル名は自由に変えられますが"index.php"というファイル名で設定する場合が多いし無難でしょう。 以上がZend FrameworkにおけるMVCのほんのさわりです。
MVC(Model-View-Controller)はそれぞれ自分の担当する役割をきっちりこなすプロの職人トリオです。
このプロ集団の中核を成す(中心になって引っ張って行ってくれる)のが、『コントローラ』になります。 Zend Framework(以下 ZF)ではZend_Controllerコンポーネントがこれに当たります。
このZend_Controller、飲み会で言ったら”幹事役”と言ったことろでしょう おにぎりの具て言ったらおかか、 果物で言ったら梨、 インスタントらーめんで言ったらサッポロ一番みそラーメン、 AKB48で言ったら峯岸みなみということになります。
ZFはコンポーネント(Zend_Fooの形式で、処理機能ごとに分かれているクラス)の集まりです。 Zend_ControllerがあるわけですからZend_Viewもちゃんと用意されています。
じゃあZend_Modelも当然のことながら用意されているかと思いきや、無いんですねこれが・・うっかり作り忘れたんでしょうかね。
いや、きっとモデルはアプリケーション開発に自由度を与えるためにあえて用意していないんでしょう。 よくモデルの説明でビジネスロジックということが言われます。種々のビジネスに沿ったロジックはおまかせしますということでしょうね。 もし用意したら使い方を事細かく覚える手間とのトレードオフがありますからね。その代わりZend_Dbなどの強力なコンポーネントが用意されています。
では処理の流れになりますが、一般のサーバサイド処理(CGIやPHP、サーブレットなど)と同様『リクエスト』⇒『処理』⇒『レスポンス』の流れをたどります。 このうちの『処理』の部分をMVCアプリケーションとして開発していくことになります。
まず特徴的なのはCが『フロントコントローラ』と『アクションコントローラ』の二重構造になっていることです。
すべての処理は『フロントコントローラ』がリクエストを受け付けるところから始まります。 企業・ホテルのフロント(受け付け係)といったところでしょうか
いきなりメインの処理を『アクションコントローラ』に引き渡して、処理が戻ってきたらレンダリング(レスポンス用HTMLページの描画)をVに依頼して返ってきたページをレスポンスします。
要するに『フロントコントローラ』はリクエストがきたら、メインの処理を『アクションコントローラ』に丸投げして結果をレスポンスしてるだけで、アプリケーションとしての実質的な処理は完全に『アクションコントローラ』がやってることになります。
こうやってアプリケーションの処理を『フロントコントローラ』と完全に分けることで、開発したアプリケーションをモジュールとして簡単に分離することができるわけですね。
application/ (↓非公開ディレクトリ↓) +- models/ M関連置き場 +- views/ V関連置き場 | +- scripts/ | +- index/ | +- index.phtml (レンダリング用ビュースクリプト) +- controllers/ C関連置き場 +- IndexController.php (アクションコントローラ) htdocs/ (↓公開ディレクトリ↓) +- index.php (フロントコントローラ) +- .htaccess
アプリケーション関連のファイルが『フロントコントローラ』とまったく別の非公開の場所(ネット越しに直接アクセスできない場所)に分けて置かれてるのがよく分かります。 公開ディレクトリには『フロントコントローラ』である"index.php"が置かれているだけです。 アプリケーション関連ファイルを公開ディレクトリに置かないのはセキュリティを上げる意味でも良さそうです。
さて、『フロントコントローラ』にすべてのリクエストを集中させる仕組みですが、Apacheに組み込まれたモジュールmod_rewriteの機能を使って力技で書き換えて行います。 『フロントコントローラ』の設置場所に".htaccess"というApacheのアクセス制御ファイルを用意して下記のように設定します。
この設定により、 http://サーバ名/ http://サーバ名/index.html http://サーバ名/path/to/another/file.html http://サーバ名/noexist/path/ http://サーバ名/bar.cgi?action=delete
といったリクエストをしても、全部 http://サーバ名/index.php に行くことになります。
RewriteBase /foo 設定であれば http://サーバ名/foo/path/to/another/file.html といったリクエストをしても http://サーバ名/foo/index.php に行くことになります。
この場合ドキュメントルートがhtdocsだとすると
htdocs/ (↓公開ディレクトリ↓) +- foo/ +- index.php (フロントコントローラ) +- .htaccess
というディレクトリ構造になります。
これが『フロントコントローラ』にすべてのリクエストが行くからくりです。 もちろん『フロントコントローラ』のファイル名は自由に変えられますが"index.php"というファイル名で設定する場合が多いし無難でしょう。
以上がZend FrameworkにおけるMVCのほんのさわりです。
フロントコントローラは大抵 "index.php" というファイル名で設定することが多いと思います。 では具体的な記述例を見ていきましょう。 ▼ 基本的なフロントコントローラの記述 setControllerDirectory('../application/controllers'); // アクションコントローラ置き場を教える $front->dispatch(); // ルーティング&ディスパッチ&メイン処理&レンダリング&レスポンス まずフロントコントローラの場合'Zend/Controller/Front.php'を呼び出して記述していくことになります。 後述するアクションコントローラは'Zend/Controller/Action.php'を呼び出して記述します。 次にインスタンスの取得です。 インスタンスの作成ではなく取得と書いているのは、始めから一つだけ用意(メモリ上に確保)されているからです。(シングルトン) ウェブアプリケーション処理の『リクエスト』⇒『処理』⇒『レスポンス』の流れにおいて、コネクションは常に一つだけなのでそれ以上は不要ということですね。 なのでnew演算子で作成しようとするとFatal errorになってしまいます。 ×$front = new Zend_Controller_Front(); ○$front = Zend_Controller_Front::getInstance(); それからアクションコントローラの置き場所を教えていますが、デフォルトで置くような場所は特に決まっていないので教えてあげないと次のdispatch()メソッド内で行われるディスパッチができないことになります。ここは教えときましょ 最後の行になりますが、dispatch()メソッド内ではいろんなことをいっぺんに行っています。 まずルーティング。これはフロントコントローラにおいて真っ先に行われる処理で、たった一度だけの処理となります。 次にディスパッチですが、これでメインの処理をアクションコントローラに依頼しています。 そしてアプリケーションとしてメイン処理をディスパッチされたアクションメソッドで行います。 それからレンダリングを実行します。ビューの機能を使ってメイン処理で導き出した結果などを、ビュースクリプトに反映させてページを作成します。 最後にレスポンスですが、レンダリング結果をレスポンスします。 以上が基本的なフロントコントローラ(index.php)の記述になります。 下記のように、もっとシンプルな書き方もできますが、これはあくまでもこんなにコンパクトに書けますよという曲芸的なものであまりにも内部でやってることが見えにくくなってしまいます。なのでわたしは使うことはないと思います。 ▼ ルーティング ルーティングはフロントコントローラで最初に行われる処理(つまり、ZFのMVCで最初の処理)です。 これはdispatch()メソッドにおいて、最初に行われます。 ルーティング(経路制御)の名のとおり、リクエスト(要求)に応じてどこに処理(アクション)を振り分けるのかをここで決めることになります。 リクエストURLはリライト処理により書き換えられてすべてフロントコントローラ(index.php)に行く仕組みになっているので、まだ書き換えられる前のリクエストURLを使って処理の振り分けを行うことになります。 基本は以下のようなパターンで当てはめていきます。 http://サーバ名/{コントローラ名}/{アクション名}/{パラメータ1}/{値1}/{パラメータ2}/{値2}/... 例えば下記URLにリクエストしたら、まずブツ切りにしてリクエストオブジェクトという場所に格納して以降の処理(ディスパッチ時)でこの値を参照できる状態にします。 http://サーバ名/foo/bar/fuga/001/ ↓ブツ切り http://サーバ名/ foo | bar | fuga | 001 ↓リクエストオブジェクトに格納 コントローラ名 = foo アクション名 = bar パラメータ[fuga] = 001 といったイメージになります。 では、もし想定していないようなリクエストURLだった場合にはどうなるでしょうか? http://サーバ名/foo/ ↓ブツ切り http://サーバ名/ foo ↓リクエストオブジェクトに格納 コントローラ名 = foo アクション名 = なし パラメータ = なし このケースだとコントローラ名だけが取得できますね。でも少なくともコントローラ名とアクション名の2つが無いと、その後のメインの処理をしてくれるメソッドをコールできないことになるので、こういう場合のためにデフォルト値が決まっています。 上記ではコントローラ名が"foo"そのままで、アクション名が取得できなかったので"index"というデフォルト値になります。 つまり以下のリクエストをしたのと同じことになります。 http://サーバ名/foo/index/ では、コントローラ名も取得できなかった場合はどうなるかというとこれもデフォルト値が"index"になります。 つまり以下のリクエストをしたのと同じことになります。 http://サーバ名/index/index/ 『ルーティング』は、リクエストオブジェクトに格納するまでで役割りは終わります。 **ちょっとひと息** リクエストオブジェクトがあるならレスポンスオブジェクトなるオブジェクトもあるだろとお思いのアナタ! 当たりです。ちゃんとありますよ。 『リクエストオブジェクト』がURLブツ切り情報やディスパッチがなされた時のフラグを持つオブジェクトなら、 『レスポンスオブジェクト』は、それまでの処理(アクションなど)で収集したヘッダやコンテンツ、発生したエラー内容などを保持しているオブジェクトで、レスポンスの総合商社といったことろでしょうか。 つまりこれらは対になるわけですね。『リクエストオブジェクト⇔レスポンスオブジェクト』 ▼ ディスパッチ ルーティングにより『リクエストオブジェクト』に格納された値を参照し、メインの処理(アクション)を行うメソッドをコールします。 メインの処理は『アクションコントローラ』に書かれた機能ごとの関数(アクションメソッド)なのでこれをコールすべく、リクエストオブジェクトに格納されている「コントローラ名」と「アクション名」を使います。 アクションコントローラ置き場はすでにフロントコントローラ(index.php)で指定しているので、その場所に存在する「アクションコントローラファイル」を呼び出します。(このファイル名は命名規則により「コントローラ名」に基づいた名前になっている。) アクションコントローラはZend_Controller_Actionのサブクラス(継承クラス)として作成するので、まずこれのインスタンスを作成してアクションメソッドをコールする流れになります。これも命名規則により「コントローラ名」と「アクション名」を使って適切なものをコールすることになります。 適切なアクションメソッドをコールして、リクエストオブジェクトにフラグを立てたら「ディスパッチ」の役割りは終わりとなります。(多分) ▼ メイン処理 MVCアプリケーションとしてのメイン処理は、ディスパッチ先のアクションメソッドで行うことになります。 その際にデータの出し入れ(データベース・ファイルなど)があればモデルに手伝ってもらったり、レンダリング用のビュースクリプト指定を切り替えたりしてあとはコンテンツを置き換えるだけの状態まで持っていきます。 詳細は次節アクションコントローラにて ▼ レンダリングとレスポンス 最終的なHTMLページを描き上げてレスポンスします。 これはdispatch()メソッドにおいて、ルーティングやディスパッチが終わって最後に行われます。 Zend_Viewの機能を使って、ビュースクリプト内の変数をここに来るまでの処理(アクション)過程でセットされた値に反映させた上での処理がなされてレンダリング(描画)されます。 dispatch()でレスポンスまで完了した後にレスポンスオブジェクトを取得してあれやこれやする例 $front->dispatch(); // レスポンスまで完了 $response = $front->getResponse(); // レスポンスオブジェクト取得 if ($response->isException()) { // もし例外があれば $exceptions = $response->getException(); // 例外内容を取得(配列) // 発生した例外をログ記録やメール送信で知らせるなどの処理をここで } dispatch()ではレンダリング&レスポンスさせずに、レスポンスオブジェクトを受け取って処理する方法もあります。 returnResponse(true)とすることでそれができるようになります。 $front->returnResponse(true); // dispatch()ではレンダリングもレスポンスもしないでよ $response = $front->dispatch(); // ルーティング&ディスパッチまでで止めてレスポンスオブジェクト取得 if ($response->isException()) { // もし例外があれば $exceptions = $response->getException(); // 例外内容を取得(配列) die("" . print_r($exceptions, true) . ""); } else { // レンダリングしてレスポンス $response->sendResponse(); // 本来はdispatch()内部で最後にやってる作業 } 恐らくこれらは実践的じゃないでしょう。 フロントコントローラ(index.php)にごちゃごちゃ例外処理やら書いてテストしないといけないお粗末なものを作ってしまっては、頑丈なMVCアプリケーションを作ってモジュール化するのは夢のまた夢になりそうな気がしますね。 やはりあくまでもアプリケーション本体(applicationディレクトリ以下)に例外処理もなにもかもしっかり書けていないとダメなような気がします。 index.phpに直書きするとしたらせいぜい設定ファイルの呼び出しぐらいにしたいですね。 ▼ フロントコントローラで使えるメソッド フロントコントローラで使うメソッドなので、インスタンスを取得してからdispatch()を呼ぶまでの間に使用するメソッドになります。 アクションコントローラの置き場所を教えるメソッド(これはもう既出) $front->setControllerDirectory('/path/to/controllers'); デフォルトの「コントローラ名」や「アクション名」を"index" "index"から別のものに設定する。 $front->setDefaultControllerName("foo"); $front->setDefaultAction("bar"); ルーティング処理の時にURLから取り除く基底URLを設定 $front->setBaseUrl('/foo'); オブジェクト $front->{set|get}Request(); // リクエストオブジェクトの設定と取得 $front->{set|get}Response(); // レスポンスオブジェクトの設定と取得 $front->getRouter(); // より細かなルーティング設定で使用 パラメータをセットする // $front->setParam($name, $value); // デフォルトで有効になっているErrorHandlerプラグインを無効にする $front->setParam('noErrorHandler', true); // デフォルトで有効になっているViewRendererアクションヘルパーを無効にする $front->setParam('noViewRenderer', true); // インスタンスは始めから存在しているので取得しないで値をセットすることもできる Zend_Controller_Front::getInstance()->setParam($name, $value); setParam()でセットした値(数値や文字列の他オブジェクトも可)はアクションコントローラ側の$name = $this->getInvokeArg('name');で取得できる。 ▼ プラグイン プラグインには始めから有効になっている「ErrorHandler」があります。 プラグインはフロントコントローラのイベント発生時(「ルーティング」および「ディスパッチ」の前後のタイミング)に実行させるコードのことです。 ▽ タイミングいろいろ メソッド名タイミング routeStartup()ルーティング前 routeShutdown()ルーティング後 dispatchLoopStartup()ディスパッチループ前 preDispatch()アクションのディスパッチ前 postDispatch()アクションのディスパッチ後 dispatchLoopShutdown()ディスパッチループ後 プラグインからはgetRequest()やgetResponse()を扱うこともできます。 ▽ プラグインの作り方 プラグイン [application/controllers/plugins/FooPlugin.php] getRequest()->setControllerName('foo'); $this->getRequest()->setActionName('bar'); } } Zend_Controller_Plugin_Abstractを継承したサブクラスを作成して、そこに実行させたいタイミングに応じたメソッドを記述していく。 ▽ プラグインの使い方 フロントコントローラ [htdocs/index.php] setControllerDirectory('../application/controllers'); // コントローラ置き場を教える require_once '../application/controllers/plugins/FooPlugin.php'; // プラグイン呼び出し $front->registerPlugin(new FooPlugin()); // プラグインのインスタンスをセット $front->dispatch(); // ルーティング&ディスパッチ&レスポンス フロントコントローラに直書きまたは別のファイルにプラグインのクラスを書いて、registerPlugin()メソッドにそのインスタンスを渡すだけで機能します。 ちなみに、以下はイベント発生のタイミングを視覚的にわかりやすく表示させたものです。 \n"; } public function routeShutdown() { echo "FooPlugin::routeShutdown()\n"; } public function dispatchLoopStartup() { echo "FooPlugin::dispatchLoopStartup()\n"; } public function preDispatch() { echo "FooPlugin::preDispatch()\n"; } public function postDispatch() { echo "FooPlugin::postDispatch()\n"; } public function dispatchLoopShutdown() { echo "FooPlugin::dispatchLoopShutdown()\n"; } } ▽ 表示結果 FooPlugin::routeStartup() FooPlugin::routeShutdown() FooPlugin::dispatchLoopStartup() FooPlugin::preDispatch() IndexController::init(/* ディスパッチされたアクションコントローラのinit() */) FooPlugin::postDispatch() FooPlugin::dispatchLoopShutdown() IndexController::preDispatch(/* ディスパッチされたアクションコントローラのpreDispatch() */) IndexController::indexAction(/*ディスパッチされたアクションメソッド*/) IndexController::postDispatch(/* ディスパッチされたアクションコントローラのpostDispatch() */) これらのメソッドは記述(定義)していれば実行されることになるので、必要なタイミングのメソッドに必要な記述をしていくことになります。 ▼ モジュールの利用 この項はモジュール利用を考えていない場合は読み飛ばしていい部分です。 モジュールのディレクトリ配置 application/ +- default/ (該当モジュールが無い場合のデフォルトのモジュール) | +- models/ (M置き場) | +- views/ (V置き場) | | +- scripts/ | | +- index/ | | +- index.phtml デフォルトのビュースクリプト | +- controllers/ (C置き場) | +- IndexController.php デフォルトのアクションコントローラ +- foo/ (fooモジュール) +- models/ +- views/ | +- scripts/ | +- index/ | +- index.phtml デフォルトのビュースクリプト +- controllers/ +- IndexController.php デフォルトのアクションコントローラ htdocs/ +- index.php (フロントコントローラ) +- .htaccess モジュールごとにMVCのディレクトリをまとめた構成になっています。 フロントコントローラからアプリケーションのアクションメソッドをコールしてきたこれまでと同様に、モジュール利用の場合もそのアクションメソッドをコールできれば使うことができるということになります。 これまでアクションメソッドのコールは、以下の3要素によって実現していました。 アクションコントローラの置き場所を教える コントローラ名の決定 アクション名の決定 以上の3つに加えて、どのモジュールかを指定できれば特定のモジュールのアクションメソッドがコールできるということになります。 これまではアクションコントローラの置き場所は一つだけだったのでそれを教えるだけで済みました。 モジュールに分かれている場合はどのモジュールのアクションコントローラ置き場なのかを知らせる必要があります。 これまでコントローラ置き場を教えるのに使ってきたsetControllerDirectory()メソッドに今度はキーがモジュール名、値が置き場所のリストを持つ配列を与えることになります。 $front->setControllerDirectory( array( 'default' => '../application/default/controllers', 'foo' => '../application/foo/controllers' ) ); もし各モジュールのディレクトリ配置が整った配置になっていれば、各モジュールをまとめているディレクトリを教えるだけで指定できます。 その際にはaddModuleDirectory()メソッドを使います。 $front->addModuleDirectory('../application'); こちらの方がシンプルに書けます。ただし、ディレクトリ配置が整っていないとうまく伝わらないでしょう。 これまでのルーティングでは、リクエストURLに応じて以下のような解釈がなされていました。 http://サーバ名/{コントローラ名}/{アクション名}/{パラメータ1}/{値1}/{パラメータ2}/{値2}/... 一方モジュールがある場合、どのようなルーティングがなされるかを見ていきますと、 http://サーバ名/{モジュール名}/{コントローラ名}/{アクション名}/{パラメータ1}/{値1}/{パラメータ2}/{値2}/... サーバ名の直下が新たに{モジュール名}として解釈されることになり、コントローラ名以降がずれ込む形になっています。 その他、モジュールではファイルの配置やファイル名に変更はありませんが、デフォルトモジュール以外のアクションコントローラのクラス名が変わることになります。 IndexController ...これはコントローラ名"index"(デフォルト)のアクションコントローラのクラス名 ↓ Foo_IndexController ...これはfooモジュールのコントローラ名"index"(デフォルト)のアクションコントローラのクラス名 これらは命名規則できっちり決まっています(決まってないと適切なメソッドコールができない) もしクラス名がそのままだと別のモジュールにも同じクラス名が存在した場合にインスタンス化できなくなります。 具体的な命名規則は『アクションコントローラ』の項で説明します。 ▼ ルーティングを独自に変更 この項はルーティングを独自に変える必要がない場合は読み飛ばしていい部分です。 長くなったURLを見せたくない場合などにルーティングの変更により該当アクションへディスパッチさせる技といったことろです。 前に書いたルーティングは、リクエストURLをブツ切りにしてリクエストオブジェクトに格納するというデフォルトで行われるルーティングでした。 一方ここでは、リクエストURLを好みの形式にしてそれを該当アクションへディスパッチさせるためのルーティングの変更を施します。 新たなルーティング方式追加の骨格 $router = $front->getRouter(); // ルータオブジェクトを取得 $router->addRoute('fooRoute', /* ここに新たなルーティングを追加 */); addRoute()メソッドの第一引数は追加ルーティングに好きな名前を自由に付けていいです。 第二引数に具体的な指定をしていくことになります。 「Zend_Controller_Router_Route」という標準のクラスと、 「Zend_Controller_Router_Route_Static」という静的な専用ルーティングクラス 「Zend_Controller_Router_Route_Regex」という柔軟に設定できるクラス これら3つのルーティングを書いていきます。 [1] まずはZend_Controller_Router_Routeクラスを使ったルーティング設定追加 $route = new Zend_Controller_Router_Route( 'archive/:year', array( 'controller' => 'archive', 'action' => 'read' ) ); ベースURL「http://サーバ名/」+「第一引数」で、 http://サーバ名/archive/:year となり、「:year」の記述は動的に変わる値を意味するので http://サーバ名/archive/2001 http://サーバ名/archive/2009 みたいなアクセスに対してルーティング変更する感じになります。 より便利に使うために、これにデフォルト値も設定してパターン制限もかけてみます。 $route = new Zend_Controller_Router_Route( 'archive/:year', array( 'controller' => 'archive', 'action' => 'read', 'year' => '2010' ), array('year' => '\d+') ); 第二引数の配列内にデフォルト値year=2010が加わり、第三引数に正規表現による制限をかけています。 このルーティング設定によって、 http://サーバ名/archive/数字並び にアクセスがあれば、 アクションコントローラ:application/controllers/ArchiveController.php アクションメソッド:readAction() にディスパッチされることになり、ディスパッチ先のアクションメソッド内から $req = $this->getRequest(); $year = $req->getParam('year'); または $year = $this->_getParam('year'); のように値を参照できることになります。 一旦まとめます。 ルーティングの標準クラスZend_Controller_Router_Routeの設定追加例 $router = $front->getRouter(); // ルータオブジェクトを取得 // ルーティング設定 $route = new Zend_Controller_Router_Route( 'archive/:year', array( 'controller' => 'archive', 'action' => 'read', 'year' => '2010' ), array('year' => '\d+') ); // 設定追加 $router->addRoute('fooRoute', $route); [2] 次に、Zend_Controller_Router_Route_Staticクラスを使ったルーティング設定追加 $router = $front->getRouter(); // ルータオブジェクトを取得 // ルーティング設定 $route = new Zend_Controller_Router_Route_Static( 'sitemap', array( 'controller' => 'index', 'action' => 'sitemap' ) ); // 設定追加 $router->addRoute('sitemapRoute', $route); これはシンプルで分かりやすいURLを公開しておき、 http://サーバ名/sitemap 「sitemap」がリクエストされてきたら内部ではディスパッチ先を「IndexController::sitemapAction()」にするように単純置き換えするルーティングです。 ピンポイントなので動的に変わる部分はありません。 したがって他より高速とされています。 これはアクション数が一つしかない(処理が一つしかない、またはデータを取得して1ページ表示させるリードオンリーなもの)ようなカテゴリーのサイトメニューなどに使えそうですね。loginやsitemap, newsなど・・ 公開URL的に http://サーバ名/index/sitemap だとダサいから http://サーバ名/sitemap にしたいみたいなこだわりがあればの話ですが [3] 最後に、Zend_Controller_Router_Route_Regexクラスを使ったルーティング設定追加 より柔軟性のあるルーティングができる。 $router = $front->getRouter(); // ルータオブジェクトを取得 // ルーティング設定 $route = new Zend_Controller_Router_Route_Regex( 'archive/(\d{4})(\d{2})\.html', array( 'controller' => 'archive', 'action' => 'read' ), array(1 => 'year', 2 => 'month') ); // 設定追加 $router->addRoute('barRoute', $route); 第一引数で複雑な正規表現にマッチさせればいろんなディスパッチが実現できるようになります。 このルーティングだけは別途 require_once 'Zend/Controller/Router/Route/Regex.php'; の呼び出し記述が必要なようです。
フロントコントローラは大抵 "index.php" というファイル名で設定することが多いと思います。 では具体的な記述例を見ていきましょう。
まずフロントコントローラの場合'Zend/Controller/Front.php'を呼び出して記述していくことになります。 後述するアクションコントローラは'Zend/Controller/Action.php'を呼び出して記述します。
次にインスタンスの取得です。 インスタンスの作成ではなく取得と書いているのは、始めから一つだけ用意(メモリ上に確保)されているからです。(シングルトン)
ウェブアプリケーション処理の『リクエスト』⇒『処理』⇒『レスポンス』の流れにおいて、コネクションは常に一つだけなのでそれ以上は不要ということですね。 なのでnew演算子で作成しようとするとFatal errorになってしまいます。
それからアクションコントローラの置き場所を教えていますが、デフォルトで置くような場所は特に決まっていないので教えてあげないと次のdispatch()メソッド内で行われるディスパッチができないことになります。ここは教えときましょ
最後の行になりますが、dispatch()メソッド内ではいろんなことをいっぺんに行っています。
以上が基本的なフロントコントローラ(index.php)の記述になります。
下記のように、もっとシンプルな書き方もできますが、これはあくまでもこんなにコンパクトに書けますよという曲芸的なものであまりにも内部でやってることが見えにくくなってしまいます。なのでわたしは使うことはないと思います。
ルーティングはフロントコントローラで最初に行われる処理(つまり、ZFのMVCで最初の処理)です。 これはdispatch()メソッドにおいて、最初に行われます。
ルーティング(経路制御)の名のとおり、リクエスト(要求)に応じてどこに処理(アクション)を振り分けるのかをここで決めることになります。
リクエストURLはリライト処理により書き換えられてすべてフロントコントローラ(index.php)に行く仕組みになっているので、まだ書き換えられる前のリクエストURLを使って処理の振り分けを行うことになります。
例えば下記URLにリクエストしたら、まずブツ切りにしてリクエストオブジェクトという場所に格納して以降の処理(ディスパッチ時)でこの値を参照できる状態にします。
といったイメージになります。
では、もし想定していないようなリクエストURLだった場合にはどうなるでしょうか?
このケースだとコントローラ名だけが取得できますね。でも少なくともコントローラ名とアクション名の2つが無いと、その後のメインの処理をしてくれるメソッドをコールできないことになるので、こういう場合のためにデフォルト値が決まっています。 上記ではコントローラ名が"foo"そのままで、アクション名が取得できなかったので"index"というデフォルト値になります。
では、コントローラ名も取得できなかった場合はどうなるかというとこれもデフォルト値が"index"になります。
『ルーティング』は、リクエストオブジェクトに格納するまでで役割りは終わります。
ルーティングにより『リクエストオブジェクト』に格納された値を参照し、メインの処理(アクション)を行うメソッドをコールします。
メインの処理は『アクションコントローラ』に書かれた機能ごとの関数(アクションメソッド)なのでこれをコールすべく、リクエストオブジェクトに格納されている「コントローラ名」と「アクション名」を使います。
アクションコントローラ置き場はすでにフロントコントローラ(index.php)で指定しているので、その場所に存在する「アクションコントローラファイル」を呼び出します。(このファイル名は命名規則により「コントローラ名」に基づいた名前になっている。)
アクションコントローラはZend_Controller_Actionのサブクラス(継承クラス)として作成するので、まずこれのインスタンスを作成してアクションメソッドをコールする流れになります。これも命名規則により「コントローラ名」と「アクション名」を使って適切なものをコールすることになります。
適切なアクションメソッドをコールして、リクエストオブジェクトにフラグを立てたら「ディスパッチ」の役割りは終わりとなります。(多分)
MVCアプリケーションとしてのメイン処理は、ディスパッチ先のアクションメソッドで行うことになります。
その際にデータの出し入れ(データベース・ファイルなど)があればモデルに手伝ってもらったり、レンダリング用のビュースクリプト指定を切り替えたりしてあとはコンテンツを置き換えるだけの状態まで持っていきます。
詳細は次節アクションコントローラにて
最終的なHTMLページを描き上げてレスポンスします。
これはdispatch()メソッドにおいて、ルーティングやディスパッチが終わって最後に行われます。 Zend_Viewの機能を使って、ビュースクリプト内の変数をここに来るまでの処理(アクション)過程でセットされた値に反映させた上での処理がなされてレンダリング(描画)されます。
dispatch()でレスポンスまで完了した後にレスポンスオブジェクトを取得してあれやこれやする例
dispatch()ではレンダリング&レスポンスさせずに、レスポンスオブジェクトを受け取って処理する方法もあります。 returnResponse(true)とすることでそれができるようになります。
" . print_r($exceptions, true) . "
恐らくこれらは実践的じゃないでしょう。
フロントコントローラ(index.php)にごちゃごちゃ例外処理やら書いてテストしないといけないお粗末なものを作ってしまっては、頑丈なMVCアプリケーションを作ってモジュール化するのは夢のまた夢になりそうな気がしますね。 やはりあくまでもアプリケーション本体(applicationディレクトリ以下)に例外処理もなにもかもしっかり書けていないとダメなような気がします。 index.phpに直書きするとしたらせいぜい設定ファイルの呼び出しぐらいにしたいですね。
フロントコントローラで使うメソッドなので、インスタンスを取得してからdispatch()を呼ぶまでの間に使用するメソッドになります。
setParam()でセットした値(数値や文字列の他オブジェクトも可)はアクションコントローラ側の$name = $this->getInvokeArg('name');で取得できる。
プラグインには始めから有効になっている「ErrorHandler」があります。
プラグインはフロントコントローラのイベント発生時(「ルーティング」および「ディスパッチ」の前後のタイミング)に実行させるコードのことです。
プラグインからはgetRequest()やgetResponse()を扱うこともできます。
▽ プラグインの作り方
Zend_Controller_Plugin_Abstractを継承したサブクラスを作成して、そこに実行させたいタイミングに応じたメソッドを記述していく。
▽ プラグインの使い方
フロントコントローラに直書きまたは別のファイルにプラグインのクラスを書いて、registerPlugin()メソッドにそのインスタンスを渡すだけで機能します。
ちなみに、以下はイベント発生のタイミングを視覚的にわかりやすく表示させたものです。
これらのメソッドは記述(定義)していれば実行されることになるので、必要なタイミングのメソッドに必要な記述をしていくことになります。
この項はモジュール利用を考えていない場合は読み飛ばしていい部分です。
モジュールのディレクトリ配置
application/ +- default/ (該当モジュールが無い場合のデフォルトのモジュール) | +- models/ (M置き場) | +- views/ (V置き場) | | +- scripts/ | | +- index/ | | +- index.phtml デフォルトのビュースクリプト | +- controllers/ (C置き場) | +- IndexController.php デフォルトのアクションコントローラ +- foo/ (fooモジュール) +- models/ +- views/ | +- scripts/ | +- index/ | +- index.phtml デフォルトのビュースクリプト +- controllers/ +- IndexController.php デフォルトのアクションコントローラ htdocs/ +- index.php (フロントコントローラ) +- .htaccess
モジュールごとにMVCのディレクトリをまとめた構成になっています。
フロントコントローラからアプリケーションのアクションメソッドをコールしてきたこれまでと同様に、モジュール利用の場合もそのアクションメソッドをコールできれば使うことができるということになります。
以上の3つに加えて、どのモジュールかを指定できれば特定のモジュールのアクションメソッドがコールできるということになります。
これまではアクションコントローラの置き場所は一つだけだったのでそれを教えるだけで済みました。 モジュールに分かれている場合はどのモジュールのアクションコントローラ置き場なのかを知らせる必要があります。
これまでコントローラ置き場を教えるのに使ってきたsetControllerDirectory()メソッドに今度はキーがモジュール名、値が置き場所のリストを持つ配列を与えることになります。
もし各モジュールのディレクトリ配置が整った配置になっていれば、各モジュールをまとめているディレクトリを教えるだけで指定できます。 その際にはaddModuleDirectory()メソッドを使います。
こちらの方がシンプルに書けます。ただし、ディレクトリ配置が整っていないとうまく伝わらないでしょう。
サーバ名の直下が新たに{モジュール名}として解釈されることになり、コントローラ名以降がずれ込む形になっています。
その他、モジュールではファイルの配置やファイル名に変更はありませんが、デフォルトモジュール以外のアクションコントローラのクラス名が変わることになります。
これらは命名規則できっちり決まっています(決まってないと適切なメソッドコールができない) もしクラス名がそのままだと別のモジュールにも同じクラス名が存在した場合にインスタンス化できなくなります。 具体的な命名規則は『アクションコントローラ』の項で説明します。
この項はルーティングを独自に変える必要がない場合は読み飛ばしていい部分です。
長くなったURLを見せたくない場合などにルーティングの変更により該当アクションへディスパッチさせる技といったことろです。
前に書いたルーティングは、リクエストURLをブツ切りにしてリクエストオブジェクトに格納するというデフォルトで行われるルーティングでした。 一方ここでは、リクエストURLを好みの形式にしてそれを該当アクションへディスパッチさせるためのルーティングの変更を施します。
addRoute()メソッドの第一引数は追加ルーティングに好きな名前を自由に付けていいです。 第二引数に具体的な指定をしていくことになります。
「Zend_Controller_Router_Route」という標準のクラスと、 「Zend_Controller_Router_Route_Static」という静的な専用ルーティングクラス 「Zend_Controller_Router_Route_Regex」という柔軟に設定できるクラス これら3つのルーティングを書いていきます。
$route = new Zend_Controller_Router_Route( 'archive/:year', array( 'controller' => 'archive', 'action' => 'read' ) );
ベースURL「http://サーバ名/」+「第一引数」で、 http://サーバ名/archive/:year となり、「:year」の記述は動的に変わる値を意味するので
みたいなアクセスに対してルーティング変更する感じになります。
$route = new Zend_Controller_Router_Route( 'archive/:year', array( 'controller' => 'archive', 'action' => 'read', 'year' => '2010' ), array('year' => '\d+') );
第二引数の配列内にデフォルト値year=2010が加わり、第三引数に正規表現による制限をかけています。
このルーティング設定によって、 http://サーバ名/archive/数字並び にアクセスがあれば、 アクションコントローラ:application/controllers/ArchiveController.php アクションメソッド:readAction() にディスパッチされることになり、ディスパッチ先のアクションメソッド内から
のように値を参照できることになります。
一旦まとめます。
[2] 次に、Zend_Controller_Router_Route_Staticクラスを使ったルーティング設定追加
これはシンプルで分かりやすいURLを公開しておき、 http://サーバ名/sitemap 「sitemap」がリクエストされてきたら内部ではディスパッチ先を「IndexController::sitemapAction()」にするように単純置き換えするルーティングです。
ピンポイントなので動的に変わる部分はありません。 したがって他より高速とされています。
これはアクション数が一つしかない(処理が一つしかない、またはデータを取得して1ページ表示させるリードオンリーなもの)ようなカテゴリーのサイトメニューなどに使えそうですね。loginやsitemap, newsなど・・
公開URL的に http://サーバ名/index/sitemap だとダサいから http://サーバ名/sitemap にしたいみたいなこだわりがあればの話ですが
[3] 最後に、Zend_Controller_Router_Route_Regexクラスを使ったルーティング設定追加
第一引数で複雑な正規表現にマッチさせればいろんなディスパッチが実現できるようになります。
の呼び出し記述が必要なようです。
アクションコントローラは、少なくとも一つのアクションメソッドを持つクラス(設計図)です。 Zend_Controller_Actionクラスを継承したサブクラスとして作成します。 クラスなのでメソッドの集合体です。アクションメソッド(フロントコントローラからコールされてメインの処理を行う)以外にも初期化用のメソッドやアクション前後に呼ぶことのできるメソッドなどいろいろ記述することができます。 ▼ 基本的なアクションコントローラの記述 シンプルな記述例を以下に示します。これはコントローラ名が"index"、アクション名も"index"のデフォルトのアクションコントローラになります。 [application/controllers/IndexController.php] フロントコントローラが行うディスパッチによりアクションメソッドがコールされます。 アクションコントローラはクラスなのでそのクラスファイルを呼び出したらまずnew演算子を使って実体化(インスタンス化)した後に適切なアクションメソッドをコールすることになります。 その作業はディスパッチャーが「コントローラ名」と「アクション名」に基づいて自動的に行います。 したがって自動的に行うために「コントローラ名」と「アクション名」に基づいた命名規則が存在します。 項目命名規則例 アクションコントローラのファイル名コントローラ名(先頭大文字)+ Controller.phpIndexController.php、FooController.phpなど アクションコントローラのクラス名コントローラ名(先頭大文字)+ ControllerIndexController、FooControllerなど (モジュールならMod_IndexController,Bar_FooController) アクションメソッド名アクション名+ ActionindexAction、fooActionなど 「コントローラ名」は少なくとも一つはあるアクション(機能ごとに分けられた処理)をまとめる入れ物の名前だと考えることができるので、ファイル名やクラス名にこれを使うのはうまい手だと思います。 ▼ アクションコントローラに定義できるメソッド これまではアクションメソッドに関して書いてきましたが、アクションコントローラにはそれ以外にも定義できるメソッドがあります。そしてそれらは特定の役割りを持っています。 [application/controllers/IndexController.php] init() ...その名のとおり、定義されていれば最初に呼ばれて初期化作業などに使うメソッド preDispatch() ...その名のとおり、定義されていればディスパッチ前に呼ばれる。認証情報チェックなどに使う? (アクションメソッド) ...フロントコントローラからディスパッチされて呼ばれるメソッド postDispatch() ...その名のとおり、定義されていればディスパッチ後に呼ばれる。ビューの仕上げ処理など 上記のように、呼ばれるタイミングがそれぞれ異なります。 タイミングが異なることを利用して、処理段階における確認作業等の記述場所がよりはっきりするかも知れません。 定義するもしないも自由なのでここぞというときに効果的に使いたいですね。 この他に、__call()というメソッドを定義してアクションメソッドが見つからない場合などのエラー処理を行うこともできます。 [application/controllers/IndexController.php] view->message = 'アクションが見つかりません。__call()を呼び出しました。'; $this->render('index'); } } __call()メソッドが定義されていれば、後述するエラーコントローラよりも優先されて呼ばれます。 ただ、存在しないアクションメソッドが呼ばれた時のためにすべてのアクションコントローラに__call()メソッドを定義しておくのは面倒なので、エラーコントローラを用意するのが実践的だと思います。 ▼ エラーコントローラの用意 デフォルトで有効になっているErrorHandlerプラグインを利用するために用意しておきましょ アクションコントローラやアクションメソッドが見つからなかったり、アクションコントローラ内で例外が発生した時にここでエラー処理できます。 エラーコントローラファイル ...アクションコントローラとまったく同じ書き方で、「コントローラ名」「アクション名」が共に"error"だということがファイル名・クラス名・アクションメソッド名を見たら判りますね。 [application/controllers/ErrorController.php] view->errortype = $this->_getParam('error_handler')->type; } } ビュースクリプト [application/views/scripts/error/error.phtml] エラー発生 <body> エラー発生! エラーの種類:escape($this->errortype);?>
アクションコントローラは、少なくとも一つのアクションメソッドを持つクラス(設計図)です。 Zend_Controller_Actionクラスを継承したサブクラスとして作成します。
クラスなのでメソッドの集合体です。アクションメソッド(フロントコントローラからコールされてメインの処理を行う)以外にも初期化用のメソッドやアクション前後に呼ぶことのできるメソッドなどいろいろ記述することができます。
シンプルな記述例を以下に示します。これはコントローラ名が"index"、アクション名も"index"のデフォルトのアクションコントローラになります。
フロントコントローラが行うディスパッチによりアクションメソッドがコールされます。
アクションコントローラはクラスなのでそのクラスファイルを呼び出したらまずnew演算子を使って実体化(インスタンス化)した後に適切なアクションメソッドをコールすることになります。 その作業はディスパッチャーが「コントローラ名」と「アクション名」に基づいて自動的に行います。
したがって自動的に行うために「コントローラ名」と「アクション名」に基づいた命名規則が存在します。
「コントローラ名」は少なくとも一つはあるアクション(機能ごとに分けられた処理)をまとめる入れ物の名前だと考えることができるので、ファイル名やクラス名にこれを使うのはうまい手だと思います。
これまではアクションメソッドに関して書いてきましたが、アクションコントローラにはそれ以外にも定義できるメソッドがあります。そしてそれらは特定の役割りを持っています。
上記のように、呼ばれるタイミングがそれぞれ異なります。 タイミングが異なることを利用して、処理段階における確認作業等の記述場所がよりはっきりするかも知れません。
定義するもしないも自由なのでここぞというときに効果的に使いたいですね。
この他に、__call()というメソッドを定義してアクションメソッドが見つからない場合などのエラー処理を行うこともできます。
__call()メソッドが定義されていれば、後述するエラーコントローラよりも優先されて呼ばれます。 ただ、存在しないアクションメソッドが呼ばれた時のためにすべてのアクションコントローラに__call()メソッドを定義しておくのは面倒なので、エラーコントローラを用意するのが実践的だと思います。
デフォルトで有効になっているErrorHandlerプラグインを利用するために用意しておきましょ
アクションコントローラやアクションメソッドが見つからなかったり、アクションコントローラ内で例外が発生した時にここでエラー処理できます。
エラーコントローラファイル ...アクションコントローラとまったく同じ書き方で、「コントローラ名」「アクション名」が共に"error"だということがファイル名・クラス名・アクションメソッド名を見たら判りますね。
ビュースクリプト