Symfonyでタスク管理アプリ作ってみた(コントローラ編)

  • 0
    Like
  • 0
    Comment

    Symfonyフレームワークの中心的な役割を果たしているのがControllerと呼ばれるクラス群。ここに、ルーティング情報、処理内容、そして出力方法を書き込みます。

    フレームワークの勉強のため、Symfony3でタスク管理アプリ作ってみたのパート1です。

    ちなみに、Symfonyをインストールした時点でのディレクトリ構造。

    src
    └── AppBundle
        └── Controller
            └── DefaultController.php
    

    このコントローラークラスに実行するメソッドを追加していきます。

    Actionメソッド

    例えば、こんなコードになります。

    class DefaultController extends Controller
    {
        public function projectAction(Request $request)
        {
            $em = $this->getDoctrine()->getManager();
            $repository = $em->getRepository(Project::class);
            $projects = $repository->findAll();
            return $this->render('task/project.html.twig', [
                'projects' => $projects,
            ]);
        }
    

    DBアクセスにはDoctrine2を利用しているので、エンティティマネージャー$emを「ゲット」して、$projectを取得します。

    そしてtwigテンプレートに$projectを渡して画面を作成しています。twigの描画はrender内で完結しているので、フレームワーク全体を理解してなくてもコードが追いやすいです。

    SymfonyといえばDIというイメージだったので、コントローラーではサービスロケーターを使いまくるところが意外でした。

    アノテーションの導入

    アノテーションを利用するには「SensioFrameworkExtraBundle」バンドルをインストールする必要があります。ここらへんの詳細はPHP フレームワーク Symfony3 日本語ドキュメント Wiki
    などが詳しそうです。

    Routeアノテーション

    Routeアノテーションを使うと、コントローラのコードでルート定義を行うことができます。

    先のアクションメソッドだと、こんな感じになります。

    use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config;
    use Doctrine\ORM\EntityManager;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Response;
    
    class DefaultController extends Controller
    {
        /**
         * @Config\Route("/projects")
         * @param Request $request
         * @return Response
         */
        public function projectAction(Request $request)
        {
            /** @var EntityManager $em */
            $em = $this->getDoctrine()->getManager();
            $repository = $em->getRepository(Project::class);
            $projects = $repository->findAll();
            return $this->render('task/project.html.twig', [
                'projects' => $projects,
            ]);
        }
    

    そして、コメント内の@Config\Route("/projects", name="by-project")がRouteアノテーションになります。これを元にして、URLから実行するコントローラとメソッドを決定してます。

    実装とルートが一緒になっているので、気持ちがいいです。そして格好いいのですが、どんな点が便利になるのか

    Routeアノテーションのマッチング

    例えば次のような@Config\Routeの場合。

    use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config;
    
        /**
         * @Config\Route("/projects/{id}", name="project-detail")
         */
        public function indexAction($id) { ...
    

    これで/project/123などのようなURLにマッチして、このメソッドを呼び出してくれます。$idにはURLの{id}にマッチした値が入いります。変数の名前$idからRouteパターンの名前{id}を探し出してくれる親切仕様です。順番関係ないのです。

    わざわざuse文を使っているのは、以下のように書くとPhpStormでウォーニングがでてしまうからです。

    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    /**
     * @Route("/projects/{id}", name="project-detail")
     */
    

    設定が悪いのかな?

    ルート名の利用

    ルートには名前をつけられます。

    /**
     * @Route("/projects/{id}", name="project-detail")
     */
    

    上のアノテーションではname="project-detail"の部分です。このルート名を使ってリダイレクトやフォームに利用します。

    例えば、新規プロジェクトの登録後にプロジェクト詳細画面に飛ばす場合は、

    return $this->redirectToRoute('project-detail', ['id' => $id]);
    

    と使います。

    開発していて、ルートを変更した場合でもルート名が同じなら修正箇所が減るので、便利ですね。

    requirementsとdefaults

    {id}は数字のみとする場合にはrequirementsを、デフォルトの値を指定する場合にはdefaultsを指定することができます(
    マニュアル参照)。

    /**
     * @Route("/projects/{id}", 
     *     name="project-detail", 
     *     requirements={"id": "\d+"),
     *     defaults={"id":"123"}
     */
    

    アノテーションの便利な点

    Symfonyの「CONTROLLERに書くアノテーションについて」というページにもまとまってますが、アノテーションは便利だと思いました。

    PhpStormがすごい

    ルートアノテーションやルート名を利用するメリットは理解できますが、実際にコーディングすると名前を確認するのが面倒です。

    そんなふうに思ってました。

    が、PhpStormのコード補完機能がすごかったです。ちなみに次のPlugInを入れてるのですが…。

    • PHP Annotations
    • Symfony Plugin

    すると、アノテーションにあるルート名を全て覚えてくれて、redirectToRouteと書いたところで、コード補完をしてくれます。便利だ。

    ルート名をコード補完

    twigのテンプレートでも同じようにコード補完をしてくれます。これだけ補ってくれると、開発がスムーズに進みます。

    twigテンプレート名をコード補完

    PhpStormはずっと使ってましたが、こんな機能があるとは知りませんでした。

    Controllerの分割が楽

    ルートアノテーションを使う利点としては、コントローラーを分割しやすいと思いました。

    ルート用ファイルが別にあると、コントローラーを増やすと面倒なので、つい躊躇してしまいます。が、メソッドにアノテーションがくっついている構造だと、メソッドとアノテーションを一緒にコピペすると簡単に別クラスに移動できます。

    作っている間に、TaskControllerが200行近くまで大きくなってきたので、3つのクラスに分割しています。

    TaskController.php
    TaskCreateController.php
    TaskEditController.php
    

    中身は、クラス名からだいたい想像できる、はず…

    classルート

    クラスにもルートアノテーションを設定できます。すると、メソッドでのルートにプリフィックスとして使われます。

    /**
     * @Config\Route("/projects/")
     */
    class ProjectController {
        /**
         * @Config\Route("{id}", name="project-detail")
         */
        public function indexAction($id) { ...
    }
    

    コントローラで同じURLで始める場合は便利です。コントローラの継承でルートも継承できるかなと試しましたが、できませんでした。こういう場合はrouting.ymlを利用することができそうです。

    app:
        resource: "@AppBundle/AdminController/"
        type:     annotation
        prefix:    /admin/
    

    php bin/console debug:router

    ルート定義が各コントローラに分散するので、全貌を把握するのが難しくなります。そんな場合には、次のコマンドでルート一覧を出してくれます。

    php bin/console debug:router
    

    今回のデモ開発中で、必要になったことはまだないです。認証しはじめたら、必要になる、かも。