symfony book 日本語ドキュメント

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

リンクとURLはWebアプリケーションのフレームワークにおいて特別な扱いをする価値があります。アプリケーション唯一のエントリーポイント(フロントコントローラー)とヘルパーを利用することでURLの動作方法とそれらの表現方法を完全に分離できるようになります。この機能はルーティング(routing)と呼ばれます。ルーティングはアプリケーションをガジェットよりもユーザーフレンドリでセキュアにするための便利なツールです。この章ではsymfonyのアプリケーションでURLを扱うために知る必要があることをすべてお伝えします。

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

ルーティングとは何か?

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

サーバーに対する命令としてのURL

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

http://www.example.com/web/controller/article.php?id=123456&format_code=6532

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

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

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/2006/activity-breakdown.html

恩恵は計り知れません:

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

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

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

NOTE アセット(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=2006&subject=finance&title=activity-breakdown

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

ルーティングシステムを定義するにはrouting.ymlという名前の特別な設定ファイルを使います。リスト9-2で示されているルールを考えます。このルールはarticles/*/*/*のようなパターンを定義し、ワイルドカードとマッチする内容の一部を命名します(訳注:既存のルーティングファイルで試す場合、default:よりも上側にしないと機能しません。またphp symfony cache:clearでキャッシュをクリアする必要があります)。

リスト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/2006/activity-breakdown.html

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

TIP 外部URLの.html拡張子はシンプルな飾り付けでルーティングシステムは無視します。その唯一の利点は動的なページを静的なページに見えるようにすることです。この章の後のほうにある"ルーティングの設定"でこの拡張子を有効にする方法を見ることになります。

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

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

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

[php]
// url_for()ヘルパーは内部URIを外部URLに変換する
<a href="<?php echo url_for('article/permalink?subject=finance&year=2006&title=activity-breakdown') ?>">ここをクリック</a>

// ヘルパーはURIがarticle_by_titleルールにマッチすることを見る
// ルーティングシステムはそれから外部URLを作成する
 => <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">ここをクリック</a>

// link_to()ヘルパーは直接ハイパーリンクを出力し
// PHPとHTMLを混合させることを回避する
<?php echo link_to(
  'ここをクリック',
  'article/permalink?subject=finance&year=2006&title=activity-breakdown') ?>
) ?>

// 内部では、link_to()はurl_for()への呼び出しを行うので結果はつぎのものと同じ
=> <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">ここをクリック</a>

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

URLを書き換える

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

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

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

prod:
  .settings:
    no_script_name:  off

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

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

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

http://www.example.com/myapp_dev.php/articles/finance/2006/activity-breakdown.html

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

http://www.example.com/articles/finance/2006/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

  # .somethingを持ったすべてのファイルをスキップする
  # ルートでピリオドを許可するためにつぎの3行をコメントにする
  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章でキャッシュを説明します)。

TIP route内でピリオドを許可するには、.htaccessファイルで最初の2つの書き換え条件と最初の書き換えルールをコメントにします。

しかしながら、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_nameonに設定する場合、すべてのURLはprod環境のfrontendアプリケーションへのリクエストとして解釈されます。これが任意のプロジェクトに対してURLの書き換えを利用できる1つの環境を持つアプリケーションを1つだけ持つことができる理由です。

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

リンクヘルパー

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

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

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

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

[php]
// 文字列上のハイパーリンク
<?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

[php]
// 内部の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]
// 連想配列としての追加オプション
<?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]
<?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はブックマークされ、キャッシュされ、検索エンジンのインデックスに登録されます。すべての嫌なことがデータベースもしくはこのテクニックを使うWebサイトの評価指標に起こることを想像してみてください。当然のことながら、このリクエストはPOSTとして見なされるべきです。なぜなら検索エンジンのロボットはインデックスの上ではPOSTリクエストを行わないからです。

symfonyはlink_to()ヘルパー、もしくはbutton_to()ヘルパーへの呼び出しを実際のPOSTに変換する方法を提供します。リスト9-11で示されるように、post=trueオプションを追加するだけです。

リスト9-11 -リンク呼び出しをPOSTリクエストにする

[php]
<?php echo link_to('ショッピングカートに移動する', 'shoppingCart/add?id=100', '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メソッドだけに応答するように、アクションの始めにつぎのようなコードを追加することで、アクションを制限しなければなりません:

[php]
$this->forward404If($request->getMethod() != sfRequest::POST);

このオプションは独自の<form>タグを生成するので、フォームに設置されたリンク上でこのオプションが使われていないことを確認してください。

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

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

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

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

[php]
<?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パラメーターの外部で強制する必要のある変数を設置します。リスト9-13で示されるように、すべてのリンクヘルパーがこのオプションを受けとります。

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

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

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

SIDEBAR アセットヘルパー

7章でアセットヘルパーのimage_tag()stylesheet_tag()javascript_include_tag()を紹介しました。これらのヘルパーによって画像、スタイルシート、JavaScriptファイルをレスポンスに含めることができます。これらのアセットへのパスはルーティングルールによって処理されません。これらは公開Webディレクトリの元に実際に設置されたレスポンスにリンクするからです。

アセットに対してファイルの拡張子を記載する必要はありません。symfonyは自動的に.png.js、もしくは.cssの拡張子を画像、JavaScript、もしくはスタイルシートのヘルパー呼び出しに追加します。また、symfonyはweb/images/ディレクトリ、web/js/ディレクトリとweb/css/ディレクトリで自動的にこれらのアセットを探します。もちろん、特定のファイルフォーマットもしくは特定の場所からのファイルを含めたい場合、正式なファイル名もしくはファイルパスを引数として使います。そして、メディアファイルが明確な名前を持つ場合、symfonyがあなたの代わりに決定するので、alt属性を指定することを悩まずにすみます。

[php]
<?php echo image_tag('test') ?>
<?php echo image_tag('test.gif') ?>
<?php echo image_tag('/my_images/test.gif') ?>
 => <img href="/images/test.png" alt="Test" />
    <img href="/images/test.gif" alt="Test" />
    <img href="/my_images/test.gif" alt="Test" />

画像のサイズを修正するにはsize属性を使います。これは、ピクセル単位の、xで区切られた幅、高さを必要とします。

[php]
<?php echo image_tag('test', 'size=100x20')) ?>
 => <img href="/images/test.png" alt="Test" width="100" height="20"/>

(JavaScriptとスタイルシートに対して)アセットを<head>セクションのなかに含めたい場合、レイアウト内で_tag()バージョンを使う代わりに、テンプレートのなかでuse_stylesheet()ヘルパーとuse_javascript()ヘルパーを使います。これらのヘルパーはアセットをレスポンスに追加し、</head>タグがブラウザーに送られるまえにこれらのアセットはインクルードされます。

絶対パスを使う

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

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

[php]
<?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') ?>

SIDEBAR メールヘルパー

今日において、Eメール収集ロボットがWebを徘徊するので、一日以内にスパムの餌食にならずにすむEメールアドレスを表示することはできません。symfonyがmail_toヘルパーを提供する理由はそういうことです。

mail_to()ヘルパーは2つのパラメーターをとります: 実際のEメールアドレスと表示される文字列です。追加オプションはHTMLでは全く読めない何かを出力するencodeパラメーターを受けとります。ブラウザーはこれを理解できますがロボットは理解できません。

[php]
<?php echo mail_to('myaddress@mydomain.com', 'contact') ?>
 => <a href="mailto:myaddress@mydomain.com'>contact</a>
<?php echo mail_to('myaddress@mydomain.com', 'contact', 'encode=true') ?>
 => <a href="&#109;&#x61;... &#111;&#x6d;">&#x63;&#x74;... e&#115;&#x73;</a>

エンコードされたEメールのメッセージはランダムな10進法と16進法のエンティティエンコーダによって変換された文字列で構成されます。このトリックは現在のアドレス収集するたいていのスパムボットを停止させますが、収集テクニックは急速に発展していることをご了承ください。

ルーティングの設定

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

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

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

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

default_symfony:
  url:   /symfony/:action/*
  param: { module: default }

default_index:
  url:   /:module
  param: { action: index }

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

ルールとパターン

ルーティングルールは外部URLと内部URI間の全単射の関係(bijective associations)です。典型的なルールはつぎのように構成されます:

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

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

リスト9-16 - ルールは完全に解析される

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

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

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

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

リスト9-17 - artile/readアクションに対して外部URL形式を変更する

[php]
<?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に設定されたidをともなうreadアクションを呼び出します。この問題を回避するには、article_by_idルールがワイルドカードであるidが整数であるURLだけにマッチするようにパターン制約を追加しなければなりません。

パターンの制約

URLが複数のルールにマッチするとき、制約もしくは要件(requirements)をパターンに追加することでルールを洗練させなければなりません。要件は正規表現のセットでマッチするルールのためにワイルドカードによってマッチされなければなりません。

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

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

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

URLのarticle/deletearticle_by_idにはマッチできません。'delete'の文字列が要件を満たさないからです。それゆえ、ルーティングシステムはつぎのルールでマッチするものを探し続け、最後にはdefaultルールを見つけます。

SIDEBAR パーマリンク

ルーティングのためのよいセキュリティのガイドラインは主キーを隠し、これらを可能なかぎり重要な文字列で置き換えることです。これらのIDよりもこれらのタイトルから記事にアクセスしたい場合はどうしますか?これを行うにはつぎのような外部URLになります

http://www.example.com/article/Finance_in_France

この範囲に対して、新しいpermalinkアクションを作成する必要があります。このアクションはidパラメーターの代わりにslugパラメーターを使い、新しいルールを追加します:

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

article_by_slug:
  url:          /article/:slug
  param:        { module: article, action: permalink }

permalinkアクションはタイトルからリクエストされた記事を決定する必要があるので、モデルは適切なメソッドを提供しなければなりません。

[php]
public function executePermalink()
{
  $article = ArticlePeer::retrieveBySlug($this->getRequestParameter('slug');
  $this->forward404Unless($article);  // 記事がslugにマッチしない場合404を表示する
  $this->article = $article;          // オブジェクトをテンプレートに渡す
}

内部URIの正しいフォーマッティングを有効にするために、テンプレートのなかのreadアクションへのリンクをpermalinkアクションへのリンクに置き換えることも必要です。

[php]
// つぎのコードを
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?>

// 以下のコードに置き換える
<?php echo link_to('my article', 'article/permalink?slug='.$article->getSlug()) ?>

requirementsの行のおかげで、article_by_idルールが最初に現れたとしても、/article/Finance_in_Franceのような外部URLがarticle_by_slugルールにマッチします。

slugによって記事が読みとられるので、データベースのパフォーマンスを最適化するにはインデックスをArticleモデルの記述内容のslugカラムに追加すべきです。

デフォルト値を設定する

パラメーターが定義されていなくても、ルールを機能させるために名前つきのワイルドカードにデフォルト値を渡すことができます。デフォルト値を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がそれぞれのデフォルト値であることがわかります。

TIP sf_routing_default設定パラメーターを定義することによって、すべてのルーティングルールに対してデフォルトのパラメーターを定義できます。たとえば、デフォルトでthemeパラメーターをdefultの値に設定するすべてのルールが欲しい場合、sfConfig::set('sf_routing_defaults',array('theme' => 'default'));の行をアプリケーションのconfig.phpに追加します。

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

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

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

[php]
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?>

// つぎのように書くこともできる
<?php echo link_to('my article', '@article_by_id?id='.$article->getId()) ?>

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

一方で、わるい点は、新しいハイパーリンクを追加することが自明ではなくなることです。アクションに対してどのラベルが使われているのか解明するためにrouting.ymlファイルにつねに参照する必要があるからです。

最良の選択はプロジェクト次第です。結局は、あなた次第です。

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

.html拡張子を追加する

以下の2つのURLを比較してください:

http://myapp.example.com/article/Finance_in_France
http://myapp.example.com/article/Finance_in_France.html

同じページであっても、ユーザー(とロボット)はURLなのでこれを違うものとして見るかも知れません。2番目のURLは静的なページの深くてよく整理されたWebディレクトリを呼び出します。静的なページは検索エンジンがインデックスを作成する方法を理解しているWebサイトの種類のものです。

リスト9-22で示されるように、サフィックスをルーティングシステムによって生成されたすべての外部URLに追加するには、settings.ymlファイル内のsuffixの値を変更します。

リスト9-22 - すべてのURLに対してサフィックスを設定する(myapp/config/settings.yml)

prod:
  .settings
    suffix:         .html

デフォルトのサフィックスはピリオド(.)に設定されます。このことはあなたが接尾辞を指定しないかぎりルーティングシステムは接尾辞を追加しないことを意味します。

時に、唯一のルーティングルールのためにサフィックスを指定する必要があります。その場合、リスト9-23で示されるように、接尾辞をrouting.ymlファイルの関連するurl:の行に直接書きます。グローバルな接尾辞は無視されます。

リスト9-23 - 1つのURLに対してサフィックスを設定する(myapp/config/routing.yml)

article_list:
  url:          /latest_articles
  param:        { module: article, action: list }

article_list_feed:
  url:          /latest_articles.rss
  param:        { module: article, action: list, type: feed }

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

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

ルーティングルールを扱うオブジェクトはSingletonのsfRoutingです。これはsfRouting::getInstance()メソッドを求めることでコードのあらゆる部分から利用できます。prependRoute()メソッドはrouting.ymlファイルで定義された既存のルールの上に新しいルールを追加します。このメソッドは4つのパラメーターを必要とします。これらのパラメーターはルールを定義するために必要とされるパラメーターと同じです: ルートのラベル、パターン、デフォルト値の連想配列と別の要件の連想配列です。たとえば、リスト9-18で示されるrouting.ymlルールの定義はリスト9-24で示されるPHPコードと同等です。

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

[php]
sfRouting::getInstance()->prependRoute(
  'article_by_id',                                  // routeの名前
  '/article/:id',                                   // routeパターン
  array('module' => 'article', 'action' => 'read'), // デフォルト値
  array('id' => '\d+'),                             // 要件
);

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

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

アクションのなかでrouteを処理する

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

リスト9-25 - 現在のrouteについて情報を読みとるためにsfRoutingオブジェクトを使う

[php]
// つぎのようなURLを求める場合
http://myapp.example.com/article/21

// article/read アクションのなかでつぎのコードを使う
$uri = sfRouting::getInstance()->getCurrentInternalUri();
 => article/read?id=21

$uri = sfRouting::getInstance()->getCurrentInternalUri(true);
 => @article_by_id?id=21

$rule = sfRouting::getInstance()->getCurrentRouteName();
 => article_by_id

// 現在のmodule/action名が必要なだけなら
// これらが実際のリクエストパラメーターであることを覚えておく
$module = $this->getRequestParameter('module');
$action = $this->getRequestParameter('action');

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

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

[php]
$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で可能なプレフィックスの存在に関する追加設定を含みます。