.. 2011/08/07 fivestar abc6ecf3 .. index:: single: フォーム フォーム ======== HTML のフォームを扱うことは多くの Web アプリケーションで行うことですが、考慮することが多く扱いが難しい部分の1つです。 Symfony2 の Form コンポーネントを活用すると、フォームを簡単に扱うことができるようになります。 本章では、複雑なフォームを1から作成を行う過程を通じて、フォームライブラリの重要な機能について学びます。 .. note:: Symfony の Form コンポーネントは単独で利用できるように設計されています。 コンポーネントについて詳しく知りたい方は、Github の `Symfony2 Form Component`_ を見てください。 .. index:: single: フォーム; シンプルなフォームの作成 シンプルなフォームの作成 ------------------------ フォームを解説するために、商品を表示する簡単なストアアプリケーションを作成していきましょう。 このアプリケーションには、ユーザーが商品の作成や編集を行うためのフォームを作成する必要があります。 フォームを作成するに当たって、まずは商品情報を表す ``Product`` クラスを作成します。 .. code-block:: php // src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity; class Product { public $name; protected $price; public function getPrice() { return $this->price; } public function setPrice($price) { $this->price = $price; } } .. note:: サンプルコードを入力しながら進めていく場合は、``AcmeStoreBundle`` というバンドルを作成して、そこにファイルを作成するようにしましょう。 次のコマンドを実行するとバンドルが作成されます。 .. code-block:: bash php app/console generate:bundle --namespace=Acme/StoreBundle このようなクラスは Symfony などのライブラリを必要としないため、一般的に「プレーンオールド PHP オブジェクト」と呼ばれます。 ストアアプリケーションにおける商品などのように、*あなたが作ろうとしている* アプリケーションに実装しなければならない本質的な問題を解決するための、とてもシンプルな PHP オブジェクトです。 本章の最後までに、フォームを通じて送信されたデータを ``Product`` オブジェクトに反映し、検証を行い、データベースに保存することまで行います。 ここまでで、アプリケーションの実装の中心となるシンプルな PHP クラスを作成しましたが、フォームに関連する作業は何も行っていません。 本章における最終的な目標は、ユーザーがフォームを通じて ``Product`` オブジェクトをインタラクティブに操作できるようになることです。 .. index:: single: フォーム; コントローラでフォームを作成する フォームを組み立てる ~~~~~~~~~~~~~~~~~~~~ ``Product`` クラスを作成したので、次は実際にフォームをレンダリングを行います。 Symfony2 では、フォームオブジェクトを組み立て、それをテンプレート内でレンダリングします。 まずはこれらの処理をコントローラに記述していきます。 .. code-block:: php // src/Acme/StoreBundle/Controller/DefaultController.php namespace Acme\StoreBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Acme\StoreBundle\Entity\Product; use Symfony\Component\HttpFoundation\Request; class DefaultController extends Controller { public function indexAction(Request $request) { // Productオブジェクトを作成し、ダミーデータを設定する $product = new Product(); $product->name = 'Test product'; $product->setPrice('50.00'); $form = $this->createFormBuilder($product) ->add('name', 'text') ->add('price', 'money', array('currency' => 'USD')) ->getForm(); return $this->render('AcmeStoreBundle:Default:index.html.twig', array( 'form' => $form->createView(), )); } } .. tip:: 今回のサンプルではコントローラの内部で直接フォームを組み立てています。 この後、「:ref:`book-form-creating-form-classes`」節で、この組み立て処理を単独のクラスとして定義するようにします。 再利用性のため、クラスとして定義する方法をおすすめします。 Symfony2では「フォームビルダー」を用いてフォームオブジェクトの組み立てるため、とても簡単にフォームが作成できます。 フォームビルダーはフォームの組み立てを簡単に行うために用意されたオブジェクトです。 今回のサンプルでは、フォームには2つのフィールドを定義します。 定義するフィールドは ``name`` と ``price`` の2つで、これらはそれぞれ ``Product`` クラスの ``name`` と ``price`` プロパティに対応します。 ``name`` フィールドは ``text`` というタイプが指定されており、このフィールドがテキストフィールドとして扱われることを意味します。 ``price`` フィールドには ``money`` タイプが指定されており、これはローカライズされた金額が表示される特別なテキストフィールドとなります。 Symfony2 には様々なタイプが組み込まれており、リファレンスページ(:ref:`book-forms-type-reference`)にそれぞれの説明が記載してあります。 フォームオブジェクトが作成できたので、次はこれをレンダリングします。 レンダリングのために用意されたフォーム「ビュー」オブジェクトをテンプレートに渡し(前述のコントローラの ``$form->createView()`` の部分)、フォーム用のヘルパー関数にセットすることで行います。 .. configuration-block:: .. code-block:: html+jinja {# src/Acme/StoreBundle/Resources/views/Default/index.html.twig #}
.. code-block:: html+php .. image:: /book/images/forms-simple.png :align: center .. note:: この例では ``AcmeStoreBundle:Default:index`` コントローラに ``store_product`` というルーティングでアクセスできるように設定してあるものとしています。 これでレンダリングができました。 ``form_widget(form)`` によってそれぞれのフィールド、ラベルのレンダリングが行われ、フォームに関するエラーメッセージもレンダリングされるようになりました。 実際に開発を行う際は、フォームの配置を整えるため、フィールドごとにレンダリングを行いたいでしょう。 それについては「 :ref:`form-rendering-template` 」を参考にしてください。 次に進む前に、レンダリングされた ``name`` テキストフィールドの値に ``$product`` の ``name`` プロパティに設定した値(ここでは "Test product")が設定されていることに注目してください。 このように、オブジェクトに設定されたデータをフィールドに合わせて変換し、HTMLのフォームに適切に反映することが、フォームが行う1つめの仕事です。 .. tip:: フォームの仕組みはとてもよくできており、 ``Product`` の ``price`` プロパティのようにprotectedで定義されたプロパティの場合、``getPrice()`` や ``setPrice()`` メソッドを通じてプロパティにアクセスを行います。 プロパティがpublicでない場合、フォームがプロパティにアクセスできるよう *必ず* ゲッターとセッターメソッドを定義しておかなければなりません。 Boolean型のプロパティの場合、ゲッターの代わりに「is」ではじまるメソッド(たとえば ``getPublished()`` の代わりに ``isPublished()``)を用いても構いません。 .. index:: single: フォーム; フォームに送信されたデータを処理する フォームに送信されたデータを処理する ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ フォームの2つめの仕事は、ユーザーから送信されたデータをオブジェクトのプロパティに設定することです。 それを行うために、ユーザーから送信されたデータをフォームに結びつける(バインドする)必要があります。 コントローラに次のコードを記述してください。 .. code-block:: php use Symfony\Component\HttpFoundation\Request; // ... public function indexAction(Request $request) { // 新規に $product オブジェクトを作成(ダミーデータは入れない) $product = new Product(); $form = $this->createFormBuilder($product) ->add('name', 'text') ->add('price', 'money', array('currency' => 'USD')) ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // データベースへの保存など、何らかのアクションを実行する return $this->redirect($this->generateUrl('store_product_success')); } } // ... } これで、フォームから送信が行われたときに、コントローラが送信されたデータをフォームにバインドし、それらのデータが ``$product`` オブジェクトの ``name`` や ``price`` といったプロパティに設定されます。 この一連の処理は ``bindRequest()`` メソッドを通じて行われます。 .. note:: ``bindRequest()`` が呼び出された時点で、送信されたデータはフォームの内部に持つオブジェクトに反映されます。 その際、データの妥当性に問わず、オブジェクトに反映されます。 今回実装したコントローラでは、フォームを操作する際に用いる一般的なパターンに乗っ取り、次の3つのことを行います。 #. 最初にページがロードされる時は、``GET`` リクエストメソッドによってアクセスされ、コントローラはフォームの生成とレンダリングのみを行います(バインドは行いません)。 #. ユーザーが``POST`` メソッドを用いてフォームの送信を行った時、その中に妥当でない値を含んでいる場合(妥当性の検証(バリデーション)は次節で説明します)、バインドされたデータをレンダリングする際に、バリデーションエラーメッセージを含んだ状態でレンダリングが行われます。 #. ユーザーが妥当なデータを送信した時、データベースへの保存など ``$product`` に対する何らかのアクションを行い、登録完了画面などへリダイレクトします。 .. note:: ユーザーがフォームを送信して何か操作を行った後にリダイレクトを行うことは、ユーザーがページを再読込みしてフォームが再送信されることを防ぐ意味を持っています。 .. index:: single: フォーム; バリデーション フォームバリデーション ---------------------- 前節で、フォームから送信されたデータに対するバリデーションについて触れました。 Symfony2では、バリデーションはフォームが保持しているオブジェクト(今回は ``Product``)に対して行われます。 つまり、フォームから送信された値が妥当かどうかではなく、その値が反映された ``$product`` オブジェクトが妥当かどうかを判定します。 ``$form->isValid()`` は ``$product`` オブジェクトが妥当かどうかを判定するショートカットとなります。 バリデーションを行うためには、クラスに対してルールのセット(これを「制約」と呼んでいます)を設定します。 それでは、``name`` フィールドには空以外の値を、``price`` フィールドには空以外で、かつ0以上の数のみを受け付けるよう、制約を設定します。 .. configuration-block:: .. code-block:: yaml # Acme/StoreBundle/Resources/config/validation.yml Acme\StoreBundle\Entity\Product: properties: name: - NotBlank: ~ price: - NotBlank: ~ - Min: 0 .. code-block:: xml