|
| 1 | +フォーム |
| 2 | +========== |
| 3 | + |
| 4 | +フォームは、取り扱う範囲が広く、膨大な機能があるために、誤って使用されることが最も多いコンポーネントです。 |
| 5 | +この章では、フォームを活用して素早く実装できるようにすることができるベストプラクティスを説明します。 |
| 6 | + |
| 7 | +フォームを構築する |
| 8 | +------------------- |
| 9 | + |
| 10 | +.. best-practice:: |
| 11 | + |
| 12 | + フォームをPHPクラスで定義しましょう。 |
| 13 | + |
| 14 | +Formコンポーネントでは、コントローラーのコードの中でもフォームを構築できるようになっています。はっきり言って、フォームをどこか他の場所で使う予定がないのなら、それでも全く構いません。 |
| 15 | +しかし、コードの整理と再利用のために、あらゆるフォームをPHPクラスとして定義することをおすすめします。 |
| 16 | + |
| 17 | +.. code-block:: php |
| 18 | +
|
| 19 | + namespace AppBundle\Form; |
| 20 | +
|
| 21 | + use Symfony\Component\Form\AbstractType; |
| 22 | + use Symfony\Component\Form\FormBuilderInterface; |
| 23 | + use Symfony\Component\OptionsResolver\OptionsResolverInterface; |
| 24 | +
|
| 25 | + class PostType extends AbstractType |
| 26 | + { |
| 27 | + public function buildForm(FormBuilderInterface $builder, array $options) |
| 28 | + { |
| 29 | + $builder |
| 30 | + ->add('title') |
| 31 | + ->add('summary', 'textarea') |
| 32 | + ->add('content', 'textarea') |
| 33 | + ->add('authorEmail', 'email') |
| 34 | + ->add('publishedAt', 'datetime') |
| 35 | + ; |
| 36 | + } |
| 37 | +
|
| 38 | + public function setDefaultOptions(OptionsResolverInterface $resolver) |
| 39 | + { |
| 40 | + $resolver->setDefaults(array( |
| 41 | + 'data_class' => 'AppBundle\Entity\Post' |
| 42 | + )); |
| 43 | + } |
| 44 | +
|
| 45 | + public function getName() |
| 46 | + { |
| 47 | + return 'post'; |
| 48 | + } |
| 49 | + } |
| 50 | +
|
| 51 | +このフォームクラスを利用するためには、 ``createForm`` を使い、この新しいクラスのインスタンスを渡してください。 |
| 52 | + |
| 53 | +.. code-block:: php |
| 54 | +
|
| 55 | + use AppBundle\Form\PostType; |
| 56 | + // ... |
| 57 | +
|
| 58 | + public function newAction(Request $request) |
| 59 | + { |
| 60 | + $post = new Post(); |
| 61 | + $form = $this->createForm(new PostType(), $post); |
| 62 | +
|
| 63 | + // ... |
| 64 | + } |
| 65 | +
|
| 66 | +フォームをサービスとして登録する |
| 67 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 68 | + |
| 69 | +`フォームタイプをサービスとして定義する`_ こともできます。 |
| 70 | +しかし、多くの場所でフォームを再利用する予定があったり、他のフォームに直接あるいは `collection`_ として埋め込む予定がない限り、この方法は推奨 *しません* 。 |
| 71 | + |
| 72 | +ほとんどのフォームは、何かを編集したり作成したりするのに使われるだけなので、フォームをサービスとして登録するのはやりすぎです。コントローラーの中で使われているフォームクラスがどれなのかわかりにくくなってしまいます。 |
| 73 | + |
| 74 | +フォームのボタン定義 |
| 75 | +------------------------- |
| 76 | + |
| 77 | +フォームクラスは、フォームがアプリケーション内の *どこで* 使われるのかなるべく無関心であるべきです。後から再利用しやすくなります。 |
| 78 | + |
| 79 | +.. best-practice:: |
| 80 | + |
| 81 | + ボタンはテンプレート上で追加し、フォームクラスやコントローラーに書かないようにしましょう。 |
| 82 | + |
| 83 | +Symfony 2.5以降では、フォームにボタンの定義を追加することができます。フォームをレンダリングするテンプレートを簡略にするのに良い方法ですが、フォームクラスの中に直接ボタンを定義してしまうと、フォームの扱う範囲を極端に狭めてしまうことになるのです。 |
| 84 | + |
| 85 | +.. code-block:: php |
| 86 | +
|
| 87 | + class PostType extends AbstractType |
| 88 | + { |
| 89 | + public function buildForm(FormBuilderInterface $builder, array $options) |
| 90 | + { |
| 91 | + $builder |
| 92 | + // ... |
| 93 | + ->add('save', 'submit', array('label' => 'Create Post')) |
| 94 | + ; |
| 95 | + } |
| 96 | +
|
| 97 | + // ... |
| 98 | + } |
| 99 | +
|
| 100 | +このフォームは投稿を新規作成する用途で作られたの *かも* しれませんが、投稿を編集する場面で再利用しようとすると、ボタンのラベルが間違っていることになります。 |
| 101 | +その代わりに、コントローラーでフォームボタンを定義する開発者もいるでしょう。 |
| 102 | + |
| 103 | +.. code-block:: php |
| 104 | +
|
| 105 | + namespace AppBundle\Controller\Admin; |
| 106 | +
|
| 107 | + use Symfony\Component\HttpFoundation\Request; |
| 108 | + use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
| 109 | + use AppBundle\Entity\Post; |
| 110 | + use AppBundle\Form\PostType; |
| 111 | +
|
| 112 | + class PostController extends Controller |
| 113 | + { |
| 114 | + // ... |
| 115 | +
|
| 116 | + public function newAction(Request $request) |
| 117 | + { |
| 118 | + $post = new Post(); |
| 119 | + $form = $this->createForm(new PostType(), $post); |
| 120 | + $form->add('submit', 'submit', array( |
| 121 | + 'label' => 'Create', |
| 122 | + 'attr' => array('class' => 'btn btn-default pull-right') |
| 123 | + )); |
| 124 | +
|
| 125 | + // ... |
| 126 | + } |
| 127 | + } |
| 128 | +
|
| 129 | +
|
| 130 | +これも重大な誤りです。というのも、プレゼンテーション用のマークアップ(ラベル、CSSクラスなど)を純粋なPHPコードの中に混在させてしまっているからです。 |
| 131 | +関心の分離は常に意識すべきプラクティスであり、全ての見た目に関係する物事はviewレイヤーに置くべきでしょう。 |
| 132 | + |
| 133 | +.. code-block:: html+jinja |
| 134 | + |
| 135 | + <form method="POST" {{ form_enctype(form) }}> |
| 136 | + {{ form_widget(form) }} |
| 137 | + |
| 138 | + <input type="submit" value="Create" |
| 139 | + class="btn btn-default pull-right" /> |
| 140 | + </form> |
| 141 | + |
| 142 | +フォームをレンダリングする |
| 143 | +--------------------------- |
| 144 | + |
| 145 | +フォームをレンダリングする方法は、フォーム全体を一行でレンダリングするのから、フィールドの各パーツを個別にレンダリングするのまで、多岐に渡ります。 |
| 146 | +一番良い方法は、アプリケーションでどこまでのカスタマイズが必要かによって異なります。 |
| 147 | + |
| 148 | +一番シンプルな方法(特に開発中に便利な方法)はHTMLのフォームタグを書いてから、 ``form_widget()`` を使って全てのフィールドを一度にレンダリングする方法です。 |
| 149 | + |
| 150 | +.. code-block:: html+jinja |
| 151 | + |
| 152 | + <form method="POST" {{ form_enctype(form) }}> |
| 153 | + {{ form_widget(form) }} |
| 154 | + </form> |
| 155 | + |
| 156 | +.. best-practice:: |
| 157 | + |
| 158 | + フォームの開始タグや終了タグに ``form()`` や ``form_start()`` を使わないようにしましょう。 |
| 159 | + |
| 160 | +Symfony に慣れた開発者なら、 ``<form>`` タグを ``form_start()`` や ``form()`` といったヘルパーを使わずにレンダリングしていることに気づくでしょう。 |
| 161 | +ヘルパーは便利ですが、反面、僅かな利便性と引き換えにわかりやすさを損なっているのです。 |
| 162 | + |
| 163 | +.. tip:: |
| 164 | + |
| 165 | + 削除フォームは例外です。削除フォームはただ一つだけのボタンなので、ショートカットによる利便性を選択しても良いでしょう。 |
| 166 | + |
| 167 | +もしフィールドのレンダリング内容についてもっとコントロールしたければ、 ``form_widget(form)`` を削除してフィールドを個別にレンダリングしたほうが良いでしょう。 |
| 168 | +フィールドを個別にレンダリングする方法の詳細と、フォームテーマを使ってフォームレンダリングをアプリケーション全体でカスタマイズする方法については `How to Customize Form Rendering`_ を参考にしてください。 |
| 169 | + |
| 170 | +フォーム送信を扱う |
| 171 | +--------------------- |
| 172 | + |
| 173 | +フォーム送信を受け取る処理は、大抵の場合、一種の定型文になります。 |
| 174 | + |
| 175 | +.. code-block:: php |
| 176 | +
|
| 177 | + public function newAction(Request $request) |
| 178 | + { |
| 179 | + // フォームを構築 ... |
| 180 | +
|
| 181 | + $form->handleRequest($request); |
| 182 | +
|
| 183 | + if ($form->isSubmitted() && $form->isValid()) { |
| 184 | + $em = $this->getDoctrine()->getManager(); |
| 185 | + $em->persist($post); |
| 186 | + $em->flush(); |
| 187 | +
|
| 188 | + return $this->redirect($this->generateUrl( |
| 189 | + 'admin_post_show', |
| 190 | + array('id' => $post->getId()) |
| 191 | + )); |
| 192 | + } |
| 193 | +
|
| 194 | + // テンプレートをレンダリング |
| 195 | + } |
| 196 | +
|
| 197 | +大事なことは2つだけです。まず、フォームのレンダリングとフォーム送信の受付に一つのアクションを使いましょう。 |
| 198 | +例えば、フォームのレンダリング *だけ* を行う ``newAction`` とフォーム送信の受付だけを行う ``createAction`` を両方作ったとします。この2つのアクションはほとんど同じ内容になるでしょう。ならば、 ``newAction`` に全てを処理させるほうがもっとシンプルになります。 |
| 199 | + |
| 200 | +次に、わかりやすくするために ``if`` 条件文に ``$form->isSubmitted()`` を使いましょう。 |
| 201 | +``isValid()`` は内部的に最初に ``isSubmitted()`` を呼び出しているので、技術的には必要のないことです。しかし、これがなければ、一見してフォーム送信を受け付ける処理が *常に* 行われるように(GETリクエストの時であっても)見えてしまい、処理の流れが読み取りにくくなってしまうのです。 |
| 202 | + |
| 203 | +.. _`フォームをサービスとして登録する`: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service |
| 204 | +.. _`collection`: http://symfony.com/doc/current/reference/forms/types/collection.html |
| 205 | +.. _`How to Customize Form Rendering`: http://symfony.com/doc/current/cookbook/form/form_customization.html |
| 206 | +.. _`form event system`: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html |
0 commit comments