Zend FrameworkでMVC

HOME

最終更新:2010/11/5

■ 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アプリケーションとして開発していくことになります。

Zend Framework ZF 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記述例

この設定により、
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" というファイル名で設定することが多いと思います。
では具体的な記述例を見ていきましょう。



▼ 基本的なフロントコントローラの記述




まずフロントコントローラの場合'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()でレスポンスまで完了した後にレスポンスオブジェクトを取得してあれやこれやする例



dispatch()ではレンダリング&レスポンスさせずに、レスポンスオブジェクトを受け取って処理する方法もあります。
returnResponse(true)とすることでそれができるようになります。



恐らくこれらは実践的じゃないでしょう。

フロントコントローラ(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]

Zend_Controller_Plugin_Abstractを継承したサブクラスを作成して、そこに実行させたいタイミングに応じたメソッドを記述していく。



▽ プラグインの使い方

フロントコントローラ [htdocs/index.php]

フロントコントローラに直書きまたは別のファイルにプラグインのクラスを書いて、registerPlugin()メソッドにそのインスタンスを渡すだけで機能します。



ちなみに、以下はイベント発生のタイミングを視覚的にわかりやすく表示させたものです。



▽ 表示結果
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要素によって実現していました。
  1. アクションコントローラの置き場所を教える
  2. コントローラ名の決定
  3. アクション名の決定


以上の3つに加えて、どのモジュールかを指定できれば特定のモジュールのアクションメソッドがコールできるということになります。



これまではアクションコントローラの置き場所は一つだけだったのでそれを教えるだけで済みました。
モジュールに分かれている場合はどのモジュールのアクションコントローラ置き場なのかを知らせる必要があります。

これまでコントローラ置き場を教えるのに使ってきたsetControllerDirectory()メソッドに今度はキーがモジュール名、値が置き場所のリストを持つ配列を与えることになります。



もし各モジュールのディレクトリ配置が整った配置になっていれば、各モジュールをまとめているディレクトリを教えるだけで指定できます。
その際にはaddModuleDirectory()メソッドを使います。

こちらの方がシンプルに書けます。ただし、ディレクトリ配置が整っていないとうまく伝わらないでしょう。



これまでのルーティングでは、リクエスト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の設定追加例



[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クラスを使ったルーティング設定追加

より柔軟性のあるルーティングができる。

第一引数で複雑な正規表現にマッチさせればいろんなディスパッチが実現できるようになります。

このルーティングだけは別途
require_once 'Zend/Controller/Router/Route/Regex.php';


の呼び出し記述が必要なようです。


■ アクションコントローラ

アクションコントローラは、少なくとも一つのアクションメソッドを持つクラス(設計図)です。
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]

  1. init() ...その名のとおり、定義されていれば最初に呼ばれて初期化作業などに使うメソッド
  2. preDispatch() ...その名のとおり、定義されていればディスパッチ前に呼ばれる。認証情報チェックなどに使う?
  3. (アクションメソッド) ...フロントコントローラからディスパッチされて呼ばれるメソッド
  4. postDispatch() ...その名のとおり、定義されていればディスパッチ後に呼ばれる。ビューの仕上げ処理など


上記のように、呼ばれるタイミングがそれぞれ異なります。
タイミングが異なることを利用して、処理段階における確認作業等の記述場所がよりはっきりするかも知れません。

定義するもしないも自由なのでここぞというときに効果的に使いたいですね。



この他に、__call()というメソッドを定義してアクションメソッドが見つからない場合などのエラー処理を行うこともできます。

[application/controllers/IndexController.php]

__call()メソッドが定義されていれば、後述するエラーコントローラよりも優先されて呼ばれます。
ただ、存在しないアクションメソッドが呼ばれた時のためにすべてのアクションコントローラに__call()メソッドを定義しておくのは面倒なので、エラーコントローラを用意するのが実践的だと思います。





▼ エラーコントローラの用意

デフォルトで有効になっているErrorHandlerプラグインを利用するために用意しておきましょ

アクションコントローラやアクションメソッドが見つからなかったり、アクションコントローラ内で例外が発生した時にここでエラー処理できます。



エラーコントローラファイル ...アクションコントローラとまったく同じ書き方で、「コントローラ名」「アクション名」が共に"error"だということがファイル名・クラス名・アクションメソッド名を見たら判りますね。

[application/controllers/ErrorController.php]

ビュースクリプト

[application/views/scripts/error/error.phtml]



▼ アクションヘルパー

アクションヘルパーには始めから有効になっている「ViewRenderer」があります。

アクションヘルパーはその名のとおり、アクションコントローラを助ける役割りを担っています。
その役割りは、アクションコントローラに共通の機能を付け足すことです。
つまり特定のアクションコントローラでは「ViewRenderer」機能をオフにしたいとか、リダイレクトでどこかに飛ばしたいとかそういった特定アクションコントローラに共通の処理をさせたいときのヘルパーと言えます。

アクションヘルパーの狙いは、 アクションコントローラに共通機能を追加するために いちいち抽象クラスを継承する手間を省くことにあります。



▽ タイミングいろいろ
メソッド名タイミング
init()ヘルパーの初期化
preDispatch()ディスパッチ前
foo(/* 自作メソッド */)アクションコントローラ側から呼ばれたタイミング
postDispatch()ディスパッチ後


▽ 使用できるメソッド
メソッド名内容
setActionController()アクションコントローラを設定
getRequest()リクエストオブジェクト取得
getResponse()レスポンスオブジェクト取得
getName()ヘルパーの名前取得


▽ アクションヘルパーの作り方

アクションヘルパー [application/controllers/helpers/Samplehelper.php]

Zend_Controller_Action_Helper_Abstractを継承したサブクラスを作成してメソッドを書いていきます。

アクションコントローラと同じZend_Controller_Actionコンポーネントを呼び出していることからアクションヘルパーは、アクションコントローラの親戚であることが想像できます。
親戚関係なのでアクションコントローラと同じinit(), preDispatch(), postDispatch()メソッドを定義することができます。

init(), preDispatch(), postDispatch()は実行されるタイミング(順番)が決まっているのでアクションヘルパーで記述すれば、それぞれアクションコントローラ側のinit(), preDispatch(), postDispatch()とシンクロしたタイミングで実行されることになります。



▽ アクションヘルパーの使い方

アクションコントローラ [application/controllers/IndexController.php]

init()ではヘルパーを登録しています。addPath()の第一引数はヘルパーの置き場所で、第二引数はヘルパーのクラス名のプレフィックス(頭の部分)に「Foo」を持つものをすべて登録しています。

getHelper()でヘルパーのインスタンスを取得しています。

アクションメソッドでは、自作したヘルパーの戻り値をビュースクリプトの「$this->str」へ代入しています。



ビュースクリプト [application/views/scripts/index/index.phtml]

ちなみに、以下は実行されるタイミングを視覚的にわかりやすく表示させたものです。



▽ 表示結果
Foo_Samplehelper::init()
IndexController::init(/* アクションコントローラのinit() */)
Foo_Samplehelper::preDispatch()
IndexController::preDispatch(/* アクションコントローラのpreDispatch() */)
Foo_Samplehelper::foo()
IndexController::indexAction(/* アクションメソッド */)
IndexController::postDispatch(/* アクションコントローラのpostDispatch() */)
Foo_Samplehelper::postDispatch()




▼ アクションコントローラ内で使えるメソッド

アクションコントローラはアクションメソッドを始めとするメソッドの集合体です。
それらメソッド内で使うことのできる(外部の)メソッドをここにメモしておきます。



ビュースクリプト側の変数$this->nameに、$valueを代入
$this->view->assign('name', $value);

ViewRendererアクションヘルパー内のpreDispatch()でビューオブジェクトが定義されているので、アクションコントローラ内ではいきなり「$this->view」として使うことができる。

スカラー代入の場合はassign()メソッドを使わなくても
$this->view->name = $value;
でもいける。



オブジェクトの取得
$req = $this->getRequest() ..リクエストオブジェクト取得
$res = $this->getResponse() ..レスポンスオブジェクト取得


スーパーグローバル変数値を取得するためのメソッド
getQuery() ..$_GET
getPost() ..$_POST
getCookie() ..$_COOKIE
getServer() ..$_SERVER

// 使用例
$req = $this->getRequest(); // リクエストオブジェクト取得
$foo = $req->getPost('foo');


リクエストパラメータ
_getParam(param) ..リクエストパラメータ取得
_setParam() ..リクエストパラメータ設定


処理の流れを変える
_forward(アクション名,コントローラ名,モジュール名,パラメータ) ..別のアクション実行
_redirect(URL, option) ..別のURLにリダイレクトを行う


フロントコントローラ側setParam()でセットされた値を取得する
$name = $this->getInvokeArg('name');



■ ビュー

HTMLページの描画(レンダリング)の役割りを担うZend_Viewコンポーネントを使います。
これはMVCではコントローラから呼ばれることになります。

フロントコントローラからdispatch()メソッドを呼ぶと、
ルーティング&ディスパッチの結果、アクションメソッドがコールされてそこでメインの処理が終わると最後にこのビューが呼ばれてレンダリングした結果をレスポンスをしています。

これはViewRendererアクションヘルパーが初期状態で有効になっているために自動的に呼ばれているためです。

ではビュー関連に注目してディレクトリ構造を見てみましょう。

 application/  (↓非公開ディレクトリ↓)
     +- models/        M関連置き場
     +- views/        V関連置き場
     |   +- filters/
     |   +- helpers/
     |   +- scripts/
     |       +- index/
     |           +- index.phtml (レンダリング用ビュースクリプト)
     +- controllers/     C関連置き場

 htdocs/     (↓公開ディレクトリ↓)
     +- index.php  (フロントコントローラ)
     +- .htaccess


まずviewsディレクトリ直下に「filters」「helpers」「scripts」の3つのディレクトリが存在します。

これらはそれぞれ「ビューフィルター」「ビューヘルパー」「ビュースクリプト」関連用の入れ物になります。



[描画] [描画後の微調整]
ビュースクリプトを仕上げる
  • 変数展開
  • ビューヘルパーの協力
ビューフィルターを使うと、仕上がったページに意図しない部分があれば取り除いたり、別のもの(タグ・語句)に置き換えたりできる。

ビュースクリプトはMVCにおいて、デフォルトのViewRendererモードには必要となるものと言えます。

また、ビュースクリプトをうまく構造化して管理・再利用していくためにもビューヘルパーを適材適所で使う必要があります。

ビューフィルターに関しては使わないに越したことはありません。が、万が一意図しない表示があったりすれば応急処置として一時的に使うことがあるかも知れません。





▼ ビューの全体的な指定

Zend_Viewのオプションを使うことにより、ビューの描画時の振る舞いを変更できます。

オブジェクト作成時のコンストラクタを使って、

$view = new Zend_View(array('escape' => 'htmlspecialchars', 'encoding' => 'sjis'));

またはオブジェクト作成後に、

$view = new Zend_View();
$view->setEscape('htmlspecialchars');
$view->setEncoding('sjis');


setEscape()で指定した関数名は、多くのビューヘルパーで表示用エスケープとして利用される関数になります。

setEncoding()で指定した文字コードは、多くのビューヘルパーで使用できる文字コードになります。指定された文字コードと違っていれば破棄されます。
なにも指定しないでgetEncoding()メソッドの返り値を見てみると「UTF−8」になっています。つまりUTF−8がデフォルトで許可されている文字コードということになります。



scripts, filters, helpers各種パス指定用のメソッドが用意されています。

ビュースクリプト置き場を教えるパス指定は以下のとおり

$view->setScriptPath('/path/to/scripts');
$view->addScriptPath('/path/to/scripts');

set系はパス指定、add系は追加のパス指定。
追加すると、最後に追加したパスからビュースクリプト探索を行うようになります。



あんまり使わないけどビューヘルパー、ビューフィルターにもパス指定メソッドがあります。

setHelperPath(), addHelperPath(), setFilterPath(), addFilterPath()など・・

その他、3つの置き場(scripts, filters, helpers)が一つのディレクトリ内にまとまっていればsetBasePath()一つで指定できるようになっています。



▼ 変数への代入

ビュースクリプト側の変数に値を代入することができます。

[実行スクリプト]
$view = new Zend_View();
$view->str = 'Hello world';
/* $view->assign('str', 'Hello world');と書くのと同じ */

直接代入する方法とassign()メソッドを使う方法があります。

こうして代入された値はビュースクリプト側から

[ビュースクリプト]
<h2>あいさつをする</h2>
<?php echo $this->str;?>

このようにして値参照できることになります。

配列の場合も同様にこんな感じで

[実行スクリプト]
$view = new Zend_View();
$view->str = array('Hello', ' ', 'world');
/* $view->assign('str', array('Hello', ' ', 'world'));と書くのと同じ */


[ビュースクリプト]
<h2>あいさつをする</h2>
<?php echo $this->str[0],$this->str[1],$this->str[2];?>


assign()メソッドはその他の特徴として第一引数に配列を渡す以下のような書き方ができます。

$view->assign(array(
    'foo' => '001',
    'bar' => '002',
    'baz' => '003',
));

これは次のように書いたのと同じことです。

$view->foo = '001';
$view->bar = '002';
$view->baz = '003';




▼ ビューヘルパー

ビュースクリプトの描画を手助けするのがビューヘルパーです。

ビューヘルパーにはいくつか種類があり、初めから用意されているものがそれなりの数あります。
が、使えるのはごく一部だと思います。
使えるヘルパーを吟味して、足りないものは自作して補うのがここでの腕の見せ所だと思います。



ビューヘルパーの基本形
$this->ヘルパー名();

変数と似ていますが、関数(メソッド)なので必ずカッコを伴います。



ビューヘルパーいろいろ
[1] フォーム関連のビューヘルパー
フォームタグおよびフォーム要素タグ作成に使うビューヘルパーです。
$this->formText('要素名', '初期値', 属性配列);と指定しておくと、
<input type="text" name="要素名" value="初期値">のようなタグ描画してくれます。
これを使うと記述量が減るわけでもないし、見通しが善くなる訳でもないので今のところこれを使う必要性を感じません。

[2] フォーム以外のタグ用ビューヘルパー
$this->htmlList($array);を使うと、<ul>リストにすることができます。
$this->htmlList($array, true);で順序付き<ol>リストにできます。

[3] アクションビューヘルパー
$this->action(アクション名, コントローラ名[, モジュール名]);と書くことで、特定のアクションの返す値を利用できるようにするためのものです。
ビュー側で使うためにわざわざアクションメソッドを用意するのも本末転倒なので、たまたま作っていたアクションメソッドがビュー側で再利用できるものだった的な使い方になると思います。今一つピンときません。
もしかしたら、一旦ビュー側へ移った処理の過程でアクションにアクセスできるのがウリ?

[4] パーシャルビューヘルパー
$this->partial('foo.phtml', array('変数名' => '値'));と書くと、ビュースクリプトから他の指定したビュースクリプトを呼ぶことができます。
呼び出した先では別の名前空間になるのでそこで使う変数を連想配列を使って渡すことになります。
呼び出す側ビュースクリプトがレイアウトスクリプトだとしたら、そこから呼び出されるパーシャルスクリプトは当然パーツ的な記述となります。
パーツで管理して使いまわす有効な手段かも知れません。

[5] パーシャルループビューヘルパー
前出のパーシャルビューヘルパーをループ処理させる感じです。なのでパーシャルビューヘルパーでは1次元配列を渡していましたが、今度は1次元配列を要素数(ループ数)だけ持つ配列を渡すことになります。
$this->partialLoop('foo.phtml', array(array('変数名' => '値'), array('変数名' => '値')));
指定したスクリプトがループ回数(配列の要素数)反復処理されるということになります。

[6] プレースホルダ
$view->placeHolder('name')->set('data');と書いて連想配列みたいに値をセットしておき、描画時に$this->placeHolder('name');と書くことでセットされた値を参照できるようになります。
これだけのことなら変数を使うのと何ら変わらないわけですが、単にset()する以外にもsetPrefix()とすれば先頭付加、setPostfix()とすれば末尾付加、setSeparator()とすれば間付加にできるので別の処理段階で使っても位置指定ができるということになります。
このplaceHolder()を特化させたものがDoctype(),HeadLink(),HeadMeta(),HeadScript(),HeadStyle(),HeadTitle(),InlineScript()といったヘルパーになります。
主にHTML文書を表す部分をセットしておき、描画時に引数なしで使うとタグ付きで返します。

[7] JSONヘルパー
$this->json(data);とすると、JSON形式のデータを返します。これを使用すると内部で適切なヘッダーを設定してレイアウト機能を無効にします。
JSON形式なので渡すデータは文字コードUTF−8にしておく必要があります。

[8] ナビゲーションヘルパー
ページ情報をまとめておいて、メニューやパンくずリスト、サイトマップなどの作成に利用できます。

[9] ビューヘルパーの自作
開発経験を積んでいくと、ビュースクリプトのレイアウトコレクションが増えていきここが充実してくると思います。
上記の各種ビューヘルパーの有用なものをまとめたり微調整したりして使いやすくすることはもちろん、開発の成果がここに現れると思います。


ビューヘルパーの自作

Zend_View_Helper_が頭に付いたクラス名でクラス作成をします。

作成するビューヘルパー名を「Foo」とすると、
クラス名は「Zend_View_Helper_Foo」となり、
foo()メソッドをクラス内に作って、
ファイル名はFoo.phpに、
ファイル置き場はapplication/views/helpers内になります。

自作ビューヘルパー [application/views/helpers/Foo.php]

クラス名・ファイル名は先頭大文字で、メソッド名が先頭小文字になります。

setView()メソッドを定義することでビューオブジェクトにアクセスできるようになります。

自作したビューヘルパーはビュースクリプト側から以下のようにして呼び出すことができます。

<?php echo $this->foo();?>

こう書いただけで便利な描画を行ってくれるヘルパーをどんどん作っていきたいものです。





▼ ビュースクリプト

ビュースクリプトはアプリケーション画面のレイアウトパターン数だけ用意するのが理想だと思います。
必要最小限の汎用的なレイアウトを用意し、できるだけ使いまわすようにすれば少ない数で済みます。

と考えると、そのままではコントローラ名とアクション名の組み合わせによって読みに行くビュースクリプトが決まるので、その組み合わせ数のビュースクリプトを用意しないとなると、アクションの処理中に希望のレイアウト(ビュースクリプト)をいちいち指定する必要が出てきます。
そのような時のためにZend_Layoutを使う方法もあります。

その他どんな方法を使ったらビュースクリプト作成と利用が楽にできるかは腕の見せ所だと思います。



それではビュースクリプト例を考えていきます。



画面としては特に凝ったレイアウトでもない、大きい文字のタイトルがあって、普通サイズ文字でなにかコンテンツを表示するだけのものです。

見てのとおり、描画を手助けしてくれるビューヘルパーをふんだんに使って、いろんな画面用のビュースクリプトとして使いまわしが利くように作っています。


$this->escape()は表示用エスケープを変数$this->strに対して行っています。
それ以外はすべてビューヘルパーを使っています。ビューヘルパーは描画時に必要な表示用エスケープを行うので$this->escape()に通す必要はないです。


まず最初に$this->docType()を書いています。
これは<DOCTYPE>タグを描画してくれるものです。
指定したDOCTYPEにより、他のビューヘルパーの描画タグもそれに対応する形式が使われるようになります。

現状(ZF ver1.10.0)では下記の指定が可能です。

XHTML11
XHTML1_STRICT
XHTML1_TRANSITIONAL
XHTML1_FRAMESET
XHTML_BASIC1
XHTML5
HTML4_STRICT
HTML4_LOOSE
HTML4_FRAMESET
HTML5

指定していない場合はデフォルトのHTML4_LOOSE形式が描画されます。

また、"<!DOCTYPE"で始まる指定を直接書くカスタム指定もできます。(携帯サイト用など足りないものはこれを利用できる)


次に<head>タグ内に連続でビューヘルパーを使っています。
もしこれが汎用的な記述なら、1つの自作ビューヘルパーにこれらをまとめることができそうです。

headMeta()は、<meta>タグ情報が事前にセットされていればそれをタグ描画します。セットされていなければ空文字を返すだけです。

headLink()は、<link>タグ情報が事前にセットされていればそれをタグ描画します。セットされていなければ空文字を返すだけです。

headTitle()は、<title>タグを描画します。
ここでは$this->placeHolder('title')を渡しているのでこれが例えば「Zend Framework」という文字がセットされていた場合には
「<title>Zend Framework</title>」を描画することになります。

だとしたらもっとシンプルな書き方ができそうですが、headTitle()には便利な機能があって、頭の方に指定文字を描画させることができます。
なので

$this->headTitle('私のサイト - ', 'PREPEND');
$this->placeHolder('title')->set('Zend Framework');

echo $this->headTitle($this->placeHolder('title'));

とすると

<title>私のサイト - Zend Framework</title>

のように描画させることができます。


もちろん以下のようにしても

$this->headTitle('私のサイト - ', 'PREPEND');
$this->headTitle('Zend Framework');

同様のことができるし、タイトルをプレースホルダではなく単なる変数に持たせることもできます。

$this->headTitle('私のサイト - ', 'PREPEND');
$this->title = 'Zend Framework';


ただし、単なるビュー変数に持たせた場合は描画時$this->escape()に通す必要があるでしょう。
また、タイトルを<title>と<h1>の2ヶ所で使っているのでheadTitle()とplaceHolder('title')に分けています。



ここでもし、placeHolder('title')への値セットがなんらかの理由でされなければ

<title>私のサイト - </title>

という描画になってしまいます。
こんな時どうすればいいでしょう。。。 ということでこんな時の為にビューフィルターがあります。

レンダリングが完成したページに不本意なゴミがあれば取り除くことができます。
この続きはビューフィルターのところに書きます。



つい先ほど、headTitle()ビューヘルパーにはsetSeparator()メソッドが使えることを知りました。
なので以下を書いておけばビューフィルターを使うまでもないということになります。
$this->headTitle()->setSeparator(' - ');
(2010/10/29更新)


そんなこんなで、先ほど作成したビュースクリプトを更に使いまわせるように書き直してみます。



まず1行目がdocType2()に変わりました。これは自作ビューヘルパーということになります。

わたし個人的にはDOCTYPEを書かないので、セットされてない場合には空文字になるようにするためのdocType()のラッパーになります。

docType()はセットされていなくても記述していればデフォルトのDOCTYPEを描画するので私にとっては少々不便です。

[application/views/helpers/DocType2.php]

ビュー変数doctypeが空じゃない時だけdocType()ヘルパーで描画しているだけです。



次にhead()ビューヘルパーに変わりましたが、これも自作ということになります。

単に以前のheadMeta(), headLink(), headTitle()をまとめたようなものになりますが、描画時の末尾改行がタグ外にある分もなぜか削られてしまうのでそのへんもきれいに描画するためのものでもあります。

[application/views/helpers/Head.php]

ビューヘルパーで追加描画した時の末尾文字が">"なら改行付加を繰り返しているだけです。
これをいちいちビュースクリプトに記述するよりもこうして自作ビューヘルパーにまとめた方が楽な気がします。



そしてh1()ビューヘルパーですが、これも自作になります。

<h1>タグはSEOの面で重要視されるタグです。そのページにタイトルがあればそこに1度だけ使うことになるでしょう。

前回のように<h1>タグをハードコーディングしていれば、タイトルのない画面では使えないレイアウトスクリプトになってしまいます。(まぁ無理やり使うことはできますが・・)

そこで、タイトルがセットされてるときだけ<h1>タグ付きで描画する自作のh1()ビューヘルパーを作ってみたわけです。



以上のように、多くの画面用として使いまわせるようにビュースクリプトを作成できればかなり楽ができると思います。

次は描画のダメ押し、ビューフィルターについて書いていきます。





▼ ビューフィルター

ビューのレンダリング結果ページからなにか(タグや文字など)を除去したい、あるいはなにかを付け足したいといった場合に、フィルターに通す感じで追加処理できます。

なのでHTMLソースをpreg_replace()関数を使って置き換える形になる場合が多いと思います。



ビューフィルターは始めから付属しているものがないので、必要に応じて自作することになります。

作り方は以下のとおりfilter()メソッドを持つクラスとして定義することになります。


ビューフィルターの自作

Zend_View_Filter_が頭に付いたクラス名でクラス作成をします。

作成するビューフィルター名を「Foo」とすると、
クラス名は「Zend_View_Filter_Foo」となり、
filter()メソッドをクラス内に作って、
ファイル名はFoo.phpに、
ファイル置き場はapplication/views/filters内になります。

自作ビューフィルター [application/views/filters/Foo.php]

これは渡ってきたHTMLソースになにもせずにそのまま返すだけを行っています。(つまりなにもしないフィルターです)



このように自作したフィルターをビュー最後の処理として施すようにするために登録を行う必要があります。

簡単です。ビューオブジェクトのaddFilter()メソッドにフィルター名を登録するだけで済みます。

$view->addFilter('Foo');
$view->addFilter('Bar');
$view->addFilter('Baz');

このように、通したいフィルター名を書き連ねるだけで順番に実行されることになります。



では、ビューヘルパーのheadTitle()とplaceHolder()を使ったところで行ったタイトル描画で、以下のようになった時に除去するフィルターを作ってみます。

<title>私のサイト - </title>


自作ビューフィルター [application/views/filters/FixTitle.php]

このように作ることができました。
これを$view->addFilter('FixTitle')と登録しておくことでこのフィルターを通すことができるようになります。



その他どういう場合にビューフィルターを使えば便利か考えてみます。


例えば、「gooooooooooooooooooooooooooooooooooooooooooogle」などとした場合に、レイアウトによっては表示が崩れる場合があります。アルファベットが連なってるとブラウザでは一つの英単語だと見なして自動改行してくれないのでこれを未然に防ぐフィルターなんてどうでしょう。


その他、やり取りしている変数内容を一覧表示させたり実行時間を表示させたりして開発の手助けとして役立てるとかも考えられます。


あとは携帯サイトでキャリアごとに対応した絵文字に置き換えるなんてどうでしょ




■ モデル

モデルとして使われることの多いZend_Dbコンポーネントについて、下記リンクにまとめました。

Zend Framework - Zend_Db編




■ MVCまとめ


● フロントコントローラ
[フロントコントローラの説明と作り方]
概要すべてのリクエストを一手に受け付けて、メイン処理をアクションコントローラのアクションメソッドに投げてビューのレンダリング結果を受け取ってレスポンスをする。
呼び出し元リクエスト + リライト機能
呼び出し先アクションコントローラ、 ビュー
できることプラグイン登録、ルーティング、ディスパッチ
記述場所htdocs/index.php
必要なクラスrequire_once 'Zend/Controller/Front.php';
記述方法必要なクラスを呼び出してインスタンスを取得したら、アクションコントローラ置き場を教える(setControllerDirectoryメソッド)。最後にdispatchメソッドを呼ぶ。このdispatchメソッド内部でルーティング&ディスパッチ&アクションにメイン処理依頼&レンダリング&レスポンスが行われる。 [記述例]
[プラグインの説明と作り方]
概要フロントコントローラが行う処理の特定のタイミング(ルーティングやディスパッチ前後)で、指定したコードを実行させる。
呼び出し元フロントコントローラ
できること特定のタイミングで値を収集したり、セットしたりできる。値を変えることで挙動が変わり、そのことが結果的に機能を追加したような感じになる。
記述場所フロントコントローラに直書き、または別ファイルに書いてそれを呼び出す
必要なクラスrequire_once 'Zend/Controller/Front.php'を書いていればZend/Controller/Plugin/Abstract.phpも呼ばれることになる
記述方法Zend_Controller_Plugin_Abstractを継承したサブクラスとして作成する。フロントコントローラが行うルーティングやディスパッチ処理の前後に機能するそれぞれのメソッド(routeStartup, routeShutdown, dispatchLoopStartup, preDispatch, postDispatch, dispatchLoopShutdown)を定義することになる。[記述例]
使用方法自作したプラグインはフロントコントローラからZend_Controller_Front::registerPlugin(new FooPlugin())とすることで登録できます。

● アクションコントローラ
[アクションコントローラの説明と作り方]
概要リクエストに応じて実行するメイン処理(アクションメソッド)を束ねたコントローラファイル。ディスパッチャがリクエストされたコントローラ名とアクション名に基づいて適切なアクションメソッドをコールする。必要に応じて特定のタイミングで実行させることのできるメソッド(init, preDispatch, postDispatch)を定義しておくとディスパッチ前後の特定のタイミングで処理させることができる。
呼び出し元フロントコントローラによるディスパッチ
呼び出し先アクションヘルパー、データの出し入れが必要ならモデルに依頼、ビューヘルパー、ビュースクリプト
できること必要に応じてモデルとやり取り。ビューの準備(置き換える値をビュー変数にセットしたり、レンダリング用のビュースクリプトをrenderメソッドで指定)、ビュースクリプトの描画
記述場所application/controllers/{先頭大文字コントローラ名}Controller.php
必要なクラスrequire_once 'Zend/Controller/Action.php'
記述方法Zend_Controller_Actionを継承したサブクラスとして作成する。最低でもアクションメソッドを1つは用意しておく必要がある。[記述例]
[アクションヘルパーの説明と作り方]
概要アクションに共通の機能を追加することができます。逆に考えると、アクション間で共通の処理や設定があればアクションヘルパーにくくり出すことでアクションの記述を減らすことができ、機能を使いたいアクションコントローラからはアクションヘルパーを呼んで、不要なコントローラからは呼ばなければいいということになります。
呼び出し元アクションコントローラ(init, preDispatch, アクションメソッドなど)
できることアクションと並行するタイミング(init, preDispatch, postDispatch)で機能を追加できる。ヘルパーメソッドはいくつでも定義できるのでアクション側から必要なヘルパーメソッドを必要なタイミングで呼ぶことができる。
記述場所application/controllers/helpers/{先頭大文字ヘルパー名}.php
必要なクラスアクションコントローラで'Zend/Controller/Action.php'を呼んでいるので'Zend/Controller/Action/Helper/Abstract.php'は呼ぶ必要なし
記述方法Zend_Controller_Action_Helper_Abstractを継承したサブクラスとして作成する。最低1つのヘルパーメソッドを記述する。[記述例]
使用方法アクションコントローラからZend_Controller_Action_HelperBroker::addPath(置き場所, クラス名のプレフィックス)とすることで該当するヘルパーが登録される。これを$this->_helper->getHelper('先頭大文字ヘルパー名')->ヘルパーメソッド();でコールすることができる。

● ビュー
[ビュースクリプトの説明と作り方]
概要ウェブページが完成する前の設計図を書いたスクリプト。アクション(メイン処理)でセットされた変数値を反映させ、ビューヘルパーでタグ描画を施してレスポンス用のHTML文書を完成させることになります。
呼び出し元アクションで実行されるrenderメソッド内部
呼び出し先ビューヘルパー
できること様々なレイアウトパターンを用意しておくことでいろんな画面表示
記述場所application/views/scripts/コントローラ名/アクション名.phtml(デフォルトで呼ばれる形式)
記述方法基本的にはHTML文書そのもので、
<?php echo $this->変数名;?>で変数値を置き換え、
<?php echo $this->ヘルパー名();?>でビューヘルパーにタグ描画させることができる。[記述例]
[ビューヘルパーの説明と作り方]
概要ビュースクリプトのタグ描画を手助けする。
呼び出し元アクション(メイン処理)、ビュースクリプト、別のビューヘルパー
できることフォーム要素やヘッダタグ描画の他、アクションメソッドの返り値やパーツ的なビュースクリプトを利用したタグ描画、Zend_Navigationなどのコンポーネント機能と関連したタグ描画など
記述場所application/views/helpers/{先頭大文字ヘルパー名}.php
必要なクラスなし
記述方法Zend_View_Helper_{先頭大文字ヘルパー名}というクラス名形式でクラス作成する。setViewメソッドを用意して、ビュースクリプトで扱うビューオブジェクトにアクセスできるようにする。メソッド名は先頭小文字ヘルパー名になる。[記述例]
使用方法ビュースクリプトで<?php echo $this->ヘルパー名();?>の形式
[ビューフィルターの説明と作り方]
概要ビュースクリプトの描画が終わった後に呼ばれる処理。描き上がったページから余計な部分を取り除いたり、付け足したりすることができる。最初から用意されているフィルターはないので必要に応じて自作する。
呼び出し元ビューのrenderメソッド内の最後
できることページのタグ部分や文字部分を削ったり、情報をページに追加したり
記述場所application/views/filters/{先頭大文字フィルター名}.php
必要なクラスなし
記述方法Zend_View_Filter_{先頭大文字フィルター名}というクラス名形式でクラス作成する。filterメソッドを用意してそこにフィルター処理を書く。[記述例]
使用方法アクションやビュースクリプトでビューのメソッド$view->addFilter('フィルター名')を使って必要なフィルターを追加していく




■ MVCをもっと単純に考えてみる


MVCは、オブジェクト指向の思想とからんだ独特な構造をしていて、いろんな聞きなれない用語が出てきて困惑してしまいます。

なので混乱して振り回されないように違う視点から単純化して考えてみたいと思います。


逆にMVCを手玉に取るぐらいの気持ちでいきましょう。
とは言ってもMVC設計した人だって混乱させようとして作ったわけじゃなくて、処理をきっちり役割り分担させた最良の分かり易い方法がコレだったんでしょう。



CGIでよくリクエストパラメータを渡して処理分けしているのを見たことがある人は多いでしょう。
http://サーバ名/index.cgi?action=search

「action=処理名」とか「mode=処理名」みたいなものをCGI側で受け取って、次にどの処理をするか振り分ける方法です。



こうやって振り分けた先(上の例ではread, search, delete関数)でメインの処理をおっぱじめることになります。

この振り分けはちょうどMVCでリクエストをフロントコントローラが受け付けて、ルーティングしてディスパッチでアクション側にメイン処理を依頼するまでにそっくりです。



http://サーバ名/index.php?controller=index&action=index

上記のリクエスト形式を

http://サーバ名/index/index

シンプルな形にして、これをリライト機能を使ってすべてindex.phpが受け取るように強引に書き換える方法を使っています。

これは大人のやり方ですね。
発想は子供の発想ですが、このずる賢さは大人のやり方です。

プログラミングではこのずる賢さは非常に重要です。時に問題解決のスマートな方法に成り得ます。

この方法を考えた人に会ったら、ジャックダニエルを一杯ぐらいおごりたいものです。



ここまでで、MVCのC(フロントコントローラ)が担当しているメイン処理へ持っていくまでの振り分けが終わりました。
次もC(アクションコントローラ)が主導権を持つメイン処理(アクション)開始です。



メイン処理では、

  • 送られてきた情報自体に処理を施したりして、これを保存したり結果表示など
  • 指定された保存情報の取り出し(検索・抽出)、更新・削除などして結果表示

送られてきた情報にちょっとした処理を加える程度だったらそのまま記述するでしょう。

コードが膨らんだり、再利用できるものだったら関数化してメイン処理の見通しをよくした方がいいでしょう。

再利用性の高いデータの出し入れ(保存・検索・更新・削除)もまた、関数化して見通しをよくすることでバグの入り込む余地を減らすようにします。


このデータの出し入れを、MVCではM(モデル)が担当するように設計されている感じだと思います。

要はメイン処理の見通しをよくしてバグの入り込む余地を減らすための手段の1つがM(モデル)だと捉えることができます。

逆に言うと、メイン処理には単純にコードを羅列して見通しが悪くなる書き方だってできてしまいます。
こうなると後で読み返すのも大変なメンテナンスし難いものになってしまいます。
なのでここの切り分けは腕の見せ所の1つになると思います。



ここまでで、MVCのCとMが協力して行うメイン処理が終わりました。
メイン処理が終わったらVに描画を依頼することになります。



以前よく使われていたシンプルなテンプレート方式では、



上記のようなテンプレートを用意して「%キーワード%」部分を置き換えてページ作成していました。
こうすることでページが同じレイアウトで同じデザインになり統一感のあるサイトに仕上げることができます。

これはVがビュースクリプトを用意して描画するのとよく似ています。


高機能なテンプレートになると、forループやif文などの制御構造を盛り込んだり外部ファイルの取り込みなどもできたりします。

ビュースクリプトもスクリプトと名の付くとおり「<?php PHPコード;?>」を書けばPHPコードで処理できることはなんでもできます。
しかも、何度も利用するタグ描画はビューヘルパーにしておくことで描画が楽になるしその分コーディングも楽になります。

つまりビューをうまく使いこなせれば、コーダーの負担を減らすことができるわけです。


Webアプリケーションを大勢で役割り分担して作るとなると、画面レイアウトおよびデザインをデザイナーに任せ、データベース周りがあればDBAに任せ、残りをコーダーが担当することで開発の分担ができます。

なので少しでもコーダーの負担を減らす意味でも再利用できる便利なビューヘルパーの自作は腕の見せ所だと思います。




■ MVCアプリケーションを作ってみる


実際に作りながら動きを見ていかないと覚えることはできません。

私自身、Zend Frameworkはおろかフレームワークを始めたばっかりでフレームワークでWebアプリケーションと呼べるものを作ったことがありません。それどころかPEARさえも使ったことがありませんでした。
いろんなサイトを見て回ってだいたいの感じはつかめてきたので、そろそろなにか作ってみようと思います。


その為には、まず開発環境(インストール・設定)を整えないといけません。
これは既に完了しています。

次に、何を作るか決めないと先へ進めません。
左サイドにメニューのあるホームページで、掲示板を付ける(とりあえずRDBMSは使わないテキストベースのやつ)

あとはそれに向かって突き進む
これはいくつかの段階に分けて進めることにします。
【第1フェーズ】 …ファイル構成と配置を決める
標準的な配置を基本にして、なるだけシンプルな構成を心掛けます。
一箇所でグローバル設定できるようにする要素をリストアップします。(要素によってはアクション中に設定値を上書き変更してそれが反映できた方がいいものもあるのでとりあえずリストアップ)
【第2フェーズ】 …ファイルを作成
所定の配置にファイルを作成し、現段階で書けるコードを一通り書いてみる。
この段階でトップページの表示はできるようにしておく。
【第3フェーズ】 …開発しやすくする
エラー発生時の詳細表示ややり取りしている変数内容などを表示させるようにして、開発時と運用時で表示を切り替えられるようにする。
【第4フェーズ】 …画面レイアウトに着手
作成するアプリケーションの表示画面を頭に描いて、大まかなレイアウト(エリア分け)をしておく。
スタイルシートと絡めたカラム分けの他、文字色・背景色などもここで決めておく。
私の苦手分野である画像等を使ったデザインまでは手が回らないので保留にします。
ビュースクリプト(レイアウトスクリプト)にレイアウトを書いていく中で、そのままタグを埋め込んでしまうと他で使い回しが利かなくなりそうな部分をビューヘルパーで描画するようにする。
【第5フェーズ】 …メイン処理を書く
「第1フェーズ」でリストアップしたグローバル設定要素の値を、全アクションコントローラで自動的に使えるようにするためにアクションヘルパーを使って(基本的にビュー変数に)セットする。なのでコントローラからヘルパーを呼ぶ記述が必要。
メイン処理(アクション)はデータの出し入れ部分をモデルに分離してクラス化して、それ以外をアクションメソッド内に記述する。コントローラ内で何度も使うような処理ならコントローラ内のprivateなメソッドにするも良し。
【第6フェーズ】 …なんかする
最後にいろいろ確認とかするかな?


以上が私の目算です。

うまく作っていくことができれば、第4フェーズまではどんなサイト作りにも流用できるファイル構成と内容にできると思います。
次からはそれを使えばかなり楽ができそうです。うっシッシ



というわけで、早速MVCアプリケーションを作ってみました。
まだ途中課程ですが、実際作ってみるといろんな発見があるものです。

MVCアプリケーション作成の練習(2010/11/6更新)

2010(C)Mingw