ZendFramework2ガイド

機能編

ルーティング

ルーティングのコンフィグの構造

リクエストURLと、それに対して呼び出すコントローラークラスのアクションメソッドを決定することをルーティングと呼びます。
このルーティングはmodule.config.phpに記載します。
module.config.phpのルーティング設定部分は以下の様な構造になっています。

module/Hoge/config/module.config.php
    // ルーティング設定
    'router' => array(
        'routes' => array(
            'home' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/',
                    'defaults' => array(
                        'controller' => 'Hoge\Controller\Index',
                        'action'     => 'index',
                    ),
                ),
            ),
            'hoge' => array(
                'type' => ・・・

            ),
        ),
    ),

~~~(中略)~~~

    // コントローラーリスト
    'controllers' => array(
        'invokables' => array(
            'Hoge\Controller\Index' => 'Hoge\Controller\IndexController',
            'Hoge\Controller\Fuga' => 'Hoge\Controller\FugaController',
        ),
    ),

'router'以下に'routes'があり、その下に復数のルーティング設定が可能です。上の例で言うと、homeとhogeの2つが定義されています。このhomeとかhogeとかいうのは特に意味を持たず、ルーティング設定に名前をつけている程度に考えて問題ありません。重複しなければなんでもOKです。内容を表すようなわかりやすい名前がいいでしょう。

'home'の中身を見てみると、まず'type'と'options'があります。先にoptionsを見てみましょう。中には'route'と'defaults'というのがあります。この、routeというのが、リクエストURLのパスがこれにマッチした場合に、そこに記述されたアクションを呼び出しますよ、という意味になります。

'type'には、'route'に対するマッチング方法を指定します。ここに指定するのはZend\Mvc\Router\Http\RouteInterfaceというインターフェイスを実装したクラスです。例ではZend\Mvc\Router\Http\Literalと指定されていますが、これもRouteInterfaceを実装したクラスです。Zend2として用意されているのはLiteralの他にもたくさんあり、Zend2のフレームワークディレクトリのZend\Mvc\Router\Http内にあります。
RouteInterfaceを実装した自作のRouteクラスも利用可能です。

’defaults'は、'route'にマッチした場合に呼び出すコントローラークラスとアクションメソッドを指定します。
'controller'には名前空間を含むコントローラー名を指定しますが、注意が必要なのは、実際のコントローラークラスのクラス定義としての名前空間とクラス名を指定するというわけではないということです。どういうことかというと、ここで指定するのは、module.config.php内にあるコントローラーリストのキーです。なのでここで指定するコントローラー名はこのコントローラーリストに定義されていることが前提になります。

例では名前空間も含めたフルネームで指定されていますが、Zend\Mvc\Router\Http名前空間内にあらかじめ用意されているクラスであれば、'Literal'など、名前空間を含めないクラス名での指定も可能です。つまり、

   'router' => array(
        'routes' => array(
            'home' => array(
                'type' => 'Literal',
                'options' => array(

というふうに書いてもちゃんと機能します。逆に、もし自作のRouteクラスを作った場合は名前空間も含めたフルネームでの指定が必要ということになります。

というふうに単に'Literal'と指定してもちゃんと機能します。逆に、もし自作のRouteクラスを作ってそれを指定した場合は名前空間も含めたフルパスでの指定が必要になります。

あらかじめ用意されたRouteクラスをいくつか紹介します。

Literal

Literalは最も単純なルーティングタイプです。
リクエストURLのパスと、'route'に指定したパスが完全一致する場合のみマッチと判定されます。

            'hoge' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/',
                    'defaults' => array(
                        'controller' => 'Hoge\Controller\Index',
                        'action'     => 'index',
                    ),
                ),
            ),

route

routeには、完全一致させたいリクエストURLのパスを指定します。
例えば
http://ドメイン/hoge/huga
というURLへのアクセスに対してマッチさせたい場合は'/hoge/fuga'と指定することになります。

defaults

defaultsには、呼び出すコントローラークラス名と、そのコントローラーに存在するアクション名を指定します。
'controller'に、名前空間を含むコントローラークラスのフルネームを指定します。
または、'__NAMESPACE__'というキーに名前空間のみを指定し、'controller'には名前空間を含まないクラス名を指定しても同じです。
コントローラー名の指定についての注意点については冒頭の「ルーティングのコンフィグの構造」のところを見てください。

defaultsの2つのパターンの指定方法
                    'defaults' => array(
                        'controller' => 'Hoge\Controller\Index',
                        'action'     => 'index',
                    ),

                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller' => 'Index',
                        'action'     => 'index',
                    ),

ちなみに、controllerやactionに指定するのはクラス名やメソッド名の接尾文字を含めません。
コントローラークラスは'HogeController'のように、'Controller'がつきますが、ルーティング設定には付けません。
同じくアクションメソッド名も、'indexAction'のように、'Action'がつきますが、これも不要です。

Segment

URLのパスの"/"で区切られた各部をセグメントと呼びます。SegmentはこのURLのパスセグメントがそのままルーティング先のコントローラー・アクションを表すようなルーティングをしたい場合の設定です。

Segmentを利用すると、考えられる全てのリクエストURLを想定して、全ての組合せの設定をいちいちLiteralなどで設定を記述していく必要がなく、楽になるというのが最大のメリットです。

ただし、URLとアクションが直結してしまい、結合度という観点では密になってしまうというデメリットもあります。どういうことかというと、クラス名やメソッド名が簡単には変更できなくなるということです。例えば公開してそれなりにアクセスがあるサイトの場合、どのページが直接リンクされていたりするかわかりませんし、多くの人がブックマークなどしていることでしょう。そんな状態でもし何らかの理由でクラス名やメソッド名を整理したい、となった場合、それはイコールURLの変更を意味します。これはプログラムの作りとしては変更に弱いと言わざるを得ません。
いちいちLiteralなどで設定した場合には、そのアクションへルーティングしている設定部分を書き換えるだけで、URLは変わることなく、クラス名やメソッド名を変更することが出来ます。
もっと言えば、Segmentの場合はコントローラーの設計がそのままURLに表れてしまっているとも言えます。それが何が悪いのだという方には問題ないでしょうが、気にする方にとっては気持ち悪い、となるところでしょう。

そのようなことを踏まえ、Segmentをどの程度取り入れた設定をするのか、考える必要はあります。楽なのでどうしてもこの設定になりがちですが。

最も単純な設定

下に示すのはSegment設定において必要最低限に絞った最も単純な例です。

            'segment_sample' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/:controller/:action',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                    ),
                ),
            ),

routeの設定を見ると、第1セグメントの部分に":controller"と記述されています。これは、第1セグメントの文字列をそのままコントローラー名として利用する、ということになります。そして第2セグメントの部分には:actionと記述されているので、これは第2セグメントの文字列ををそのままアクション名として利用するという設定です。
ただ、コントローラー名とアクション名はリクエストURLから決定されますが、そのコントローラーの属する名前空間は、リクエストURLからの判断は不可能です。そのため、defaultsでcontrollerやactionの指定は不要ですが名前空間の指定は必要になるわけです。

上のような設定の場合、リクエストURLとルーティング先のアクションの関係は、

/fuga/piyo  →  Hoge\Controller\FugaController::piyoAction

ということになります。

:controllerや:actionのデフォルトを設定してrouteに柔軟性を持たせる

上の最も単純な例では、第1セグメントと第2セグメントがそのままコントローラー、アクションを表すことになりますが、マッチするURLはパスセグメントが2個に限られています。そのため、/hugaなどにはマッチしません。アクション名が判断できず、ルーティング出来ないからです。
そこで以下の様な設定をすることでもう少し柔軟性を持たせることが出来ます。

            'segment_sample' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/[:controller[/:action]]',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Ec\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
            ),

違いは、routeの設定として[]で囲われている部分が存在するということです。[]で囲われている部分はリクエストURLに含まれていてもいなくてもマッチする、という意味になります。
上の例では[]が2重になっています。この場合、例えば、

  /

にもヒットするし、

  /hoge

にもヒットするし、

  /hoge/fuga

にもヒットするということになります。
そうすると、リクエストURLからは判断できない部分が出てくることになります。例えば"/hoge"だったらアクション名が特定できないし、"/"だったらコントローラー名すら特定できません。そのため、[]で":controller"や":action"を囲う場合はdefaultsで"controller"や"action"を指定することが必要になってきます。この場合、文字通りデフォルトの設定であるため、ここで指定したものはURLパスセグメントで判断できない場合にのみ適用される設定となります。
ちなみにコントローラー名の指定についての注意点については冒頭の「ルーティングのコンフィグの構造」のところを見てください。

少し余談になりますが、'/[:controller[/:action]]'という設定の場合、

  /hoge/fuga

にはヒットしますが、

  /hoge/fuga/

にはヒットしません。なぜなら":action"の後ろに"/"がついていないからです。なので'/[:controller[/:action]/]'とするとヒットすることになりますが、今度は逆に/hoge/fugaにはヒットしなくなります。そこで[]で囲われている部分はあってもなくてもよい、というルールから、'/[:controller[/:action][/]]'とすればどちらでもヒットするようになります。

上位セグメントの固定化、:controllerと:actionの位置の自由

routeの設定には":controller"や":action"以外にも固定値を含めることもできるし、":controller"や":action"の位置は自由なので逆転することももちろん可能です。

            'segment_sample' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/hoge[/:action[/:controller[/]]]',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
            ),

この例の場合、まずURLのパスが/hogeで始まっていないとマッチしません。つまり/hogeで始まる場合のみ、Segmentの設定を生かすというような設定が出来るわけです。
そして":controller"と":actoin"が逆転しているため、例えばリクエストURLとルーティング先のアクションは以下の様な関係になります。

  /hoge/fuga/piyo  →  Hoge\Controller\PiyoController::fugaAction

更にrouteの設定には[/]が含まれているため、

  /hoge/fuga/piyo/  →  Hoge\Controller\PiyoController::fugaAction

というルーティングも成り立ちます。

Regex

文字通り、正規表現によってマッチしたリクエストURLに対するルーティング設定です。

            'regex_sample' => array(
                'type'    => 'Regex',
                'options' => array(
                    'regex'    => '/hoge/fuga/(.+)',
                    'spec'    => '/hoge/fuga/index',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller'    => 'Fuga',
                        'action'        => 'index',
                    ),
                ),
            ),

regex

optionsのregexにはリクエストURLに対してマッチさせたい正規表現を設定します。上の例の場合、/hoge/fuga/以降の部分にどんな文字列が来ようがマッチさせるという設定です。そしてマッチした場合のルーティング先をdefaultsに設定します。

spec

specは省略することは出来ませんがルーティングには一切関係ありません。
これは例えばコントローラークラス内で、$this->url()->fromRoute()などでアクセスされたURLを取得した場合に帰ってくる値がこれになります。なので例えば/hoge/fuga/indexの方が本体の処理で、/hoge/fuga以下がなんであろうが全て/hoge/fuga/indexへリダイレクトさせるという処理にすることなどが可能と言えます。

regexでマッチした場合のワイルドカードマッチの部分をパラメータとして取得する

上の設定の場合、正規表現でマッチさせることが出来るとは言え、ワイルドカード部分はなんであろうが関係なく、その部分の値によって処理を切り分けるなどが出来ません。そこでワイルドカード部分をパラメータとして取得できるような記法があります。

            'regex_sample' => array(
                'type'    => 'Regex',
                'options' => array(
                    'regex'    => '/hoge/list/page_(?<page>[0-9]+)',
                    'spec'    => '/hoge/list/%page%',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller'    => 'List',
                        'action'        => 'index',
                    ),
                ),
            ),

ここで想定しているURLは、/hoge/list/page_2などのような形式です。一覧表示画面をページ切り替え出来るようなインターフェイスで、"page_"に続く部分に数字が来ることを期待しています。この場合、実際にマッチさせるだけの正規表現であれば、

  'regex' => '/hoge/list/page_[0-9]+’

でよい所ですが、

  'regex' => '/hoge/list/page_(?<page>[0-9]+)’

というように、'(?<page>  )'が余計についています。
これは、パラメータとして取得したい部分を(?<page>  )で括ることで、<>で囲まれた部分をパラメーター名として取得できるようになります。
こうすると、コントローラークラスで

  $this->param()->fromRoute('page')

または

  $this->getRequest()->getRoute('page')

で取得することが可能です。

specについては$this->url()->fromRoute()で取得できるのは先述のとおりです。今回の例では"%page%"という記述がありますが、この部分がマッチしたパラメータで自動的に置き換えられるわけではありません。置き換えた状態で取得するには、

  $this->url()->fromRoute(null, array('page' => $this->param()->fromRoute('page')))

または

  $this->url()->fromRoute(null, $this->param()->fromRoute()))

という具合にします。

階層的な設定

Literal、Segment、Regexなど個別のルーティング設定は組合せて階層構造の設定にすることが出来ます。
まずは例を見てみてください。

            'tree_sample' => array(
                'type' => 'Literal',
                'options' => array(
                    'route'    => '/hoge',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller' => 'Index',
                        'action'     => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'child1' => array(
                        'type'    => 'Literal',
                        'options' => array(
                            'route'    => '/fuga',
                                'defaults' => array(
				    'controller'  => 'fuga',
                                    'action'     => 'index',
                                ),
                        ),
                    ),
                ),
            ),

Literalのルーティング設定を2段階にした設定です。
まず、前半の部分についてはごく単純な設定で、単に、

/hoge  →  Hoge\Controller\IndexController::indexAction

というだけのものです。
違うのはそれ以降です。

child_routes

child_routesという設定がありますが、この下には、ルーティング設定をまるごともう一つ収めることが出来ます。
この場合、

  /hoge  →  Hoge\Controller\IndexController::indexAction

という設定のchild_routesに、

  /fuga →  Hoge\Controller\FugaController::indexAction

という設定があるように見えます。この場合、どういうことかというと、組合せて実際には

  /hoge/fuga →  Hoge\Controller\FugaController::indexAction

という設定を単独でしているのと同じ意味になります。

may_terminate

may_terminateという設定がchild_routesのすぐ上にあります。
これは、child_routesの設定なしで、単独のルーティング設定として機能させることを許可するか否かの設定です。trueに設定すると、例えば上の例であれば、

/hoge  →  Hoge\Controller\IndexController::indexAction
/hoge/fuga →  Hoge\Controller\FugaController::indexAction

という2つのルーティングが有効になります。

逆にmay_terminateがfalseの場合はどうなるかというと、child_routesの設定ありきになります。なので前者の/hogeのみでのルーティングは無効になります。リクエストされたURLが/hogeの場合には404 NOT FOUNDになります。
なので上の例の場合で、may_terminateをfalseに設定したなら、

            'tree_sample' => array(
                'type' => 'Literal',
                'options' => array(
                    'route'    => '/hoge/fuga',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller' => 'Fuga',
                        'action'     => 'index',
                    ),
                ),
            ),

という単純な設定と同じ意味になってしまいます。

種類の違うルーティング設定を組み合わせる

Literalの下にSegmentなどの組合せも可能です。

            'tree_sample' => array(
                'type' => 'Literal',
                'options' => array(
                    'route'    => '/hoge',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Hoge\Controller',
                        'controller' => 'Index',
                        'action'     => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'child1' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '/[:controller[/:action[/]]]',
                                'defaults' => array(
                            ),
                        ),
                    ),
                ),
            ),

この例はLiteralのchild_routesにSegmentを組み合わせたものです。実はスケルトンアプリケーションのデフォルトのmodule.config.phpはこれに近い状態になっています。

上の例では、まず第1セグメントがhogeであることが前提の設定ということになります。名前空間とコントローラー名、アクション名のデフォルトも設定されています。
そしてchild_routesで第2セグメントはコントローラー名、第3セグメントはアクション名という設定です。名前空間やコントローラー名、アクション名のデフォルトは親のものを引き継ぐため設定不要です。
may_terminateはtrueなので/hogeへのアクセスも可能でIndecController::indexActionへルーティングされます。

child_routes内のchild数や階層の深さは自由

child_routesの直下には復数のルーティング設定を並べることができ、またchild_routesの下の各ルーティング設定にもまたchild_routesを・・ツリー構造的な設定が可能になります。上でも説明したように、LiteralやSegment等、異なる種類の設定も組み合わせる事が出来るため、ひたすら並列に設定を並べるより効率的に設定ができます。あまりやり過ぎると逆に複雑になり過ぎて収拾がつかないようなことになる可能性がありますし、可読性も低下するかもしれないので、注意は必要です。