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のテンプレートでも同じようにコード補完をしてくれます。これだけ補ってくれると、開発がスムーズに進みます。
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
今回のデモ開発中で、必要になったことはまだないです。認証しはじめたら、必要になる、かも。