Symfony Advent Calendar JP 2012 18日目です。
下っ端(むしろ幽霊部員)なのに、ありえない5周目の執筆です(汗
今回は、symfony1 vs Symfony2 検索フォームの書き方比較と題して、Symfony2用の検索フォームの書き方について書きます。(私のような)symfony1→Symfony2に切り替え真っ最中の方のご参考になればと思います。
1.検索フォームを表すクラスについて
symfony1では、Hogeというモデルがある場合、doctrine:build --all-class(もしくは--filtersもしくは--all)によってlib/filter/doctrineに自動生成されるHogeFormFilterというクラスを使用するのが一般的でした。
HogeFormFilterはsfFormFilterのサブクラスで、通常のフォームを書くときと同様にconfigure(),setup()メソッド内でwidgetとvalidatorをセットすることができ、bind()で入力値を受け取ります。
一方、Symfony2では検索フォーム専用のフォームクラスというものはありません。
通常の更新用フォームと同じようにほにゃららTypeクラスを作り、buildForm()で各項目をadd()していきます(もしくはFormBuilderにaddして直でフォームを作る)。
なお、クラス名に特に決まりがあるわけではないのですが、私が書くときは、Hogeというエンティティがある場合、HogeTypeを登録・更新用、HogeSearchTypeを検索用のようにしています。
2.csrfトークンチェックを切る
csrfトークンがついたままだと、何かの検索結果ページをメールやtwitterやredmineでシェアするときに不便ですよね。検索フォームは何かデータの登録・更新・削除を行うフォームではないのでcsrfトークンのチェックは省いても大丈夫だと一般に(?)言われています。
そこで、symfony1とSymfony2それぞれのやり方で検索フォームのcsrfトークンチェックをOFFにする方法を見てみます。
symfony1では、HogeFormFilterはsfFormFilterのサブクラス、sfFormFilterはsfFormサブクラスなので、デフォルトの状態ではcsrfトークンチェックが有効になっています。OFFにするには、configure()かsetup()の中で$this->disableLocalCSRFProtection()を呼び出します。(HogeFormFilterをnewするときにコンストラクタの第三引数をfalseにするという方法もあり)
一方、Symfony2ではsetDefaultOptions()メソッドを定義してcsrf_protectionオプションをfalseにすることにより、csrfトークンのチェックをOFFにできます。
#HogeSearchType.php
public function setDefaultOptions(¥Symfony¥Component¥OptionsResolver¥OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
));
}
3.実際に検索を行う方法
ここまででフォームHTMLの出力と入力値のバリデーションまでは行えるようになりましたが、いよいよ、実際に入力値による検索を実装する方法が問題となります。
symfony1では、HogeFormFilter::getQuery()を呼び出すことで、入力値を使った検索用のクエリ(Doctrine_Query)が自動で生成されました。(生成されるクエリ内容をカスタマイズしたい場合はフォームにaddXXXColumnQuery()メソッドを追加することで可能でした)
これに対し、Symfony2では、検索フォーム自体は登録・更新等に使うのと同じ単なるフォーム生成と入力値の検証のみを担当し、クエリ生成部分はコントローラ側に任されます。(おそらく、symfony1のような実装は、データベースとフォームの結合が密になりすぎて良くないと考えられたのでしょう。)
getData()によりフォームから取り出した検証済の入力値を使って、コントローラ側で自前で検索クエリを作成するようになっています。
#コントローラ。$formがnameという項目を持つHogeTypeから作られたフォームとして
$data = $form->getData(); //array('name' => 'foo')
$query = $entityManager->getRepository()->createQueryBuilder('r')->where('r.name = :name')->setParameter('name', $data['name'])->getQuery(); //検索用クエリ
$entities = $query->getResults(); //検索結果
これだとコントローラの見通しが悪くなり、複数の画面で検索処理を書く場合に不便なため、私はカスタムEntityRepositoryに専用メソッドを追加して実装するようにしています。
#コントローラ。$formがnameという項目を持つHogeTypeから作られたフォームとして
$data = $form->getData(); //array('name' => 'foo')
$query = $entityManager->getRepository('AcmeDemoBundle:Hoge')->generateSearchQuery($data); //検索用クエリ
$entities = $query->getResult(); //検索結果
#HogeRepository.php
public function generateSearchQuery($data)
{
$qb = $this->createQueryBuilder('r');
$qb->where('r.name = :name')->setParameter('name', $data['name']);
return $qb->getQuery();
}
#正直なところ、どっぷりsymfony1脳なせいか、Symfony2の検索実装方法は使いづらいと感じてしまっているのが現状だったり。。。