Issei.M's Techlog

Web/iOS エンジニアの僕が技術関連のメモ等をつらつらと。主に Symfony について書いています。

[Symfony] [Routing] ルートの host はなるべく設定した方がいいよと言うお話

この記事は Symfony Advent Calendar 2014 の12日目の記事です。

当初はFormについて何か書く予定でしたが、どうしても長くなってしまうのでRoutingについて書こうと思います。

SymfonyのRoutingでは、YAML, XML, Annotationなど様々な形式でルートを設定する事ができますが、皆さんは何で設定していますか?僕はYAMLとAnnotationの両方をよく使います。

Webアプリケーションでは、エンドユーザ向けやシステム管理者向けと言った、複数のサイト構成を取る事が多いと思います。その場合、サイト毎の共通設定をYAML、ページ毎の設定をAnnotationで設定します。

# app/config/routing.yml

# エンドユーザ向け
frontend:
  host:     app.com
  prefix:   /
  resource: @AppBundle/Controller/Frontend/

# システム管理者向け
backend:
  host:     app.com
  prefix:   /administration/
  resource: @AppBundle/Controller/Backend/
<?php // src/AppBundle/Controller/Frontend/DefaultController.php

// ...

/**
 * @Route("/")
 */
class DefaultContrller extends Controller
{
    /**
     * http://app.com/ のコントローラ
     *
     * @Route("/", name="app_frontend_default_top")
     */
    public function topAction(Request $request)
    {
        // ...
    }
}

// src/AppBundle/Controller/Backend/DefaultController.php

// ...

/**
 * @Route("/")
 */
class DefaultContrller extends Controller
{
    /**
     * http://app.com/management/dashboard のコントローラ
     *
     * @Route("/dashboard", name="app_backend_default_dashboard")
     */
    public function dashboardAction(Request $request)
    {
        // ...
    }
}

本題に入りますが、皆さんは普段ルートの host ディレクティブを設定していますか?勿論、サイト毎に異なるドメインを使う場合はしていると思いますが、上記のように同じドメインを使う場合はしていない方もいると思います。

そもそもホストルーティングはHTTPサーバがやってくれるので、URLマッチングでは必要ありません。ですが、これらの設定はURL生成でも使われる為、きちんと設定していないと問題が起きる事があります。

例えば、RouterItnerface::generate() の第3引数を true にすると絶対URLを生成する事ができますが、試しに先ほどの hostコメントアウトして試してみましょう。

コントローラ:

<?php

echo $this->get('router')->generate('app_backend_default_dashboard', [], true);

// FrameworkBundleのControllerを継承していればショートカットが使える
echo $this->generateUrl('app_backend_default_dashboard', [], true);

Twigテンプレート:

{{ url('app_frontend_default_top') }}

上記3パターン、いずれも結果はこうなります:

http://app.com/administration/dashboard

URLは正しく生成されました。一見問題が無いように見えますが、コンソールアプリケーションではどうでしょうか?

<?php // src/AppBundle/Command/EchoAbsoluteUrlCommand.php

// ...
class EchoAbsoluteUrlCommand extends ContainerAwareCommand
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $router = $this->getContainer()->get('router');
        $output->writeln($router->generate('app_backend_default_dashboard', [], true));
    }
}

結果は次のとおりです:

http://localhost/administration/dashboard

ホストが localhost になってます。エンドユーザ向けに配信されるメールに使われるURLがこうなってしまったら大変ですね。

ではなぜコンソールだと正しいURLが作られないのでしょうか?答えはドキュメントに書いてあります。

絶対 URL のホスト部分には、現在の Request オブジェクトのホストが使用されます。ホスト情報は PHP のサーバー情報から自動的に検出されるため、コマンドラインから実行するスクリプトの場合は、RequestContext オブジェクトで明示的にホストを指定してください。
ルーティング | Symfony2日本語ドキュメント

具体的には、HttpKernelのonKernelRequestイベントでRequestContextにサーバ情報が渡されます。そして、このイベントはコンソールでは実行されない為、ホストが localhost となってしまった訳です。

ドキュメントに沿って、URL生成前にRequestContextにホストを指定してやれば正しく動作するのですが、実はRouterは対象のルートにホストが設定されている場合はそちらの値を使用します。

なので、最初から host ディレクティブの設定をした方が簡単かつ安全です。そしてこの値には、DIコンテナのパラメータが使えるので、環境毎に異なる設定をする事も可能となっています:

# app/config/parameters.yml
parameters:
  # ...
  base_domain: app.com

# app/config/routing.yml

# エンドユーザ向け
frontend:
  host:     %base_domain%
  prefix:   /
  resource: @AppBundle/Controller/Frontend/

# システム管理者向け
backend:
  host:     %base_domain%
  prefix:   /administration/
  resource: @AppBundle/Controller/Backend/

 

まとめ

  • ルートのhostディレクティブはURLマッチングだけでなく、絶対URLの生成の時にも使われる
  • 設定されてなければRequestContextの値を使う
  • コンソールではRequestContextが正しく初期化されない
  • なのでURLマッチングでは不要でもhostは設定しておいた方がいい