前回はエラー画面を表示させる仕組みを加えました。
と同時に日本語文字をビュー変数にセットして表示させるやり方、ビュースクリプトで$this->escape()メソッドを使ったサニタイズも行いました。
これでやっとアプリケーション開発へ進んで行けそうです。

では一気に世界を広げていきましょう!!

これまでトップページのみを使って作ってきました。
トップページ以外を作るのは特別なことではなく、それと同じやり方になります。

そこでこれまで作ってきたトップページを整理しましょう。
1.「アクションコントローラ」作成
application/controllers/直下にIndexController.phpファイルを作成しました。
このファイルにIndexControllerクラスを定義しました。
クラス内にはindexActionメソッドを用意しました。

2.「ビュースクリプト」作成
application/views/scripts/index/直下にindex.phtmlファイルを作成しました。
このファイルにページの雛形を書きました。


以上のようにしてトップページが作成できたわけです。
目に付くのは、やたらindexというフレーズがあることです。

実はこの「index」というのはこれまで適当に使ってきたわけではなくて、「命名規則」に沿って書いていたものでした。
「index」はデフォルトに割り当てられてることもあり、トップページ作成のためにindexを使ってきたことになります。

それでは「命名規則」をまとめます。

1.「アクションコントローラ」作成
application/controllers/{コントローラ名}Controller.phpファイル作成
このファイルに{コントローラ名}Controllerクラスを定義
クラス内には{アクション名}Actionメソッドを用意

2.「ビュースクリプト」作成
application/views/scripts/{コントローラ名}/{アクション名}.phtmlファイル作成
このファイルにページの雛形を書く


ページに結びついてる名前は「コントローラ名」「アクション名」の2つであることが分かります。
クラス名は英大文字から始めるルールに従って、FooControllerのようになります。
クラス名とそのクラスが定義されたファイル名は一致させるようにします。
なのでファイル名頭も英大文字で始まります。

以上のことからトップページは「コントローラ名」がindex、「アクション名」もindexだったことなります。

これまでトップページにアクセスするためにベースURLを見ていました。
http://localhost/

これに「コントローラ名」を指定してアクセスすると
http://localhost/index

さらに「アクション名」も指定してアクセスすると
http://localhost/index/index


つまりベースURLへアクセスした時は「コントローラ名」も「アクション名」も指定されなかった為に、コントローラ名・アクション名両方ともデフォルト値のindex扱いになりページを見ることができたわけです。

ではトップページ以外だとどんなアクセスが考えられるでしょうか?

【ケース1】
まず「コントローラ名」はindexで、「アクション名」がindex2の場合が考えられます。
この場合、以下のようにアクセスします。
http://localhost/index/index2


【ケース2】
次は「コントローラ名」がfooで、「アクション名」がbarの場合が考えられます。
http://localhost/foo/bar


【ケース3】
その他「コントローラ名」がfugaで、「アクション名」がindexの場合が考えられます。
http://localhost/fuga/index

またはindexは省略しても補完されるので
http://localhost/fuga


それではケース1~3の「アクションコントローラ」と「ビュースクリプト」を作成してみましょう。

【ケース1】
アクションコントローラ - IndexController.php
<?php
require_once 'Zend/Controller/Action.php';

class IndexController extends Zend_Controller_Action
{
  public function index2Action() {

  }
}

IndexControllerクラスにindex2Action()メソッドがあります。
今後は上記形式をIndexController::index2Action()のように記述していきます。

ビュースクリプト - index/index2.phtml
<html>
<body>
  <h3>コントローラ名とアクション名</h3>
  <p>index, index2</p>
</body>
</html>


【ケース2】
アクションコントローラ - FooController.php
<?php
require_once 'Zend/Controller/Action.php';

class FooController extends Zend_Controller_Action
{
  public function barAction() {

  }
}

FooController.phpファイルを用意してそこにFooController::barAction()を定義

ビュースクリプト - foo/bar.phtml
<html>
<body>
  <h3>コントローラ名とアクション名</h3>
  <p>foo, bar</p>
</body>
</html>


【ケース3】
アクションコントローラ - FugaController.php
<?php
require_once 'Zend/Controller/Action.php';

class FugaController extends Zend_Controller_Action
{
  public function indexAction() {

  }
}

FugaController.phpファイルを用意してそこにFugaController::indexAction()を定義

ビュースクリプト - fuga/index.phtml
<html>
<body>
  <h3>コントローラ名とアクション名</h3>
  <p>fuga, index</p>
</body>
</html>


どうでしょうか?
このページ作成のルールが分かれば、無尽蔵にページを増やしていけそうです。
まさに世界が広がったと言えるんじゃないでしょうか。


もう1つ書いておきます。

「コントローラ名」って何?
「アクション名」って何?
ってなると思います。

私が思うに、、、
1つのアプリケーションを作成していく際にapplicationディレクトリに階層で整理して作っていきます。
アプリケーションに持たせる機能を大分類するといくつかに分けることができる場合があります。この大分類ごとに別のアクションコントローラに分けて作成します。
その時に割り当てた名前が「コントローラ名」ということになります。

また、1つのアクションコントローラには同じ大分類に属するページ処理(アクションメソッド)を書いていくことになります。
このメソッドに割り当てた名前が「アクション名」ということになります。

なので「コントローラ」と「アクション」は1対多の関係になります。

なので以下のように階層分けできることになります。
コントローラ1/
    ├ アクション1
    └ アクション2

コントローラ2/
    ├ アクション1
    ├ アクション2
    └ アクション3


1つのアプリケーションのコントローラ数は大分類した数だと考えると、大規模システムでもそれほど多数にはならないでしょう。
なのでapplication/controllers/直下にアクションコントローラを置くのがシンプルで分かりやすいわけです。

一方、ビュースクリプトの場合はapplication/views/scripts/直下に「コントローラ名」でディレクトリ分けすることでファイルが膨大になっても管理しやすいわけです。


前回はMVCそれぞれの役割りを書いてデータの流れを見ました。
でもまだトップページへのアクセスにしか対応できていません。

なので以下のようにトップページ以外にアクセスしてしまうと、外向けに見せたくないエラー画面になってしまいます。
http://localhost/test
http://localhost/foo/bar


そこでエラー画面を用意しておきます。
エラー画面は1度用意しておけば、別のシステム開発でも再利用できる資源となります。

ではエラーが発生した際に呼ばれるエラーコントローラを作成します。

エラーコントローラ - ErrorController.php
<?php
require_once 'Zend/Controller/Action.php';

class ErrorController extends Zend_Controller_Action
{
  public function errorAction() {

    $this->view->setEncoding('Shift_JIS');
    $this->getResponse()->clearHeaders()->clearBody();

    $errors = $this->_getParam('error_handler');

    switch ($errors->type) {
    case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
    case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
    case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
      $this->view->errmsg = 'そのページは存在しません。';
      break;
    default:
      $this->view->errmsg = '予期せぬエラーが発生しました。';
      break;
    }
  }
}


上記クラスを見て気づくかも知れませんが、以前作成したIndexController.phpに外形が似ています。
同じ親クラスから派生した兄弟のようなものです。

次にコントローラがビューに描画を指示する際の雛形ページとなるエラー用のビュースクリプトを作成します。

ビュースクリプト - error.phtml
<html>
<head>
<title>エラー発生!</title>
</head>
<body>
  <h1>エラー発生!</h1>
  <p><?php echo $this->escape($this->errmsg);?></p>
</body>
</html>


ファイル配置
Zend/
application/
    models/
        IndexData.php
    views/
        scripts/
            error/
                error.phtml
            index/
                index.phtml
    controllers/
        ErrorController.php
        IndexController.php

htdocs/
    .htaccess
    index.php


配置を見ると、アクションコントローラのファイル名とそこに書かれているクラス名やアクションメソッド名、ビュースクリプトのscripts以下の階層分けとファイル名などある一定の規則がありそうなのが分かります。
この規則はデフォルトなので、変更可能なものです。
つまり開発を楽にする為だったらどんどん設定変更していい部分です。
ただし、デフォルトのやり方はきっちり理解しておいた方がいいでしょう。初見のZFで構築されたシステムに楽々対応できるようにしておく為にも・・。

ではビュースクリプトから見てみます。
以前作成したビュースクリプトと違う部分は、ビュー変数$this->errmsgをそのまま表示させずに$this->escape()というビューメソッドに渡しています。
これはサニタイズの働きをしてクロスサイトスクリプティング攻撃対策となるものです。
アクション側でビュー変数にセットされたデータにはHTMLタグの働きをする文字なんかが含まれているかも知れません。
その場合対象文字をエスケープしておかないと思ったとおりの表示ができなくなります。

このエスケープ処理にはデフォルトでPHP組み込みのhtmlspecialchars()関数を使っています。

一方、エラーコントローラですが、日本語を使っているのでsetEncoding()メソッドで使用文字セットを指定する必要があります。
デフォルト「UTF-8」指定になってるようなので、もしビュー変数にUTF-8以外をセットしたらそのままではフィルターがかかって表示されないでしょう。
これは画面表示を担当するZend_Viewクラスのソースを読めば分かりますが、ビュー描画で$this->escape()メソッドを使った場合に内部でhtmlspecialchars()関数の第3引数に文字エンコーディング指定を渡しているので、理解できない文字がきた場合にフィルターがかかってしまいます。

以下のページにある文字セットが指定できます。
PHPマニュアル/htmlspecialchars

前回はデータ入出力はモデルに任せるべきと書いておきながら、今回はエラーコントローラに直接データを書いてしまいました。
エラーコントローラの場合はコード量がそんなにないので却って直書きの方が管理しやすいのと、今後基本的にコードが増えることは無いというのもあって、特別に直書きしています。

エラー画面としては簡素なものでいいと思いますが、開発時はもっと詳細を見たくなるので今後これに付け足していく予定です。

初めに書いたように、トップページ以外にアクセスしてみて、今回用意したエラー画面が出たらOKということになります。


前回はただファイルを作り配置して、埋め込みの「Hello world!」を表示させただけでした。
これではデータの流れが掴めません。

まずは文字列データをビューで描画させることを考えていきます。

実はアクションコントローラにあるデータを、ビューが描画に使えるように渡す仕組みが用意されています。

アクションコントローラ - IndexController.php
<?php
require_once 'Zend/Controller/Action.php';

class IndexController extends Zend_Controller_Action
{
  public function indexAction() {

    $this->view->message = 'Hello world!';
  }
}


ビュースクリプト - index.phtml
<html>
<body>
  <?php echo $this->message;?>
</body>
</html>


このようにアクション側「$this->view->変数名」とスクリプト側「$this->変数名」が同じ名前空間になっています。
この仕組みを利用すればアクション側データを使って自由自在に描画できます。

これで一見解決したかに見えましたが、まだ「モデル」を使っていません!

前回書いたように「モデル」の仕事はデータの出し入れです。
そう考えると「Hello world!」だってデータです。これはモデルから取得するべきでしょう。
なのでアクションコントローラには、モデルに対してデータを要求する指示を書くべきです。


ではモデルのデータ出し入れクラスを書いてみます。

モデル - IndexData.php
<?php

class IndexData
{
  public function getMessage() {

    return 'Hello world!';
  }
}


アクションコントローラを書き変えてみます。

アクションコントローラ - IndexController.php
<?php
require_once 'Zend/Controller/Action.php';

class IndexController extends Zend_Controller_Action
{
  public function indexAction() {

    require_once dirname(__FILE__) . '/../models/IndexData.php';

    $obj = new IndexData;
    $this->view->message = $obj->getMessage();
  }
}


作成したモデルを呼ぶためにdirname(__FILE__)を使っています。定石の記述方法です。
PHP5.3.0からは__DIR__が使えますが、ツウはdirname(__FILE__)を使います。
モデルが増えたので一応ファイル配置を書いておきます。

ファイル配置
Zend/
application/
    models/
        IndexData.php
    views/
        scripts/
            index/
                index.phtml
    controllers/
        IndexController.php

htdocs/
    .htaccess
    index.php


アクションで最終的に「$this->view->message」にセットしているのでビュースクリプトは上記のものをそのまま使えます。
ベースURLにアクセスしてみて「Hello world!」が表示されればOKです。

単に「モデル」の分だけファイル数が増えた形になったように見えますが、もしデータの取得先がファイルになった場合はモデルのgetMessage()メソッドを修正すれば済むことになります。
データベースから取得するにしても同様にgetMessage()メソッド修正で済みます。

このことはアクションコントローラに「Hello world!」を直接書いた場合でも言えることですが、アクションコントローラにはいろんな機能のアクションが羅列することになります。なのでできるだけコンパクトに指示だけを書いてできるだけ修正が発生しないようにするのが理想だと思います。
データの出し入れを機能別にモデル分けすることにより、特定のデータ出し入れ方法に変更が発生した場合によりコンパクトな修正で済むというのが私の見解です。

ファイル数は増えたけど、それに余りある大規模システム構築MVCの礎が完成したと言えるんじゃないでしょうか。
とは言っても、まだ足りない部分がいくつかあります。

エラー処理、ビューでサニタイズ、日本語を扱う方法、自作クラス呼び出しの工夫など・・
それが済んだら、なぜIndexController.phpというファイル名で作成したのか? などの命名規則やいろいろなアクション呼び出しなどへ進んで行きたいと思います。