OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

Symfony2のルーティングについて基礎的内容をまとめた

こんにちは。@mrtryです。 もう10月ですね。入社して半年か...。 残りの半年もがんばっていきたいと思います!

さて、「Symfony2入門」3回目の投稿です。 前回は、Symfony2での処理の流れについてまとめました。 今回は、その中で出てきた、ルーティングについて書きたいと思います。

以下、今回の目次になります。

  • ルーティングとは
  • ルーティングの設定方法
  • 設定されているルーティングの確認
  • ルーティングの設定例

ルーティングとは

ルーティングとは、リクエストされたURLに対して呼び出すアクションを決定する仕組みのことです。 例えば、/というパスにアクセスがあったらhomepageActionを実行する、というように対応付けする仕組みです。 Symfonyでは、ルーティングの設定次第で、対応付けを任意に設定することができます。

ルーティングの設定方法

ルーティングの設定は、YAML、XML、PHP、Annotationで設定できます。 今回の記事では、利用頻度が高いYAMLとAnnotationでの設定方法を紹介します。

YAML

Symfony2の設定ファイルは概ねYAMLで記述されています。 app/config以下を確認するだけでも、config.ymlparameters.ymlなどのYAMLファイルが確認できます。 ルーティングの設定も、YAMLファイルで設定されています。

YAMLについて馴染みがない方は、以下のページを読むと書き方を理解できるかと思います!

例によって、symfonyのデモアプリケーションを見てみます。 設定ファイルは、app/config/routing.ymlにあります。 以下の箇所の設定では、/というパスにアクセスがあった時、 default/homepage.html.twigを引数にとり、FrameworkBundleTemplateControllerにあるtemplateActionを実行するという設定が書かれています。 ルーティング名はhomepageとなっています。

homepage:
    path: /{_locale}
    requirements:
        _locale: '%app_locales%'
    defaults:
        _controller: FrameworkBundle:Template:template
        template:    default/homepage.html.twig
        _locale:     '%locale%'

Annotation

コントローラにAnnotationを書くことでルーティングの設定をすることもできます。 ベストプラクティス的には、Annotationを用いたルーティングの設定方法がオススメされています。

Annotationを利用するには、設定ファイルにAnnotationでルーティングするという設定をする必要があります。 例によって、symfonyのデモアプリケーションapp/config/routing.ymlを見てみます。 resourceでAnnotationが記述されているコントローラがあるディレクトリを指定し、typeAnnotationを設定します。

app/config/routing.yml

app:
    # @を先頭につけるとBundle以下からの相対パスで指定できます。
    resource: '@AppBundle/Controller/'
    type:     Annotation
    prefix:   /{_locale}
    requirements:
        _locale: '%app_locales%'
    defaults:
        _locale: '%locale%'

設定後、コントローラにてルーティングの設定を行うことができます。 /blog/blog/page/{page}にアクセスがあった時、このコントローラのindexAction()を呼び出すという設定がされています。 ルーティング名にはそれぞれ、blog_indexblog_index_paginatedとなっています。

src/AppBundle/Controller/BlogController.php

/**
* Controller used to manage blog contents in the public part of the site.
*
* @Route("/blog")
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class BlogController extends Controller
{
    /**
    * @Route("/", defaults={"page": 1}, name="blog_index")
    * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="blog_index_paginated")
    * @Method("GET")
    * @Cache(smaxage="10")
    */
    public function indexAction($page)
    {
       $posts = $this->getDoctrine()->getRepository(Post::class)->findLatest($page);

       return $this->render('blog/index.html.twig', ['posts' => $posts]);
    }

設定されているルーティングの確認

consoleでdebug:routeを実行すると、設定されているルーティングを確認することができます。 Nameを見ていくと、先ほど紹介したhomepageblog_indexblog_index_paginatedが確認できます。

$ bin/console debug:route

-------------------------- ---------- -------- ------ ----------------------------------------
Name                       Method     Scheme   Host   Path
-------------------------- ---------- -------- ------ ----------------------------------------
_wdt                       ANY        ANY      ANY    /_wdt/{token}
_profiler_home             ANY        ANY      ANY    /_profiler/
_profiler_search           ANY        ANY      ANY    /_profiler/search
_profiler_search_bar       ANY        ANY      ANY    /_profiler/search_bar
_profiler_info             ANY        ANY      ANY    /_profiler/info/{about}
_profiler_phpinfo          ANY        ANY      ANY    /_profiler/phpinfo
_profiler_search_results   ANY        ANY      ANY    /_profiler/{token}/search/results
_profiler                  ANY        ANY      ANY    /_profiler/{token}
_profiler_router           ANY        ANY      ANY    /_profiler/{token}/router
_profiler_exception        ANY        ANY      ANY    /_profiler/{token}/exception
_profiler_exception_css    ANY        ANY      ANY    /_profiler/{token}/exception.css
_twig_error_test           ANY        ANY      ANY    /{_locale}/_error/{code}.{_format}
admin_index                GET        ANY      ANY    /{_locale}/admin/post/
admin_post_index           GET        ANY      ANY    /{_locale}/admin/post/
admin_post_new             GET|POST   ANY      ANY    /{_locale}/admin/post/new
admin_post_show            GET        ANY      ANY    /{_locale}/admin/post/{id}
admin_post_edit            GET|POST   ANY      ANY    /{_locale}/admin/post/{id}/edit
admin_post_delete          DELETE     ANY      ANY    /{_locale}/admin/post/{id}
blog_index                 GET        ANY      ANY    /{_locale}/blog/
blog_index_paginated       GET        ANY      ANY    /{_locale}/blog/page/{page}
blog_post                  GET        ANY      ANY    /{_locale}/blog/posts/{slug}
comment_new                POST       ANY      ANY    /{_locale}/blog/comment/{postSlug}/new
security_login             ANY        ANY      ANY    /{_locale}/login
security_logout            ANY        ANY      ANY    /{_locale}/logout
homepage                   ANY        ANY      ANY    /{_locale}
-------------------------- ---------- -------- ------ ----------------------------------------

ルーティングの設定例

ルーティングの設定例を4パターン紹介します。

  • URLとコントローラを1対1で対応付け
  • ワイルドカードを利用した対応付け
  • 正規表現を利用した条件指定
  • HTTPメソッドの指定

それぞれのパターンで、YAMLとAnnotationの設定例を紹介します。 なお、Annotationでルーティング設定する際に必要なYAMLの設定は省略しています。

URLとコントローラを1対1で対応付け

URLとコントローラを1対1で対応付けする設定です。

以下の例では、/にアクセスがあったら、 AcmeDemoBundleMainControllerに定義されているhomepageAction()を呼び出す設定をしています。

YAML

YAMLで設定すると、以下のようになります。

_welcome:
    path:  /
    defaults:
        _controller: AcmeDemoBundle:Main:homepage

一番最初のkeyはルーティングに登録されるルート名です。 設定されたルート名はconsole debug:routeすると、Nameの欄に表示されます。 今回の設定だと、最初にkeyに_welcomeが設定されているので、これがそのままルート名になります。

ルート名以下は、ルーティングの設定になります。 マッチするパスの指定は、pathに設定します。 今回は、/にアクセスがあった際のルーティングを設定するので、path: /と書きます。

プレースホルダの初期値、特殊なルーティングパラメーターの設定する際には、defaults以下に書きます。 プレースホルダの初期値の設定については次の設定例で紹介し、この設定例では特殊なルーティングパラメーターの設定について紹介します。

特殊なルーティングパラメーター(Special Routing Parameter)とは、Symfonyで用意されている便利に使えるパラメータです。 _controller _format _locale の3種類がこれに該当します。

指定したパスで実行するコントローラを指定するには、特殊なルーティングパラメーターである_controller論理コントローラ名を設定します。 論理コントローラー名は、バンドル名:コントローラ名:アクション名という文字列で表現されます。

今回の場合、呼び出したいのはAcmeDemoBundleのMainControllerに定義されているhomepageAction()なので、 AcmeDemoBundle:Main:homepageが論理コントローラとなります。 注意点として、コントローラ名はController、アクション名はActionをそれぞれ省略する必要があるので、注意しましょう。

Annotation

同じ設定をAnnotationで設定すると、以下のようになります。

class MainController extends Controller
{
    /**
     * @Route("/", name="_welcome")
     */
    public function homepageAction()
    {
        ...
    }
}

Annotationでの設定は、Doucument Commentに@Route()というAnnotationを書くことで設定できます。 マッチするURLの指定は、( )内に書き、ルート名はname=""で指定をすることができます。 Annotationでルーティングを設定する際は、 _contorllerの指定は、Annotationを書いてあるコントローラが呼び出されるので、必要はありません。

ワイルドカードを利用した対応付け

/blog/1/blog/2/blog/3のようなパスにマッチするルーティングを設定しようとした時、 そのパスごとに設定を書いていたのでは、とても手間です。 内容が同じようなものを表示するのであれば、/blog/*というようにまとめて指定できたらとても便利です。 これは、/blog/{page}のようなプレースホルダを設定することで、マッチさせることができます。

以下の例では、 /blog/{page}というパスにアクセスがあった場合、AcmeBlogBundleBlogControllerindexAction()を実行する設定をしています。

YAML

YAMLで設定すると、以下のようになります。

blog_index:
    path:      /blog/{page}
    defaults:
        _controller:    AcmeBlogBundle:Blog:index
        page:           1

パスにプレースホルダを設定する際は、{プレースホルダ名}という書き方で設定できます。 今回の例では、pageという名前でプレースホルダを設定しています。

defaults以下では、プレースホルダの初期値を設定しています。 今回の例では、pageには初期値として1を設定しています。 この設定により、/blog/という感じにpageの箇所が空のパスにアクセスがあった場合、defaultsで指定した値が利用されpage = 1としてコントローラで処理が行われます。

また、設定したプレースホルダは、実行するコントローラの引数に設定することで、値をそのまま利用することができます。 例として、以下の様に書くことでメソッド内で値を利用できます。

public function indexAction($page)
{
    ...

    echo $page; // '/blog/{page}' のpageの値が返ってくる

    ...
}

Annotation

同じ設定をAnnotationで設定すると、以下のようになります。

    /**
     * @Route("/blog")
     *
     */
     class BlogController extends Controller
     {
        /**
         * @Route("/{page}", name="blog_index", defaults={"page": 1})
         *
         */
        public function indexAction($page)
        {
            ...
        }
    }

先ほどのAnnotationの設定と少し変わり、classに対しても@Route("/blog")と指定がされています。 こうすることで、class以下に定義されるURLに/blogというパスを付与することができます。 なので、indexAction()に設定されているURLは/{page}としか書かれていませんが、実際には/blog/{page}と一致します。

なお、YAMLではprefixを用いて設定することができます。

正規表現を利用した条件指定

/blog/{page}というように、まとめて指定する方法を紹介しました。 ですが、前回の設定だけだと、pageに対して値のチェックをしていないので、整数以外も取りうる可能性があります。 pageの値を正規表現でチェックし、整数のみに一致するルーティングの設定に修正します。

以下の例では、 /blog/{page}というURLにアクセスがあった場合、AcmeBlogBundleBlogControllerindexAction()を実行します。 ただし、pageの値は長さが1以上の整数、という制限を設定しています。

YAML

YAMLで設定すると、以下のようになります。

blog_index:
    path:      /blog/{page}
    defaults:
        _controller: AcmeBlogBundle:Blog:index
        page: 1
    requirements:
        page:  \d+

条件指定は、requirementsで指定し、ネストして定義してある変数名を挙げることで設定できます。 pageは整数にしぼりたいので、正規表現で長さが1以上の整数という指定をしています。

Annotation

同じ設定をAnnotationで設定すると、以下のようになります。

/**
 * @Route("/blog")
 *
 */
class BlogController extends Controller
{
    /**
     * @Route("/{page}", requirements={"page" = "\d+"}, name="blog_index",defaults={"page": 1})
     *
     */
    public function indexAction($page)
    {
        ...
    }
}

HTTPメソッドの指定

APIを実装した際などに、あるURLにアクセスするHTTPメソッドを指定したい時があります。 HTTPメソッドの指定も、ルーティングの設定で制限することができます。

以下の例では、/contactにアクセスがあった時

  • GETなら、AcmeDemoBundle:Main:contact
  • POSTなら、AcmeDemoBundle:Main:contactProcess

を呼び出すという設定をしています。

YAML

YAMLで設定すると、以下のようになります。

contact:
    path:     /contact
    defaults:
        _controller: AcmeDemoBundle:Main:contact
    methods:  [GET]

contact_process:
    path:     /contact
    defaults:
        _controller: AcmeDemoBundle:Main:contactProcess
    methods:  [POST]

HTTPメソッドを制限したいときは、methodsを用いて、配列でGETPOSTを指定することができます。

Annotation

同じ設定をAnnotationで設定すると、以下のようになります。

class MainController extends Controller
{
    /**
     * @Route("/contact", name="contact")
     * @Method("GET")
     *
     */
    public function contactAction()
    {
        ...
    }

    /**
     * @Route("/contact", name="contact_process")
     * @Method("POST")
     *
     */
    public function contactProcessAction()
    {
        ...
    }
}

@Method()というAnnotationを書くことで、メソッドの指定ができます。

おわりに

「Symfony2入門」の3回目として、ルーティングについて紹介しました。 次回は、コントローラについて基本的なことをまとめたいと思います。

参考