Symfony Best Practice - 実践編 Chapter6,8-
前回
いじった結果は随時GitHubにあげていきます。
Chapter6はテンプレートについてです。 Symfony2で作ったTwigのフィルターをSymfony3で使う形にしてみようと思います。
最終的な形は以下です。
Twigエクステンションを作る
ベストプラクティスに従って作ってみます。 まずは、エクステンションのクラスを作ります。
- コンストラクタでメイン言語のtranslatorと併記する言語(sub locale)をインジェクトします。
- getFilters()/getName() は自前のエクステンションの情報を返せるようにオーバーライドします。
- multiTrans()でメインの処理を書きます。
https://github.com/hanahiroAze/symfony3_demo/pull/1/files#diff-32cc6ad49a0083779d7ee61592a42aa3
localeをいじるためのEventListenerを書きます。本家のまんまコピーです。
https://github.com/hanahiroAze/symfony3_demo/pull/1/files#diff-4968cc448d20825bee969ece9686fffa
次にサービスに登録します。この時EventListenerも登録してます。
https://github.com/hanahiroAze/symfony3_demo/pull/1/files#diff-1728bfc1c274f341afc1a0275fca694d
ここまでで自作エクステンションの追加が出来ました。
画面から使うために翻訳ファイルに手を入れます。 今回はわかりやすいようにヘッダーの「Symfony Demo」を併記してみます。
翻訳ファイルに「Symfony Demo」の対訳を追加します。
https://github.com/hanahiroAze/symfony3_demo/pull/1/files#diff-1848a926db6eb9fbe0e97b401f58d947
最後にTwigにフィルターを適用します。
https://github.com/hanahiroAze/symfony3_demo/pull/1/files#diff-a2f33b79364623e3cb2737239c5eee62
実行結果
無事併記されました!
Symfony Best Practice - 実践編 Chapter5-4-
前回
いじった結果は随時GitHubにあげていきます。
フックを使う
ドキュメントに沿って確認してみよう。 まずはdemoアプリですでに登録されているイベントリスナーを確認する。
ロケールの切り替えを行うイベントリスナーが登録されている。 イベント一覧によると、Requestイベントをフックしている。
// AppBundle\EventListener\RedirectToPreferredLocaleListener public function onKernelRequest(GetResponseEvent $event)
サービスとして登録されているか確認。
# /app/config/service.yml app.redirect_to_preferred_locale_listener: class: AppBundle\EventListener\RedirectToPreferredLocaleListener arguments: ['@router', '%app_locales%', '%locale%'] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
イベントのフックにはListenerとSubscriberを使う方法があるみたい。 ドキュメントによると、
- Subscriberはイベントに関する知識をクラスではなく、サービス定義に集約できるので、再利用性に優れている。(Symfony内部ではこっちを使っている)
- ListenerはバンドルごとにON/OFFの切り替え設定ができるので、柔軟性に優れている。
ユーザーの権限チェックとかアプリ全体で使うようなチェックはSubscriberで処理するのが良さそう。
Symfony Best Practice - 実践編 Chapter5-3-
前回
いじった結果は随時GitHubにあげていきます。
ParamConverterを使う
BlogControllerでParamConverterを使う場合と、使わない場合の書き方の比較をしてみる。
変更前は、ParamConverterを使っている。
// BlogController.php /** * @Route("/comment/{postSlug}/new", name="comment_new") * @Method("POST") * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * @ParamConverter("post", options={"mapping": {"postSlug": "slug"}}) */ public function commentNewAction(Request $request, Post $post)
そもそもParamConverterは何をしてくれるかドキュメントで確認。 ParamConverterにはDoctrine ConverterとDateTime Converterの2種類があって、今回はDoctrine Converterを使ってるということがわかった。
Doctrine ConverterによりpostSlug
というパラメーターでコントローラーに渡されてきた値を、$post
のslug
とマッピングさせて値の比較をしている。
ということは、slug
というパラメーターでコントローラーに渡してあげればParam Converte使わなくても動きそうなので、以下のように書き換えてみる。
// BlogController.php /** * @Route("/comment/{slug}/new", name="comment_new") * @Method("POST") * @Security("is_granted('IS_AUTHENTICATED_FULLY')") */ public function commentNewAction(Request $request, Post $post)
パラメーターはTwig側で設定されてるので、Twigの方も書き換える。
// /app/Resources/views/blog/_comment_form.html.twig {{ form_start(form, { method: 'POST', action: path('comment_new', { 'slug': post.slug }) }) }}
これでParamConverterを使わなくても動くようになった。 ただし、ドキュメントによると以下の条件の下で実現してるそうな。
- ルーティングに
{id}
が設定されている場合は、find ()
メソッドで主キーをフェッチ。 - ワイルドカードに指定されているプロパティで、エンティティにあるプロパティを
findOneBy()
でフェッチ。
上記挙動はoptionsを指定すると変えられる。
複雑なことをしたい場合は、Entityクラスに対応するRepositoryクラスを作って、@Entity
アノテーションでRepositoryクラスのメソッドを指定して検索させることができる。
感想
「EntityとRepositoryの違いって何?」と思っていた時期があったけど、この挙動を知ると役割の違いが明確化できていいな。 Best Practiceでは、Twigをデザイナーさんに触ってもらうことを考えているので、Entityでの名称を画面に持ち込みたくない(意識させたくない)場合とかに使ってねという意図を感じなくもない。
Symfony Best Practice - 実践編 Chapter5-2-
前回
いじった結果は随時GitHubにあげていきます。
あえて@Template
アノテーションを使ってみる
ベストプラクティスでは「使ってはいけない」とされている@Template
アノテーションをあえて使ってみる。
なるほど。すっきりはするけど、Responseオブジェクトを返しているというよりは、arrayを返しているようにしか見えないから、「なんでこれで画面が表示されるんだろう?」となるのは理解出来る。
しかも、アノテーション(SensioFrameworkExtraBundle)のドキュメントによると、コントローラーがResponseオブジェクトを返す場合は、@Template
の設定内容が無視されるらしい。
本当かどうか、やってみた。
// BlogController /** * @Route("/", defaults={"page": "1"}, name="blog_index") * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="blog_index_paginated") * @Method("GET") * @Cache(smaxage="10") * @Template("blog/index.html.twig") */ public function indexAction($page) { dump($page); $posts = $this->getDoctrine()->getRepository(Post::class)->findLatest($page); return $this->render('blog/test.html.twig', ['posts' => $posts]); // return ['posts' => $posts]; }
レスポンスにするテストページは以下のようにした。
{% extends 'base.html.twig' %} {% block body_id 'blog_index' %} {% block main %} <h1>This is test page</h1> {% endblock %} {% block sidebar %} {{ parent() }} {{ show_source_code(_self) }} {% endblock %}
実行結果
アノテーションよりResponseオブジェクトが優先されていることが確認できました。
Symfony Best Practice - 実践編 Chapter5-
前回
いじった結果は随時GitHubにあげていきます。
コントローラーのactionに渡されるパラメータをのぞく
まずはデバッグしてパラメータをのぞき見してみる。
// AppBundle\Controller\BlogController public function indexAction($page) { dump($page); // ~ }
てやると、
こんな風に出る。 2ページ目はどうかな。
え。。パラメーター文字列になるんだ。。ボタンで移動したときは文字列になって、トップページから1ページ目に遷移したときだけ数値になってるぽい。
ルーティングを見ると理解できる。デフォルトのルーティングはpage
が数値で設定されていて、/page
がルーティングに入ってきたときは文字列にしている。
// AppBundle\Controller\BlogController /** * @Route("/", defaults={"page": 1}, name="blog_index") * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="blog_index_paginated") * ~ */ public function indexAction($page)
気持ち悪いから、デフォルトも文字列にしておこうっと。
/** * @Route("/", defaults={"page": "1"}, name="blog_index") ~ */ public function indexAction($page)
せっかくなので、本家にPRだしてみよう。マージされたら嬉しいな。