ZendFramework2ガイド

入門編

コントローラー

コントローラーの役割

MVCフレームワークでCにあたるコントローラーは、ユーザーリクエストに直結するクラスです。
ルーティングの章でも触れたとおり、URLとコントローラークラスは設定によって結びついています。URLとはWEB上のリソース(WEBページ等)を一意に指し示すものですが、その観点では、コントローラーとは一意に指し示されたWEBリソースそのものと捉えることができます。
MVCという構造におけるコントローラーの役割という見方をすると、Mに当たるモデルやVに当たるビューを文字通りコントロールするクラスとも言えます。

リクエストURLと結びついて、ルーティングによって呼び出されるクラスなわけなので、アプリ開発者にとってはリクエストに対する処理の起点ということになります。そして、コントローラーはモデルを使って処理を行い、その結果データをビューに渡してHTMLを出力する。これがコントローラークラスのやるべきことです。

コントローラーの作成

まずは実際にコントローラークラスを作ってみます。
スケルトンアプリケーションのモジュールをコピーして作ったモジュールの場合、既にIndexControllerが存在するはずなので、これをコピーして作ってみましょう。

今回はまず通販サイトとしての商品リスト画面でも作ると想定し、ListControllerを作成します。

module/Ec/src/Ec/Controller/ListController.php
<?php

namespace Ec\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class ListController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel();
    }	
}

コントローラーを追加したら、フレームワークにその事実を通知しなければなりません。
module.config.phpにcontrollersというキーの設定が存在するので、そこに記述します。

module/Ec/config/module.config.php
    'controllers' => array(
        'invokables' => array(
            'Ec\Controller\Index' => 'Ec\Controller\IndexController',
            'Ec\Controller\List' => 'Ec\Controller\ListController',    // ←追加
        ),
    ),

ルーティング設定が必要ですが、ルーティングの章で説明している通り、
デフォルトの設定のまま、Segmentの設定が生きていれば設定は不要になります。

次に、このコントローラーへアクセスされた場合のレスポンスとして出力するビューテンプレートが必要です。
ビューテンプレートはコントローラーと同名のディレクトリを作成し、その中にアクションと同名のテンプレートファイルを作成します。
これも、indexコントローラーのものをコピーして作りましょう。

module/
  ∟Ec/
    ∟view/
      ∟ec/  ←コントローラーの名前空間に相当
        ∟index/  ←コントローラーのクラス名に相当
          ∟index.phtml  ←コントローラーのアクションメソッド名に相当
        ∟list/
          ∟index.phtml

list/index.phtmlの中身は、今回はとりあえず、適当な文字列でも書いておきましょう。

modules/Ec/view/ec/list/index.phtml
これはリストです。

ブラウザから/ec/list/indexにアクセスしてみてください。
以下のように表示されればOKです。

ビューへのデータ引き渡し

ビューテンプレートは固定的なHTMLを記述していてはただの静的HTMLと変わらず、フレームワークを使っている意味もありません。
当然ながら出力結果は動的なものとなるので、ビューには変数を埋め込む必要があります。
そしてコントローラーからその変数に対してデータを割り当てるイメージです。

コントローラークラスを見てみてください。アクションメソッドの返り値としてViewModelというクラスのインスタンスが返却されています。
これがZend2のアクションのお決まりです。こうすることで、アクションメソッドに対応するビューテンプレートが出力されるようになっています。

で、テンプレートへのデータの渡し方ですが、このViewModelのコンストラクタ引数として配列を渡すことで可能になっています。

module/Ec/src/Ec/Controller/ListController.php
class ListController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel(array(
	    'data' => 'ほげほげ'
	));
    }	
}
modules/Ec/view/ec/list/index.phtml
これはリストです。<br />
コントローラーから「<?php echo $data; ?>」という文字列が渡されました。

ビューテンプレートも実際はただのPHPファイルです。
ビューテンプレート側では、渡された配列の内部のキーと同名の変数に値が格納されているので、そのままechoすることで表示されます。

リクエストパラメーター

GET、POST

PHPでは$_GETや$_POSTなどのスーパーグローバル変数によってリクエストパラメーターが取得できます。
しかし、今のトレンドとしてこれらを直接参照することはしません。
特にフレームワークではほとんどが間接的にこれらを参照できる仕組みが用意されています。

Zend2ではコントローラークラスで以下のようにすることで取得できます。

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        // GET
        $getValue = $this->params()->fromQuery('test_param');
        // POST
        $postValue = $this->params()->fromPost('test_param'');
		
        return new ViewModel();
    }
}

試しにアクションメソッドに以下のように記述してブラウザから、
/ec/?test_param=hoge
アクセスしてみてください。

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $getValue = $this->params()->fromQuery('test_param');
        var_dump($getValue);
		
        return new ViewModel();
    }
}

string(3) "hoge"

と出力されるはずです。
フォームなどから送信されてきたPOSTパラメーターについても同様にfromPostで取得できます。

ルーティングパラメーター

Zend2ではmodule.config.phpによってルーティングを設定しますが、Segmentによる設定で、パスの特定のセグメントにキーを割り当て、コントローラーでそのキー指定で対応するセグメント部分をパラメーターとして取得することが出来ます。

まず、module.config.phpでルーティング設定をします。
以下の例はリクエストURLが/list/indexで始まる場合、第3セグメント部分を"page"というキーで取得できるようにする設定です。

module/Ec/config/module.config.php
return array(
    'router' => array(
        'routes' => array(

~~~(中略)~~~

            // この設定を追加
            'hoge' => array(
                'type' => 'Segment',
                'options' => array(
                    'route'    => '/list/index[/:page]',
                    'defaults' => array(
                        'controller' => 'Ec\Controller\List',
                        'action'     => 'index',
                    ),
                ),
            ),

ListControllerのindexActionメソッドに以下のように記述します。

class ListController extends AbstractActionController
{
    public function indexAction()
    {
        $page = $this->params()->fromRoute('page');
        var_dump($page);
		
        return new ViewModel();
    }	
}

そしてブラウザから

/list/index/2

へアクセスしてみてください。

string(1) "2" 

このように、URLのパスの特定セグメントをリクエストパラメーターとして利用することが出来ます。
これは例えば本来であれば、

/list/index/?page=2

というようなGETパラメーターでやりそうなところを代用するような感じに利用できます。

/list/index/2

のほうがURLとしてはスマートですし、キーがURLに現れていないぶん、ユーザーからプログラムの作りを想像されにくいように隠ぺいする意味もあります。

セッションの利用

セッションの利用も直接session_startしたり、$_SESSIONを直接参照したりはしません。

セッションはZend\Session\Containerというクラスを利用します。

$session = new Container('namespace');
$session->hoge= 'fuga';

例えば、あるGETパラメーターがリクエストされてきたらその値をセッションに格納するようなコードは以下のようになります。

use Zend\Session\Container;

class ListController extends AbstractActionController
{
    public function indexAction()
    {
        $session = new Container('list');
        $page = 1;
        if ($this->params()->fromRoute('page') != null) {
            $page = $this->params()->fromRoute('page');
            // セッションへ格納
            $session->page = $page;
        } elseif ($session->page != null) {
            // セッションから取得
            $page = $session->page;
        }
		
        return new ViewModel(array(
            'page' => $page,
        ));
    }	
}

Zend\Session\Containerクラスのコンスタラクタのパラメーターはセッションのネームスペースです。
異なるネームスペースでnewされたContainerのインスタンス同士は、同じキーでも異なる値が保持できます。
ここで言うネームスペースというのはZend2のセッションでの独自の概念です。
実際のところは単純なしくみで、結局は以下の様なことです。

// 下記2つのパターンはほぼ同じ意味

// Zend\Session\Containerを利用した場合
$session = new Container('namespace');
$session->hoge= 'fuga';

// スーパーグローバル変数を使用した場合
session_start();
$_SESSION['namespace']['hoge'] = 'fuga';

コントローラーの階層化

コントローラーはmodule/{モジュール名}/src/{名前空間}/Controller/の下にクラスファイルを配置しますが、規模の大きなアプリケーションなど、Controllerディレクトリ以下を階層化したい場合があります。Zend1では基本的に階層化は出来ませんでしたが、Zend2の場合、これは非常に簡単に実現できます。

Zend2ではクラスファイルの所属する名前空間は、ディレクトリ階層と一致するようになっています。
つまりsrcディレクトリ以下、src/Ec/Controller/IndexController.phpの場合、名前空間はEc\Controllerになり、src以下のディレクトリ階層と一致しているのがわかります。

そのため例えばControllerディレクトリの下にサブディレクトリを作成し、管理者用のメンテナンス機能等のコントローラーをまとめたい場合、以下のようにします。

module/Ec/src/Ec/Controller/Admin/IndexController.php
namespace Ec\Controller\Admin;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {

    }	
}

このように、ディレクトリを深くしたらコントローラークラスの名前空間も同様に深くする。これだけです。
なので階層はいくらでも深くすることが出来ます。