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

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

> フレームワークの勉強のため、[Symfony3でタスク管理アプリ作ってみた](http://qiita.com/asaokamei/items/8468aa366914664c518c)のパート１です。

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

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

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

### Actionメソッド

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

```php
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
](http://symdoc.kwalk.jp/doc/book/routing)などが詳しそうです。

### Routeアノテーション

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

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

```php
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`の場合。

```php
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でウォーニングがでてしまうからです。

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

設定が悪いのかな？

### ルート名の利用

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

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

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

例えば、[新規プロジェクトの登録後にプロジェクト詳細画面に飛ばす](https://github.com/asaokamei/TaskComplete/blob/e8ed55e9c848c7508a74014bc555e9912cbdf102/src/AppBundle/Controller/ProjectController.php#L46)場合は、

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

と使います。

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

### requirementsとdefaults

`{id}`は数字のみとする場合には`requirements`を、デフォルトの値を指定する場合には`defaults`を指定することができます（
[マニュアル参照](http://symfony.com/doc/current/routing/requirements.html)）。

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


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

Symfonyの「[CONTROLLERに書くアノテーションについて](http://symfony2-gf.blogspot.jp/2011/12/controller.html)」というページにもまとまってますが、アノテーションは便利だと思いました。

### PhpStormがすごい

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

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

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

* PHP Annotations
* Symfony Plugin


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

![ルート名をコード補完](https://qiita-image-store.s3.amazonaws.com/0/31171/0e7ad95a-c385-c758-fc2b-57c860762689.png)

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

![twigテンプレート名をコード補完](https://qiita-image-store.s3.amazonaws.com/0/31171/94c2f131-a0c7-0034-6af5-2c2ad0f9e246.png)

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

### Controllerの分割が楽

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

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

作っている間に、[TaskController](https://github.com/asaokamei/TaskComplete/blob/16e4ed45e5786bbb6f1a79c3225ac767dbf9c4b4/src/AppBundle/Controller/TaskController.php)が200行近くまで大きくなってきたので、[3つのクラスに分割](https://github.com/asaokamei/TaskComplete/commit/fe603a3152c3696bbfb55e9cd989ef176e3c107f#diff-a6d5ad3b1a9601460b99e9364cc9e7df)しています。

```
TaskController.php
TaskCreateController.php
TaskEditController.php
```

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

### classルート

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

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

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

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

### php bin/console debug:router

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

```
php bin/console debug:router
```

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