第9章 - リンクとルーティングシステム

リンクと URL は、Web アプリケーションのフレームワークにおいて特別な扱いをする価値があります。アプリケーションで単一のエントリーポイント (フロントコントローラー) とヘルパーを利用することで、URL の処理と URL の生成を完全に分離できるようになります。この機能はルーティングと呼ばれます。ルーティングは単なる道具ではなく、アプリケーションをよりユーザーフレンドリーでセキュアにするために必要なツールです。この章では、次のような、symfony アプリケーションで URL を処理方法について、詳しく説明します。

  • ルーティングシステムとは何か、またどのように動作するのか
  • 外部 URL のルーティングを有効にするためにテンプレートでリンクヘルパーを使う方法
  • URL の表示方法を変更するためにルーティングルールを変更する方法

ルーティングのパフォーマンスと最後の仕上げを習得するために、いくつかのトリックも紹介します。

ルーティングとは何か?

ルーティング (routing) とは URL をユーザーフレンドリーに書き換えるメカニズムです。しかし、なぜこれが重要なのかを理解するには、URL について数分考えなければなりません。

サーバーへの命令としてのURL

URL はユーザーが望むアクションを成立させるためにブラウザーからの情報をサーバーに運びます。たとえば、つぎの例のように、伝統的な URL はリクエストを完了させるために必要なスクリプトへのファイルパスとパラメーターを含みます: http://www.example.com/web/controller/article.php?id=123456&format_code=6532

この URL はアプリケーションのアーキテクチャとデータベースに関する情報を運びます。通常、開発者はインターフェイス内のアプリケーションのインフラを隠します(たとえば、開発者は "QZ7.65" よりも "個人のプロファイルページ" のようなページタイトルを選びます)。アプリケーション内部への重要な手がかりをURLに露出することはこの努力に相反することで重大な欠陥を晒すことになります。

  • URL に表示される技術上のデータは潜在的なセキュリティの欠点を作ります。前の例において、悪意のあるユーザーが id パラメーターの値を変更したら何が起こるでしょうか?アプリケーションがデータベースへの直接のインターフェイスを提供することは何を意味するのでしょうか?もしくはユーザーが面白半分にほかのスクリプト名、たとえば admin.php を試したらどうなるでしょうか?一般的に、生の URL を使うとアプリケーションを簡単に不正利用する手段を提供することになるので、これらの方法によってセキュリティの管理がほとんど不可能になります。
  • 不明瞭な URL を使うと、どこに表示されようともうっとおしく、周囲の内容の印象が希薄になります。そして今日において、URL はアドレバーだけに表示されません。検索結果と同様に、これらはユーザーがリンクの上にマウスをホーバーしたときにも表示されます。ユーザーが情報を探すとき、図9-1のようなややこしいURLよりも、ユーザーが探しものを簡単に見つけられるようにする手がかりを与えたいと開発者は願うことでしょう。

図9-1 - URL は検索結果などの多くの場所で表示される

URL は検索結果などの多くの場所で表示される

  • URL の1つを変更しなければならない場合 (たとえば、スクリプト名もしくはパラメーターの1つが修正される場合)、この URL へのすべてのリンクも同様に変更しなければなりません。コントローラー構造の修正作業は重量級で高くつくので、アジャイル開発において理想的ではありません。

symfony がフロントコントローラーのパラダイムを利用しない場合、事態はもっと悪化する可能性があります; すなわち、つぎのように、多くのディレクトリのなかで、インターネットからアプリケーションにアクセスできるスクリプトがたくさん含まれている場合です:

http://www.example.com/web/gallery/album.php?name=my%20holidays
http://www.example.com/web/weblog/public/post/list.php
http://www.example.com/web/general/content/page.php?name=about%20us

この場合、開発者は URL の構造をファイル構造とマッチさせる必要があり、どちらかの構造を変更したときに、結果は悪夢のようなメンテナンス作業になります。

インターフェイスの一部としての URL

ルーティングの背後にあるアイディアは URL をインターフェイスの一部としてみなすことです。アプリケーションはユーザーに情報をもたらすためにURLを整形し、ユーザーはアプリケーションのリソースにアクセスするためにURLを利用します。

これは symfony のアプリケーションで実現可能です。エンドユーザーに表示される URL はリクエストを実行するために必要なサーバーへの命令とは無関係だからです。代わりに、リクエストされるリソースに URLを関連づけして、自由に形式を整えることができます。たとえば、symfony はつぎの URL を理解しこの章の最初の URL のように同じページを表示できます:

http://www.example.com/articles/finance/2010/activity-breakdown.html

恩恵は計り知れません:

  • URL は実際に何かを意味するので、これはユーザーがリンクの背後にあるページのなかに望むものが含まれるかどうかを判断するための助けになります。リンクは返すリソースの詳細内容を追加できます。これはとりわけ検索エンジンの結果に対して役立ちます。加えて、URL は時にページタイトルの参照なしに表示されるので (URL をメールのメッセージにコピーするときを考えてください)、この場合、その URL はそれ自身が何らかの意味を持たなければなりません。ユーザーフレンドリーな URL の例に関しては図9-2をご覧ください。
  • 技術的な実装はユーザーからは見えません。ユーザーにはどのスクリプトが使われているのか分からず、id のようなパラメーターを推測することはできません。アプリケーションの、潜在的なセキュリティリスクに対しての脆弱度は低下します。さらに、ユーザー体験を全く変更せずに、URL の裏側の処理を完全に変更してしまうこともできます。ユーザーは、404 エラーやリダイレクトを目にすることはありません。

図9-2 - 刊行日のように、URL はページに関する追加情報を運ぶ

刊行日のように、URL はページに関する追加情報を運ぶ

  • 紙のドキュメントに書かれている URL は入力しやすく覚えやすいものにすべきです。あなたの名刺に会社の Web サイトがhttp://www.example.com/controller/web/index.jsp?id=ERD4と記載されていたら、多くの訪問者を得ることはないでしょう。
  • URLは、 直感的な方法でアクションを実行するもしくは情報を読みとるために、独自のコマンドラインになることができます。このような機能を提供するアプリケーションによってパワーユーザーは作業をより速くできるようになります。

    // 結果のリスト: 結果のリストを絞るために新しいタグを追加する
    
    // ユーザープロファイルのページ: 別のユーザープロファイルを取得するために名前を変更する
    http://www.askeet.com/user/francois
    
  • URLの整形方法とアクションの名前/パラメーターを、それぞれ個別に修正することで、変更できます。全体的に、アプリケーションをゴチャゴチャにすることなく、最初に開発をしたあとで URL の形式を整えることができます。

  • アプリケーションの内部を再編成するときでも、URLは外部の世界に対して同じ状態を保つことができます。動的なページをブックマークできるようになるので、URLの永続性は必須です。
  • 検索エンジンは Web サイトをインデックスに登録するとき、(.php.asp などで終わる) 動的なページを無視しがちです。検索エンジンが動的なページに遭遇したときでも、静的な内容をブラウジングしていると考えさせるために URL の形式を整えることができます。結果としてインデックスに登録されるアプリケーションのページの内容がよくなります。
  • より安全です。承認されていない URL を閲覧しようとすると開発者が指定したページにリダイレクトされるので、ユーザーが試しに URL を入力しても Webroot のファイル構造を閲覧することはできません。リクエストによって呼び出された実際のスクリプト名は、そのパラメーターと同様に、隠匿されています。

ユーザーと実際のスクリプト名とリクエストパラメーターに表示される URL 間の対応は設定を通して修正できるパターンに基づいた、ルーティングシステムによって実現されます。

アセット (asset) はどうでしょうか?幸運にも、URL のアセット(画像、スタイルシートと JavaScript) はブラウジングの間は大量に表示されないので、これらに対してルーティングを実際に設定する必要はありません。symfony において、すべてのアセットは web/ ディレクトリに設置され、URL はファイルシステムの設置場所に一致します。しかしながら、アセットヘルパー内部で生成されたURLを利用することで (アクションによって処理された) 動的なアセットを管理できます。たとえば、動的に生成された画像を表示するには、image_tag('captcha/image?key='.$key) ヘルパーを使います。

どのように動作するのか

symfony は外部 URL と内部 URI を切り離します。これら2つを対応させる作業はルーティングシステムによって行われます。わかりやすくするために、symfony は通常の URL の構文とよく似た内部 URI のための構文を使います。リスト9-1は例を示しています。

リスト9-1 - 外部 URL と内部 URI

// 内部の URI 構文
<module>/<action>[?param1=value1][&param2=value2][&param3=value3]...

// 内部 URI の例で、エンドユーザーに決して表示されない
article/permalink?year=2010&subject=finance&title=activity-breakdown

// 外部 URL の例で、エンドユーザーに表示される
http://www.example.com/articles/finance/2010/activity-breakdown.html

ルーティングシステムを定義するには routing.ymlという名前の特別な設定ファイルを使います。リスト9-2で示されているルールを考えます。このルールは articles/*/*/* のようなパターンを定義し、ワイルドカードとマッチする内容の一部を命名します。

リスト9-2 - サンプルのルーティングルール

article_by_title:
  url:    articles/:subject/:year/:title.html
  param:  { module: article, action: permalink }

symfony のアプリケーションに送信されるすべてのリクエストは最初ルーティングシステムによって分析されます (単独のフロントコントローラーによって処理されるのでシンプルです)。ルーティングシステムはリクエストされた URL とマッチするルーティングルールで定義されたパターンを探します。マッチするパターンが見つかった場合、名前つきのワイルドカードはリクエストパラメーターになり param: キーで定義されたものと統合されます。どのように動くのかはリスト9-3をご覧ください。

リスト9-3 - ルーティングシステムはやってくるリクエスト URL を解釈する

// ユーザーがつぎの外部 URL を入力する(クリックする)
http://www.example.com/articles/finance/2010/activity-breakdown.html

// フロントコントローラーはリクエスト URL が article_by_title ルールにマッチするか調べる
// ルーティングシステムはつぎのリクエストパラメーターを作成する
  'module'  => 'article'
  'action'  => 'permalink'
  'subject' => 'finance'
  'year'    => '2010'
  'title'   => 'activity-breakdown'

リクエストは article モジュールの permalinkアクションに渡されます。アクションは表示する記事を決定するためにリクエストパラメーターで求められたすべての情報を格納します。

しかしながらメカニズムはまったく逆のことも行わなければなりません。リンクに外部 URL を表示するアプリケーションに対して、どのルールを適用するのか決定するには十分なデータを持つルーティングルールを提供しなければなりません。ルーティングを完全に無視する <a> タグで直接ハイパーリンクを書いてはなりません。代わりに、リスト9-4で示されるように特別なヘルパーを使います。

リスト9-4 - ルーティングシステムはテンプレート内部 URL の出力形式を整える

// url_for() ヘルパーは内部 URI を外部 URL に変換する
<a href="<?php echo url_for('article/permalink?subject=finance&year=2010&title=activity-breakdown') ?>">ここをクリック</a>
 
// ヘルパーは URI が article_by_title ルールにマッチすることを見る
// ルーティングシステムはそれから外部 URL を作成する
 => <a href="http://www.example.com/articles/finance/2010/activity-breakdown.html">ここをクリック</a>
 
// link_to() ヘルパーは直接ハイパーリンクを出力し
// PHP と HTML を混在させることを回避する
<?php echo link_to(
  'ここをクリック',
  'article/permalink?subject=finance&year=2010&title=activity-breakdown') ?>
) ?>
 
// 内部では、link_to() は url_for() を呼び出すので結果はつぎのものと同じ
=> <a href="http://www.example.com/articles/finance/2010/activity-breakdown.html">ここをクリック</a>
 

ルーティングは2通りのメカニズムであり、すべてのリンクの形式を整えるlink_to()ヘルパーを使う場合のみに機能します。

URL を書き換える

ルーティングシステムに深く関わるまえに、1つのことをあきらかにする必要があります。以前のセクションで示された例において、内部 URI のフロントコントローラー (index.php もしくは frontend_dev.php) の説明をしていません。フロントコントローラーは、アプリケーションの要素ではありませんが、環境を決定します。ですのですべてのリンクは環境に依存しなければならず、フロントコントローラー名は内部 URL に決して現れることはありません。

生成された URL の例にはスクリプト名が存在しません。デフォルトの運用環境では生成URLはスクリプト名を含まないからです。settings.yml ファイルの no_script_name パラメーターは生成URLのなかでフロントコントローラー名の表示を正確にコントロールします。リスト9-5で示されるように、このパラメーターを false に設定すれば、リンクヘルパーによる URL の出力はすべてのリンクにフロントコントローラーの名前を記載します。

リスト9-5 - URL 内部でフロントコントローラーの名前を表示する (apps/frontend/settings.yml)

prod:
  .settings
    no_script_name:  false

生成 URL はつぎのように示されます:

http://www.example.com/index.php/articles/finance/2010/activity-breakdown.html

運用環境を除いたすべての環境において、no_script_name パラメーターはデフォルトでは falseに設定されます。たとえば、開発環境のアプリケーションをブラウザーで見るとき、フロントコントローラー名はつねに URL に表示されます。

http://www.example.com/frontend_dev.php/articles/finance/2010/activity-breakdown.html

運用環境において、no_script_name パラメーターは on に設定されるので、URL はルーティング情報だけを示し、よりユーザーフレンドリーです。技術的な情報は現れません。

http://www.example.com/articles/finance/2010/activity-breakdown.html

しかしながら、アプリケーションはどのフロントコントローラーが呼び出されるのかをどのように知るのでしょうか?それは URL の書き換えが行われる場所です。URL のなかに何も存在しないときに、Web サーバーが任意のスクリプトを呼び出すために設定できます。

Apache において、これはいったん mod_rewrite 拡張機能を有効にすれば可能です。symfonyのすべてのプロジェクトは .htaccess ファイルを備えており、このファイルは web ディレクトリのために mod_rewrite 設定をサーバーの設定に追加します。このファイルのデフォルトの内容はリスト9-6で示されています。

リスト9-6 - Apache のためのデフォルトの書き換えルール (myproject/web/.htaccess)

<IfModule mod_rewrite.c>
  RewriteEngine On

  # no_script_nameがうまく動作しない場合は、以下の行のコメントを
  # 解除する
  #RewriteBase /

  # 拡張子 .something をもつすべてのファイルをスキップする
  #RewriteCond %{REQUEST_URI} \..+$
  #RewriteCond %{REQUEST_URI} !\.html$
  #RewriteRule .* - [L]

  # .html バージョンがここ (キャッシュ) であるか確認する
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f

  # いいえ、Web のフロントコントローラーにリダイレクトする
  RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

Web サーバーは受けとった URL の形式を検査します。URL がサフィックスを含まず(デフォルトでコメントアウト)、かつ利用可能なページのキャッシュバージョンが存在しない場合、リクエストは index.php に渡されます (12章でキャッシュを説明します)。

しかしながら、symfony プロジェクトの web/ ディレクトリはプロジェクトのすべてのアプリケーションと環境のあいだで共有されます。これは通常の場合 web ディレクトリにあるフロントコントローラーが複数存在することを意味します。たとえば、frontend アプリケーションと backend アプリケーション、と dev 環境と prod 環境を持つプロジェクトは4つのフロントコントローラーのスクリプトを web/ ディレクトリに含みます:

index.php         // prodのfrontend
frontend_dev.php  // devのfrontend
backend.php       // prodのbackend
backend_dev.php   // devのbackend

mod_rewrite の設定はデフォルトのスクリプトの名前だけを指定します。すべてのアプリケーションと環境に対して no_script_nametrue にセットする場合、すべての URL は prod 環境の frontend アプリケーションへのリクエストとして解釈されます。これが任意のプロジェクトに対して URL の書き換えを利用できる1つの環境を持つアプリケーションを1つだけ持つことができる理由です。

スクリプトの名前を持たないアプリケーションを複数持つ方法が1つあります。Webroot でサブディレクトリを作り、フロントコントローラーをこれらの内部に移動させます。ProjectConfiguration ファイルへのパスを変更し、それぞれのアプリケーションに対して必要な .htaccess ファイルの URL 書き換え設定を作ります。

リンクヘルパー

ルーティングシステムに対して、テンプレート内では通常の <a> タグの代わりにリンクヘルパーを使うべきです。これをやっかいな問題と見なさず、むしろ、アプリケーションをきれいな状態に保ち維持しやすくするための機会として見てください。加えて、リンクヘルパーはとても便利で見逃せないショートカットをいくつか提供します。

ハイパーリンク、ボタン、フォーム

link_to() ヘルパーはすでにご存じのとおりです。このヘルパーは XHTML 準拠のハイパーリンクを出力し、2つのパラメーター: クリック可能な要素とそれが指し示すリソースの内部 URI を必要とします。ハイパーリンクの代わりにボタンが欲しい場合、button_to() ヘルパーを使います。フォームも action 属性の値を管理するヘルパーを持ちます。つぎの章でフォームを詳しく学びます。リスト9-7はリンクヘルパーのいくつかの例を示します。

リンク 9-7 - <a><input><form> タグのためのリンクヘルパー

// 文字列上のハイパーリンク
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">my article</a>
 
// 画像上のハイパーリンク
<?php echo link_to(image_tag('read.gif'), 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France"><img src="/images/read.gif" /></a>
 
// ボタンタグ
<?php echo button_to('my article', 'article/read?title=Finance_in_France') ?>
 => <input value="my article" type="button"onclick="document.location.href='/routed/url/to/Finance_in_France';" />
 
// フォームタグ
<?php echo form_tag('article/read?title=Finance_in_France') ?>
 => <form method="post" action="/routed/url/to/Finance_in_France" />
 

絶対 URL (http://で始まり、ルーティングシステムで無視される) とアンカーと同じように、リンクヘルパーは内部 URI を受けとります。実際の世界のアプリケーションにおいて、内部URIは動的なパラメーターで作られます。リスト9-8はこれらすべての事例を示します。

リスト9-8 - リンクヘルパーが受けとる URL

// 内部のURI
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">my article</a>
 
// 動的なパラメーターを持つ内部 URI
<?php echo link_to('my article', 'article/read?title='.$article->getTitle()) ?>
 
// アンカーを持つ内部 URI
<?php echo link_to('my article', 'article/read?title=Finance_in_France#foo') ?>
 => <a href="/routed/url/to/Finance_in_France#foo">my article</a>
 
// 絶対 URL
<?php echo link_to('my article', 'http://www.example.com/foobar.html') ?>
 => <a href="http://www.example.com/foobar.html">my article</a>
 

リンクヘルパーのオプション

7章で説明したように、ヘルパーは、連想配列もしくは文字列である追加オプション引数を受けとります。リスト9-9で示されているように、これはリンクヘルパーにもあてはまります。

リスト9-9 - リンクヘルパーは追加オプションを受けとる

// 連想配列としての追加オプション
<?php echo link_to('my article', 'article/read?title=Finance_in_France', array(
  'class'  => 'foobar',
  'target' => '_blank'
)) ?>
 
// 文字列としての追加オプション(同じ結果)
<?php echo link_to('my article', 'article/read?title=Finance_in_France','class=foobar target=_blank') ?>
 => <a href="/routed/url/to/Finance_in_France" class="foobar" target="_blank">my article</a>
 

リンクヘルパーに対して symfony 固有のオプション (confirmpopup) の1つも追加できます。リスト9-10で示されるように、最初のオプションはリンクがクリックされたときに JavaScript の確認ダイアログボックスが表示され、2番目のオプションは新しいウィンドウにリンクが開かれます。

リスト9-10 - リンクヘルパー用の 'confirm' オプションと 'popup' オプション

<?php echo link_to('アイテムを削除する', 'item/delete?id=123', 'confirm=Are you sure?') ?>
 => <a onclick="return confirm('よろしいですか?');"
       href="/routed/url/to/delete/123.html">delete item</a>
 
<?php echo link_to('カートに追加する', 'shoppingCart/add?id=100', 'popup=true') ?>
 => <a onclick="window.open(this.href);return false;"
       href="/fo_dev.php/shoppingCart/add/id/100.html">カートに追加する</a>
 
<?php echo link_to('カートに追加する', 'shoppingCart/add?id=100', array(
  'popup' => array('popupWindow', 'width=310,height=400,left=320,top=0')
)) ?>
 => <a onclick="window.open(this.href,'popupWindow',
       'width=310,height=400,left=320,top=0');return false;"
       href="/fo_dev.php/shoppingCart/add/id/100.html">カートに追加する</a>
 

これらのオプションを結びつけることができます。

擬似的な GET と POST オプション

Web 開発者は、実際には POST が必要な箇所で GET リクエストを使ってしまっている場合があります。たとえば、次のような URL を考えてみてください:

http://www.example.com/index.php/shopping_cart/add/id/100

このリクエストではショッピングカートのオブジェクトに商品を追加するので、セッションやデータベースに保存されているアプリケーションのデータが変更されます。一方で、この URL はブックマークされ、キャッシュされ、検索エンジンのインデックスに登録される可能性があります。このような URL を使っている Web サイトで、データベースや統計情報にどのようなことが起こりうるか想像してみてください。検索エンジンのロボットはインデックス作成に POST リクエストを使いません。上のようなリクエストは、アプリケーションでは POST 処理すべきでしょう。

symfony では、link_to() ヘルパーや button_to() ヘルパーからのリンクを POST リクエストで遷移するように指定できます。リスト 9-11 のように、オプションに post=true を追加するだけです。

リスト 9-11 -リンクを POST リクエストに変換する

<?php echo link_to('go to shopping cart', 'shoppingCart/add?id=100', array('post' => true)) ?>
 => <a onclick="f = document.createElement('form'); document.body.appendChild(f);
                f.method = 'POST'; f.action = this.href; f.submit();return false;"
       href="/shoppingCart/add/id/100.html">ショッピングカートに移動する</a>
 

この <a> タグは href 属性を持ち、検索エンジンのロボットなどの、JavaScript サポートを持たないブラウザーはデフォルトのGETメソッドを行いながらリンクを辿ります。POST メソッドだけに応答するように、つぎのようなコードをアクションの始めに追加することで、アクションを制限しなければなりません:

$this->forward404Unless($this->getRequest()->isMethod('post'));
 

このオプションによって独自の <form> タグが生成されるので、フォームに設置されたリンクの上でこのオプションが使われていないことを確認してください(訳注:<form></form> のなかにさらに <form></form> が生成されるのでフォームが入れ子になります)。

実際にデータを投稿するリンクをPOSTとしてタグ付けするのはよい習慣です。

リクエストパラメーターを GET 変数として強制する

ルーティングルールに従えば、link_to() ヘルパーにパラメーターとして渡された変数はパターンに変換されます。routing.yml ファイルで内部 URI にマッチするルールが存在しない場合、リスト9-12で示されるように、デフォルトのルールが module/action?key=value/module/action/key/value に変換します。

リスト9-12 - デフォルトのルーティングルール

<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
=> <a href="/article/read/title/Finance_in_France">my article</a>
 

実際に GET 構文を維持する必要がある場合 --リクエストパラメーターを ?key=value 形式で渡す場合 -- query_string オプションのなかで、URL パラメーターの外部で強制する必要のある変数を設置する必要があります。 これが URL アンカーにも衝突するので、これを内部 URI に追加する代わりに anchor オプションに追加しなければなりません。リスト9-13で示されるように、すべてのリンクヘルパーはこれらのオプションを受けとります。

リスト9-13 - query_string オプションで GET 変数を強制する

<?php echo link_to('my article', 'article/read', array(
  'query_string' => 'title=Finance_in_France',
  'anchor' => 'foo'
)) ?>
=> <a href="/article/read?title=Finance_in_France#foo">my article</a>
 

GET 変数として表現されるリクエストパラメーターを持つ URL はクライアントサイド上のスクリプト、サーバーサイド上の $_GET 変数と $_REQUEST 変数によって解釈されます。

絶対パスを使う

デフォルトでリンクヘルパーとアセットヘルパーは相対パスを生成します。絶対パスへの出力を強制するには、リスト9-14で示されるように、absolute オプションを true にセットします。このテクニックはリンクをメールメッセージ、RSS フィード、API レスポンスに含めるために便利です。

リスト9-14 - 相対 URL の代わりに絶対 URL を取得する

<?php echo url_for('article/read?title=Finance_in_France') ?>
 => '/routed/url/to/Finance_in_France'
<?php echo url_for('article/read?title=Finance_in_France', true) ?>
 => 'http://www.example.com/routed/url/to/Finance_in_France'
 
<?php echo link_to('finance', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">finance</a>
<?php echo link_to('finance', 'article/read?title=Finance_in_France','absolute=true') ?>
 => <a href=" http://www.example.com/routed/url/to/Finance_in_France">finance</a>
 
// 同じことがアセットヘルパーにあてはまる
<?php echo image_tag('test', 'absolute=true') ?>
<?php echo javascript_include_tag('myscript', 'absolute=true') ?>
 

ルーティングの設定

ルーティングシステムは2つの仕事を行います:

  • モジュール/アクションとリクエストパラメーターを決定するために、やってくるリクエストの外部 URL を解釈し、内部 URI に変換します。
  • リンクで使われている内部 URI を外部 URL の形式に整形します (リンクヘルパーを使っていることが前提)。

規約はルーティングルールのセットに基づいています。これらのルールはアプリケーションの config/ ディレクトリに設置された routing.yml 設定ファイルに保存されます。リスト9-15はすべての symfony に搭載されたデフォルトのルーティングルールを示しています。

リスト9-15 - デフォルトのルーティングルール (frontend/config/routing.yml)

# デフォルトのルール
homepage:
  url:   /
  param: { module: default, action: index }

# generic rules
# please, remove them by adding more specific rules
default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*

ルールとパターン

ルーティングルールにより、外部 URL と内部 URI は一対一に関連付けられます。典型的なルールはつぎのように構成されます:

  • ユニークなラベル。ラベルによりルールが読みやすくなり、また、処理が高速になります。リンクヘルパーで使われます。
  • マッチするパターン (url キー)
  • リクエストパラメーターの値の配列 (param キー)

パターンはワイルドカード (アスタリスク * で表現される) と名前つきのワイルドカード (コロン : で始まる) を含むことができます。名前つきのワイルドカードへのマッチはリクエストパラメーターの値になります。たとえば、リスト9-15で定義された default ルールは /foo/bar といった URL にマッチし、module パラメーターをfooに、action パラメーターを bar に設定します。

名前つきのワイルドカードはスラッシュもしくはドットで分離できるので、つぎのようなパターンを書けます:

my_rule:
  url:   /foo/:bar.:format
  param: { module: mymodule, action: myaction }

この方法では、foo/12.xml のような外部 URL は my_rule にマッチして $bar=12$format=xml の2つのパラメーターを持つ mymodule/myaction を実行します。 sfPatternRouting ファクトリ設定で segment_separators パラメーターを変更することで区切り文字を追加できます (19章を参照)。

ルーティングシステムは routing.yml ファイルを上から順に解析し、最初にマッチした時点で止まります。これが独自のルール群をデフォルトのルールの上に追加しなければならない理由です。たとえば、URL の /foo/123 はリスト9-16で定義されたルールの両方にマッチしますが、symfony は最初 my_rule: をテストして、ルールがマッチする場合、default: をテストしません。(foo/123 アクションではなく) 123に設定された bar を持つ mymodule/myaction アクションでリクエストが処理されます。

リスト9-16 - ルールは上から下へ順に解析される

my_rule:
  url:   /foo/:bar
  param: { module: mymodule, action: myaction }

# デフォルトのルール
default:
  url:   /:module/:action/*

新しいアクションを作るとき、そのためのルーティングルールを作らなければならないということにはなりません。デフォルトの module/action パターンがあなたの用途に合う場合、routing.yml ファイルは忘れてください。しかしながら、アクションの外部 URL をカスタマイズしたい場合、デフォルトルールの上に新しいルールを追加します。

リスト9-17は article/read アクションの URL の外部形式の変更プロセスを示しています。

リスト9-17 - article/read アクションの URL の外部形式を変更する

<?php echo url_for('article/read?id=123') ?>
 => /article/read/id/123       // デフォルトのフォーマッティング
 
// これを /article/123 に変更するため、routing.yml の始めに
// 新しいルールを追加する
article_by_id:
  url:   /article/:id
  param: { module: article, action: read }
 

問題はリスト9-17の article_by_id ルールは article モジュールのほかのすべてのアクション用のデフォルトのルーティングを壊すことです。実際、article/delete のような URL は default ルールの代わりにこのルールにマッチし、delete アクションの代わりに delete に設定された idread アクションを呼び出します。この問題を回避するには article_by_id ルールにパターンの制約を追加することで、ワイルドカードである id が整数の URL のときだけにマッチするように設定する必要があります。

パターンの制約

URL が複数のルールにマッチする場合、制約や要件をパターンに追加してルールをより厳密にする必要があります。要件 (requirements) は、ルールのワイルドカード部分に対してマッチする条件を指定する正規表現で指定します。

たとえば、id パラメーター部分が整数である URL だけにマッチするように article_by_id ルールを修正するには、リスト 9-18 のように、ルールに1行追加します。

リスト9-18 - ルーティングルールに要件 (requirements) を追加する

article_by_id:
  url:   /article/:id
  param: { module: article, action: read }
  requirements: { id: \d+ }

こうすると、article/delete という URL は article_by_id ルールにはマッチしません。'delete' という文字列は整数であるという要件を満たさないからです。この場合、ルーティングシステムはマッチするルールを探し続け、最終的には default ルールにマッチします。

デフォルト値を設定する

パラメーターが定義されていなくても、ルールを機能させるために名前つきのワイルドカードにデフォルト値を渡すことができます。param: 配列のなかでデフォルト値を設定します。

たとえば、id パラメーターが設定されていない場合 article_by_id ルールはマッチしません。リスト9-19で示されるように、強制することができます。

リスト9-19 - ワイルドカードに対してデフォルト値を設定する

article_by_id:
  url:          /article/:id
  param:        { module: article, action: read, id: 1 }

デフォルトのパラメーターはパターンのなかで見つかるワイルドカードである必要はありません。リスト9-20において、display パラメーターは URL に表示されなくても true の値をとります。

リスト9-20 - リクエストパラメーターのデフォルト値を設定する

article_by_id:
  url:          /article/:id
  param:        { module: article, action: read, id: 1, display: true }

注意深く見ると、パターン内で見つからない変数 module と変数 action に対して articleread がそれぞれのデフォルト値であることがわかります。

sfRouting::setDefaultParameter() メソッドを呼び出すことですべてのルーティングルール用のデフォルトパラメーターを定義できます。たとえば、デフォルトで theme パラメーターを default に設定するすべてのルールが欲しい場合、$this->context->getRouting()->setDefaultParameter('theme', 'default'); をグローバルフィルターの1つに追加します。

ルールの名前を利用してルーティングを加速する

リスト9-21で示されるように、ルールラベルが'at'記号 (@) のまえに来る場合、リンクヘルパーはモジュール/アクションの組の代わりにルールラベルを受けとります。

リスト9-21 - モジュール/アクションの代わりにルールラベルを使う

<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?>
 
// つぎのように書くこともできる
<?php echo link_to('my article', '@article_by_id?id='.$article->getId()) ?>
// 最も速い書き方(余分なパースが不要):
<?php echo link_to('my article', 'article_by_id', array('id' => $article->getId())) ?>
 

このトリックに関してよい点とわるい点があります、よい点はつぎのとおりです:

  • 内部URIの整形が速く行われます。symfony はリンクにマッチするルールを見つけるためにすべてのルールを探す必要がないからです。ルーティングされたハイパーリンクをたくさん持つページにおいて、モジュール/アクションの組の代わりにルールラベルを使う場合、速度の押し上げは顕著です。
  • ルールラベルを使うことはアクションの背後にあるロジックを抽象化するための助けになります。アクション名を変更するが URL はそのままにする場合、routing.yml ファイルのなかで変更を行うだけで十分です。すべてのlink_to() 呼び出しはさらに変更しなくても機能します。
  • 呼び出しロジックはルールの名前で明確になります。モジュールとアクションが明確な名前を持つ場合でも、article/display よりも @display_article_by_slug を呼び出したほうがベターです。
  • routing.yml ファイルを見れば、どのアクションが有効なのかは分かります。

一方で、わるい点は、新しいハイパーリンクを追加することが自明ではなくなることです。アクションに対してどのラベルが使われているのか解明するために routing.yml ファイルをつねに参照する必要があるからです。大規模なプロジェクトでは、最終的にはルーティングのルールが多数になり、routing.yml をメンテナンスすることが難しくなってきます。このような場合は、アプリケーションをいくつかのプラグインにパッケージ分割します。各プラグインはアプリケーションの特定の機能のみを定義します。

しかしながら、経験上、長い目で見るとルーティングルールを使うことは最善の選択です。

テストの間 (dev 環境)、 ブラウザーの任意のリクエストに対してどのルールがマッチしたのかチェックしたい場合、Web デバッグツールバーの「logs and」セクションを展開し、「matched route XXX.」と書かれている行を探してください。Web デバッグモードに関する詳細な情報は16章で知ることになります。

routing.yml なしでルールを作成する

たいていの設定ファイルにあてはまることですが、routing.yml ファイルはルーティングルールを定義するための解決方法ですが、唯一の方法ではありません。dispatch() を呼び出すまえに、ルールをPHPで定義できます。なぜなら、dispatch() メソッドは現在のルーティングルールにしたがって実行するアクションを決定するからです。ルールをPHPで定義できるので、設定もしくはほかのパラメーターに依存する、動的なルールを作成できます。

ルーティングルールを扱うオブジェクトは sfPatternRouting ファクトリです。このオブジェクトは、コードのどの部分からでも sfContext::getInstance()->getRouting() という記述で利用できます。このオブジェクトの prependRoute() メソッドは、routing.yml に定義された既存のルールの手前に新しいルールを追加します。このメソッドのパラメーターは、ルーティングルールの定義に必要なものと同じです: ルートのラベル、URL パターン、デフォルト値の連想配列、および要件を指定する連想配列です。たとえば、リスト 9-18 の routing.yml ルールの定義は、リスト 9-22 の PHP コードと同等です。

リスト 9-22 - ルールを PHP で定義する

sfContext::getInstance()->getRouting()->prependRoute(
  'article_by_id',                                  // ルートの名前
  new sfRoute('/article/:id', array('module' => 'article', 'action' => 'read'), array('id' => '\d+')),                         // ルートオブジェクト
);
 

sfRoute クラスのコンストラクタは 3 つの引数をとります: パターン、デフォルト値の連想配列、要件を指定する連想配列です。

sfPatternRouting は手動でルートを扱うために便利なほかのメソッド、clearRoutes()hasRoutes() などを持ちます。もっと学ぶには API ドキュメント ([http://www.symfony-project.org/api/1_4/)) を参照してください。

いったんこの本で説明された概念を十分に理解し始めたら、オンラインの API ドキュメント、もっとよいのは symfony のソースを眺めることで、フレームワークの理解を深めることができます。この本では symfony の調整方法とパラメーターのすべては説明されていません。しかしながら、オンラインドキュメントは無制限です。

ルーティングクラスは、factories.yml コンフィギュレーションファイルで設定できます。(デフォルトのルーティングクラスを変更するには、17 章を参照してください。) この章では、デフォルトでルーティングクラスに設定されているsfPatternRoutingクラスについて説明します。

アクションのなかでルートを処理する

現在のルート情報を読みとりたい場合、たとえば「back to page xxx」リンクを用意するには、sfPatternRouting オブジェクトのメソッドを使います。リスト9-23で示されるように、getCurrentInternalUri() メソッドによって返される URI は、link_to() ヘルパーの呼び出しで使われます。

リスト9-23 - 現在のルート情報を読みとるために sfRouting オブジェクトを使う

// つぎのような URL を求める場合
http://myapp.example.com/article/21
 
$routing = $this->getContext()->getRouting();
 
// つぎの article/read アクションを使う
$uri = $routing->getCurrentInternalUri();
 => article/read?id=21
 
$uri = $routing->getCurrentInternalUri(true);
 => @article_by_id?id=21
 
$rule = $routing->getCurrentRouteName();
 => article_by_id
 
// 現在の module/action 名が必要なだけなら
// これらが実際のリクエストパラメーターであることを覚えておく
$module = $request->getParameter('module');
$action = $request->getParameter('action');
 

テンプレートのなかの url_for() ヘルパーのように内部 URI を外部 URL に変換する必要がある場合、リスト9-24で示されている sfController オブジェクトの genUrl() メソッドを使います。

リスト9-24 - 内部URIを変換するために sfController オブジェクトを使う

$uri = 'article/read?id=21';
 
$url = $this->getController()->genUrl($uri);
 => /article/21
 
$url = $this->getController()->genUrl($uri, true);
=> http://myapp.example.com/article/21
 

まとめ

ルーティング (routing) は外部 URL の形式をよりユーザーフレンドリーにするために設計された2つの方法を持つメカニズムです。それぞれのプロジェクトの1つのアプリケーションの URL 内部でフロントコントローラーの名前を省略できるようにするには URL の書き換え (URL rewriting)が必要です。ルーティングシステムが両方の方法で機能することを望むのであれば、URL をテンプレート内部に出力する必要があるたびにリンクヘルパーを使わなければなりません。routing.yml ファイルはルーティングシステムのルールを設定し、優先順位とルールの要件 (requirements) を使います。settings.yml ファイルはフロントコントローラーの名前と外部 URL で可能なプレフィックスの存在に関する追加設定を格納します。

インデックス

Document Index

関連ページリスト

Related Pages

日本語ドキュメント

Japanese Documents

リリース情報
Release Information

Symfony2 に関する情報(公式) Books on symfony