Symfony初心者がつまづきがちな22個のポイント

この記事は[Symfony Advent Calendar 2015](http://qiita.com/advent-calendar/2015/symfony) 17日目の記事です。前日の記事は@teematsuさんの「[Symfony 2.8とPHP-DIのAuto Wiring](http://teematsu.hatenablog.jp/entry/2015/12/16/014651)」でした。

---
こんにちは。はじめまして。tarokamikazeです。
すっかりSymfonyおじさんと化した私は、数々の後輩たちにSymfonyに関する相談を受けてきました。
そのなかでも、社内でよく聞かれることをまとめます。

# 導入編 

[The Symfony Book](http://symfony.com/doc/current/book/index.html) を読んだ人、[ブログチュートリアル](http://symblog.ganchiku.com/)をやってみた人向けです。

## 01.この案件にSymfony2を使いたいんですが

下記のような条件では、Symfony2はおすすめです。

* 専用サーバーを使える。
* 他のPHP フレームワークを触ったことがある。
* ビジネスロジックが複雑になりそうなシステムを作る。(業務アプリ等)
* テストは当然書く。 [^1]
* プログラミング技術を磨きたい。
* PHPのゆるふわさに嫌気が差してきた。[^2]
* (特に学習初期は)開発速度を犠牲にできる。ある程度学習コストを許容できる。
* ドメイン駆動設計に興味がある。

## 02.公式サイトどこ？

[日本語サイト](http://docs.symfony.gr.jp/symfony2/index.html) は情報が古いです。[本家(英語)](http://symfony.com/) を見ましょう。
不満に思う人は、翻訳作業に参加したらいいと思います。

以前参加したSymfony勉強会では
**「Symfonyを使うくらいリテラシーのある人は、英語くらい読めるっしょwww」**
と言っていました。恐ろしいですね。

## 03.日本語の本はありますか？

古いですが、「[効率的なWebアプリケーションの作り方 ~PHPによるモダン開発入門](http://www.amazon.co.jp/%E5%8A%B9%E7%8E%87%E7%9A%84%E3%81%AAWeb%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9-~PHP%E3%81%AB%E3%82%88%E3%82%8B%E3%83%A2%E3%83%80%E3%83%B3%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%E5%B0%8F%E5%B7%9D-%E9%9B%84%E5%A4%A7/dp/4774150827/ref=sr_1_3?s=books&ie=UTF8&qid=1449229273&sr=1-3&keywords=Symfony) 」しかなかったんですよ。書名にSymfonyって書いていないという罠。

しかし先日「[基本からしっかり学ぶ Symfony2入門](http://www.amazon.co.jp/%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E3%81%97%E3%81%A3%E3%81%8B%E3%82%8A%E5%AD%A6%E3%81%B6-Symfony2%E5%85%A5%E9%96%80-%E5%BE%8C%E8%97%A4-%E7%A7%80%E5%AE%A3/dp/4774178039/ref=sr_1_1?s=books&ie=UTF8&qid=1449229273&sr=1-1&keywords=Symfony) 」が出ました！ いい時代ですね！

## 04.ここは読んどけって記事はありますか？

公式が充実してます。それを読めば、たいていのことは載っています。
[Symfonyの歩き方／5分でわかるSymfony Best Practices](http://blog.asial.co.jp/1457) はいい記事ですね。@ryo511 さん、ありがとうございます！

## 05.おすすめのバンドルは？

* ユーザー/ログイン管理の[FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle)
* マイグレーション(DBスキーマ差分管理)の [DoctrineMigrationsBundle](http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html)
* フィクスチャ(DBへのデータ投入)の [DoctrineFixturesBundle](http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html)

## 06.どのバージョンを使えばいいのでしょう？

[ここ](http://symfony.com/doc/current/contributing/community/releases.html)を読んで考えましょう。Symfonyはサポート期間を明示してくれているので、利用バージョンを考えやすいですよね。

2015年12月現在、個人的には以下のように考えています。

* 実案件なら、最新版LTSの 2.8
* 勉強用なら、最新版の 3.0


## 07.公式の通りにインストールしたけど動かない

本当に公式の通りにインストールしたんですか？　本当に？？

多くの人が、なぜか[ここ](http://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup) を読み飛ばします。
きちんとキャッシュ・ログディレクトリにパーミッションを設定しましょう。

* Mac なら「2. Using ACL on a system that supports chmod +a」
* CentOSなら「3. Using ACL on a system that does not support chmod +a」
* Windowsなら、まずはMacを使わせてもらえるように上司（財布）と交渉しましょう。
  * 無理なら、VirtualBox に Linux を突っ込めばいいんじゃないでしょうかね。

## 08.フォルダ構成はどうしたらいいの？

Advent Calendarで[ちょうどいい記事](http://issei-m.hatenablog.jp/entry/2015/12/15/012827)が出ましたね。@issei-mさん、ありがとうございます！

## 09.設定は何で書けばいいの? XML?

[Symfony Best Practices](https://symfony.com/doc/current/best_practices/index.html)によると、以下の様な感じです。

* Routingは、アノテーション
* DoctrineのEntity定義も、アノテーション
* サービス設定はyaml [^4]

## 10.設定内容が反映されない><

キャッシュクリアしましょう。 
```
php app/console cache:clear
``` 

それでもだめなら、apache(php-fpm)を再起動しましょう。 
設定次第で、Symfony は app/cache だけではなく、APC(u) にもキャッシュをためます。

## 11.なんか英語のエラー出た><

まずは落ち着いて、エラーメッセージ読んでください。英語が苦手なら、Google翻訳に頼ってください。 
それでもわからなければ、エラーメッセージでググってください。 


利用者が多いフレームワークって、簡単なエラーでも記事が引っかかるのでいいですよね。

# 実装編

実案件で実装を始める人向けです。

## 12.Modelはどこに書くの？

「貴様の言うModelとは何だ？ 」と訊くと、たいてい詰まりますよねｗ

Symfony2 は、(暗に)ドメイン駆動設計を推奨しています。  [^3] 

* データを取り出したいなら、Repository
* ビジネスロジックを記述したいなら、サービスを作ってDIコンテナに登録

という風に書けばよいでしょう。

## 13.確認フォームってどうやったら出せるの?

コツは、「Entity自体ではなく、Postされたarrayをセッションに詰めること」。 
Doctrineは、Entity にメタデータを詰めます。このせいで、Entityをセッションに詰めると挙動がおかしくなります。(メタデータがserialize できません) 
Formをからめるのであれば、POSTされたarrayをセッションに詰めましょう。

確認画面のAction ではセッションに詰めたarrayを、入力画面と同じFormType にbindしてあげればよいのです。

## 14.Ajax用の API 作りたい！
[FOSRestBundle](http://symfony.com/doc/master/bundles/FOSRestBundle/index.html) を使ってください。JMSSerializer便利だよぉ。

え、APIがちょっとしかないから、バンドルは導入したくない？しょうがないなぁ。
[ここ](http://symfony.com/doc/current/components/http_foundation/introduction.html#creating-a-json-response)の通りにやればいいんじゃないのかなあ。

ちょっと古いけど、[Symfony2 REST API: the best way](http://welcometothebundle.com/symfony2-rest-api-the-best-2013-way/)も目を通しておいた方がいいでしょう。

## 15.CSVダウンロード機能を作りたい！

最近のExcelは、XMLも読めますよ？(真顔) [^5]

冗談はさておき、[ここ](http://sterfield.co.jp/programmer/symfony2%E3%81%A7csv%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F.html)とか見たらいいんじゃないでしょうかね。

## 16.FormやAPIの形式とEntityの形がかけ離れている......つらい......

以下のようなやり方もあります。定義が散るので望ましくはないのですが。[^6]

1. Form / API の形に合わせたニセEntity(JavaBean的なモノ)をつくる。
2. ニセEntity にあわせたFormType を用意し、リクエストデータはそれで受ける。
3. ビジネスロジックに ニセEntity を 投げる。
4. ビジネスロジック内でニセEntityを、本物のEntityに変換する。その後、永続化処理等を行う。

このときValidationは、form 側でやってもいいですし、変換後の本物EntityをValidatorサービスに投げてもいいでしょう。

## 17.ログインの仕組みを独自に作りたいんですけど......

悪いことは言わない。やめておけ。

Symfony2のログイン関係の仕組みは、複雑です。
可能な限りネイティブの仕組み、またはFOSUserBundleなどの外部バンドルにまかせたほうがいいです。
昔触ろうとして、一週間かかりました......

# Doctrine編

## 18.Doctrineが不要なカラムもselectしようとしています。なんとかしたい。

ORM とはそういうものです。DBを富豪的に使うのです。
それでも重いというのなら、札束を積んで物理で殴ればよいのです。

## 19.Lazy Load うざい

デバッグコンソールを見ると、大量のSQLを吐いているときがあります。
原因としては、Doctrine で Entityに OneToMany を設定していると
「親も子も使うことがわかりきっているのに、子供にアクセスしたときにlazy load されて大量のSQLが走ってる」
というケースが多いです。

こういう場合は、DQLをしっかり書きましょう。
DQLでjoinすれば、一発で取ってきてくれます。

``` ParentRepository.php

class ParentRepository extends EntityRepository
{
    public function findOneWithChild($parentId)
    {
        $qb = $this->createQueryBuilder('p');
        $qb->select('p, c') // selectにもjoin先のaliasを書くのがコツ
            ->join('p.children', 'c')
            ->andWhere('p.id = :parentId')
            ->setParameter(':parentId', $parentId);
        return $qb->getQuery()->getSingleResult();
    }
}
```

## 20.論理削除ってどうすればいいの？
まずは[ここ](http://qiita.com/Jxck_/items/156d0a231c6968f2a474) を読め。話はそれからだ。


[StofDoctrineExtensionsBundle](https://symfony.com/doc/master/bundles/StofDoctrineExtensionsBundle/index.html) の [Softdeleteable](https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/softdeleteable.md#entity-mapping) を使います。
削除日時のカラムがNot Null だったら、削除とみなされます。

## 21. OneToMany を設定していて、親のEntityと子供のEntityを一気にpersistしようとしたらエラーが出た

こういう単純な例って、なかなか見つからないんですよね。[^7]

``` test.php
$em = $this->get('doctrine.orm.entity_manager');

$parentEntity = new ParentEntity();
$childEntity = new ChildEntity();
$childEntity->setParent($parentEntity);

$em->persist($parentEntity);
$em->persist($childEntity);

$em->flush();
```

## 22.「Update時に、自動的にあのカラムも更新する」とかやりたい。EventLister を使えばいいの？

悪いことは言わない。やめておけ。
頼むから、更新対象のEntityをビジネスロジッククラスに渡して処理するんだ。

この面倒臭さを説明しだすと、ひとつの記事になってしまいます。
とりあえず [Doctrineのこのクラス](https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/UnitOfWork.php) を読んでみてください。理由がわかってもらえるはずです。

ちなみに、更新日時を入れたいだけだったら
[StofDoctrineExtensionsBundle](https://symfony.com/doc/master/bundles/StofDoctrineExtensionsBundle/index.html) の [Timestampable](https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/timestampable.md) を使いましょう。

---

ちなみに22個である理由は、妻が猫好きだからです。
明日は@naoyes さんです！

---
[^1]: Symfony2はテストが書きやすいと言われています。
[^2]: 自然とJavaっぽいコードになります。Java の Spring Framework に似ていると言われています。
[^3]: そういった意味では、Symfonyは単なる「MVC」フレームワークではない何かです。以前参加したSymfony勉強会でも、ドメイン駆動設計の話題でもちきりでした。
[^4]: [JMSDiExtraBundle](http://jmsyst.com/bundles/JMSDiExtraBundle)を使えば、サービスもアノテーションで定義できます。
[^5]: 筆者はExcelとCSVを憎んでいます。エンジニアになる前は、Excel職人（運用のひと）でした。
[^6]: 実例をGithubにあげたい。だが時間がない......
[^7]: [Cascade persist](http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations) する手もありますが、[おすすめされていません](http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/best-practices.html#use-cascades-judiciously)。