symfonyのルーティング処理の内側(sfRoute)

symfonyでのルーティング処理がどのように行われているのか、ソースを読んでみました。

ルーティングの設定は、routing.yml に記述します。
routing.yml に記述した設定は、sfRoutingConfigHandler によってパースされ、キャッシュファイルとして保存されます。(cacheディレクトリ内の config/config_routing.yml.php

例えば routing.yml に以下のように設定したとします。

routing_test1:
  url:   /Dir1/Dir2/:param1/:param2
  param: { module: modulename, action: actionname }
  requirements: { param1: param\d, param2: .* }

この設定は、キャッシュファイルには以下のように書き出されています。

<?php
'routing_test1' => new sfRoute('/Dir1/Dir2/:param1/:param2', array (
  'module' => 'modulename',
  'action' => 'actionname',
), array (
  'param1' => 'param\\d',
  'param2' => '.*',
), array (
))
?>

ルーティングの設定が複数ある場合は、上記の sfRoute が配列になります。

次に、sfRoute クラスのコンストラクタですが、

<?php
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array())
?>

となっています。

パターンのコンパイル

sfRoute::compile()

sfRoute クラスで、実際に URL とルーティングパターンのマッチング処理が行われる段階で、各インスタンスが保持しているパターンのコンパイルが行われ、パターンマッチ(preg_match)を行うための正規表現が生成されます。

このコンパイルでは、以下のような処理が行われます。

  1. コンパイル用オプションの準備(initializeOptions())
    1. セグメント区切り文字('/', '.')、変数のプレフィックス(':')などが設定されます。
  2. トークンのパース(tokenize())
    1. 区切り文字に従って、保持しているパターンを分解します。
  3. コンパイル
    1. 分解したパターンで変数の部分などを正規表現に置き換えます。
    2. パターンに「*」を使用した場合は、「(?:(?:/(?P<_star>.*))?」という正規表現に置き換えられます。
    3. パターンにパラメータ名(:param1など)を使用した場合は、「(?P[^/\.]+)」といった正規表現に置き換えられます。
    4. パラメータに対してrequirementsを指定している場合は、「(?Pparam\d)」と条件が埋め込まれます。

上で示したパターンの場合、最終的に以下のような正規表現が生成されます。

<?php
#^
/Dir1
/Dir2
/(?P<param1>param\d)
/(?P<param2>.*)
$#x
?>

パターンのマッチング

特定の URL が呼び出されると、symfony では保持しているルーティング一覧に対して、先頭から順にマッチング処理を行っていきます。sfRoute::matchesUrl() に URL が渡されます。

matchesUrl() 内では、コンパイル処理で生成された正規表現を使って、preg_match が実行されます。

<?php
if (!preg_match($this->regex, $url, $matches))
?>

マッチすると、URL パラメータの解析などが行われます。
パターンに「*」が含まれている場合は、parseStarParameter() により URL のパターン部分以降にあるパラメータも自動的にパースされます。
また、パターンにパラメータ名(:param1など)を記述した場合は、$matches 変数から取り出します。