フォーム
フォームの扱いは、私のようにフレームワークに不慣れな人間からするとだいぶこみ入っている。
ただ、きちんと把握すれば、データベースからフロントエンドまできちんと繋がり、ハンドリングにも手間がない、はずだ。ただECCUBE3ではかなりフォームやEntityの構成が複雑なので把握しづらい。
ER図とかを熟読した方が早いのかもしんない。
目次
- フォーム
- フォームのオプション
- Twigでのフォームオプション指定
- name 属性の指定が効かない
- 拡張したのにformに追加されない
- 住所のaddr01だけ保存されない
- 既存のFormTypeを編集したり、要素を追加、削除したりする
- 他のメソッド
- 参考記事
Requirements
- [EC-CUBE] EC-CUBE3.0.16
全体像を俯瞰する
Symfony2 のフォームは、以下の要素が繋がっている。
- データベース
- Entity
- FormType, FormTypeExtension
- FormBuilder
- Form
- FormView
- Twigテンプレート
データベースやコアコードに手を加えずに、プラグインから干渉しようとする場合、新規ならForm、修正ならFormTypeExtensionより下の要素が対象になる。
フォーム生成の基本
// FormFactoryからFormTypeを指定してFormBuilderを生成する $builder = $app['form.factory']->createBuilder('some_form_type'); //FormBuilderにさらにフォームを追加する $builder ->add('','',array( 'required' => true, 'attr' => array( 'placeholder' => 'type something here' ), 'mapped' => true, 'label' => 'some_label', 'constraints' => array( new Assert\Length(array('max' => 20)), ), ) )//セミコロンなしで次に続ける ->add('', '', array( 'required' => false, ), ));//最後はセミコロン必須 $form = $builder->getForm(); $view = $form->createView(); //FormViewをtwigテンプレートに渡す return $this->render('something.twig', [ 'form' => $view, ]);
FormFactoryからFormBuilderを生成する。
FormBuilderで、FormType
にフィールドを追加したり加工したりする
add()
で追加・上書き修正。remove()
で削除。
FormBuilderからFormを生成する
Formは3形態のデータを持っている。model data、normalized data、そしてview dataだ。Symfony\Component\Form\Form | Symfony APIにこの3つが存在する理由について説明がある。
To implement your own form fields, you need to have a thorough understanding of the data flow within a form. A form stores its data in three different representations: (1) the "model" format required by the form's object (2) the "normalized" format for internal processing (3) the "view" format used for display
FormからFormViewを生成する
FormViewをrender()
でtwigに渡すことでform表示が可能になる。
フォームからのデータ受け取り
public function index(Request $request) { $builder = $app['form.factory']->createBuilder('some_form_type'); $form = $builder->getForm(); //ここからがデータ受け取りの処理 $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $data = $form->getData(); //Entity に紐付けされていればここで一時データ更新 $app->persist($SomeEntity); //データベース書き込み $app->flush(); } }
Form のaction
属性で指定されたrouteに向けてPOSTされたデータは、routeの処理(Eccube/ControllerProvider
など)を通って特定のControllerで処理される。ECCUBE3の場合、自分自身にPOSTしてその中で受け取っている場合が多い。Symfony2のドキュメントが挙げているサンプルでもそんな構成になっているようだ。
How to Use a Form without a Data Class (Symfony Docs)
$form->handleRequest($request)
でrequestに含まれるデータを処理する。
Inspects the given request and calls {@link submit()} if the form was submitted. Internally, the request is forwarded to the configured {@link RequestHandlerInterface} instance, which determines whether to submit the form or not.
if ($form->isSubmitted() && $form->isValid())
でフォームの整合性を確認。getData()
でPOSTされたデータを取得する。
Entityとのひもづけ
上記のページの「Setting the data class」の囲みに、OptionsResolverを使って、Entityとの紐付けを行う方法が紹介されている。
この場合、Entityの値とformの内容が一致しないといけないので、Controllerなどで後から余計なフォームを追加すると例外を投げられてしまう。
これを避けるにはadd()
のオプションで array('mapped' => false)
を指定する。'mapped' => false
になっている場合、$form->get('some_form_name')->setData()
を用いて、値を変更することもできる。
フォームのオプション
FormBuilder にadd()
する際一緒に指定できるオプションが、いろいろ用意されている。
'required', 'attr' => array( 'placeholder' => 'Name01', ), 'empty_value'
一覧はこちら。
よく使うものについては、以下で一言解説。
constraints
さまざまな基準で入力値をチェックすることができる。
オプションのrequired
とは別にNotBlank
のconstraintsがかかっていると、結局requiredにしているのと大差ないので、既存のformを変更する場合にはconstraintsもチェックした方がよい。
data
「オブジェクトに関連付けられたデータと無関係に、別のデータを表示したい時に指定する。」というようなことがSymfony2のドキュメントにある。
empty_data
選択式のinput要素に対して、選択前の状態を指定する。
mapped
Entityオブジェクトに関連付けられたformの場合、無関係のformを勝手に追加すると「こんなプロパティはない」と怒られる。mappedをfalseにしておくことで、Entityオブジェクトとの関連付けに例外を設けることができる。
required
入力必須項目に指定する。
attr
Twig テンプレートがフォームのHTMLを生成する際に属性を追加できる。HTMLの属性を参照。
ただしrequired
などにattr
の外に別途指定できるようになっているものもあるので混同しないよう。attrの中に入れてもたぶんレンダリングの結果は同じになるかもしれない。
class
を指定する時に一番使うかも。
label
form_label()
に表示する文字列を指定する。
repeatedグループ
確認のために2回入力をさせるような場合に使う。これが仕組みがわかるまではコントロールできず弱った。
Instead of code with {{ form_widget(form.plainPassword }} Just change by {{ form_widget(form.plainPassword.first }} {{ form_widget(form.plainPassword.second }\} php - How to not display label from password field in Twig - Stack Overflow
という書き方もあるし、こういう書き方もあるみたい。ECCUBE3のテンプレートにあった。
{% for emailField in form.email %} <div class="form-group {% if emailField.vars.errors is not empty %}has-error{% endif %}"> {{ form_widget(emailField,{'attr':{'class':"foobar"}}) }} {{ form_errors(emailField) }} </div> {% endfor %}
つまり、Repeatedグループのform.email
の中にはemailField
が2つ含まれているので、forで回して処理をしていく、ということらしい。うっかりEmailfield.first
とやってみたら「そんなプロパティはない」とエラーになる。それはそうだ。email.first
(=email
の中の最初の要素)は指定できるが、Emailfield.first
はできない。
{% for emailField in form.email %} <div class="form-group {% if emailField.vars.errors is not empty %}has-error{% endif %}"> {% if loop.index == 1 %} {{ form_widget(emailField,{'attr':{'class':"foobar1"}}) }} {% elseif loop.index == 2 %} {{ form_widget(emailField,{'attr':{'class':"foobar2"}}) }} {% else %} {% endif %} {{ form_errors(emailField) }} </div> {% endfor %}
choice_attr
選択肢をオプションで決める場合に。
choice Field Type (select drop-downs, radio buttons & checkboxes) (Symfony 2.7 Docs)
expanded, multiple
この2つのオプションによって、HTMLにレンダリングされた時にselectになるか、ラジオボタンになるか、など要素名が決まる模様。
Twigでのフォームオプション指定
form_widget()
内で指定する。
{{ form_widget(emailField,{'attr':{'class':"someClass thatClass", 'placeholder':"E-mail"}}) }}
実際、PHP側でコントロールするのが分かりづらい部分もあるので、Twigで制御するとだいたい確実ではある。
name 属性の指定が効かない
通常は、attrオプションによってHTMLにレンダリングされた際のformの属性値を変更することができる。class属性やid属性はこれでいい。
だがname属性は注意が必要。以下のコードを書いたとする。
$builder = $app['form.factory']->createBuilder(); $form = $builder ->add('ShopID', 'text', array('attr' => array('name'=>'ShopID'))); return $app ->render($app->render('index.twig', array( 'form'=>$form, ));
attrにnameを指定したのだからレンダリングされたフォームはそのname属性を持っているはずなのだが、結果、name属性はform[ShopID]
となっていた。勝手にform[]
が付加されて外せなかった。
確認したわけではないが、renderに渡した時の名前がここに入ってくるのかもしれない。どうやらSynfony2の仕様らしい。(それともform_themeとかいうやつ?)
たしかにECCUBE3の中で処理する限り、共通の変数配列の中に置いておけばハンドリングしやすい。それはわかるが、外部のAPIなどに値をPostしたいときにこれでは困る。
Synfony2の情報を探ったところ、やや裏技っぽいのがあった。createNamedBuilder
をnull
で呼ぶという。
Change input name of form fields in Symfony2
$builder = $app['form.factory']->createNamedBuilder(null); $form = $builder ->add('ShopID', 'text', array('attr' => array('name'=>'ShopID'))); return $app ->render($app->render('index.twig', array( 'form'=>$form, ));
この場合、name属性はShopID
となる。
拡張したのにformに追加されない
デバッグモードのSynfonyProfilerにフォームの項目があるので、ここを見るとフォームが追加されたかどうかよくわかる。
serviceProvider
に登録し忘れている
FormExtension
はServiceProvider
に登録する必要がある。忘れていると追加されない。
住所のaddr01
だけ保存されない
これは手強かった。なぜかaddr01
だけが保存されず、addr02
は保存されていた。
結論だけ言うと、FormTypeExtension
で拡張したaddr01
と、イベントフックで拡張したaddr01
が上書きしあっていた。
わかってしまえば単純なミスだが、挙動からこれを見つけるのはけっこう難しい。addr01
フォームから受けたデータはFormTypeExtension
が受け取っていたが、その後にイベントフックしたaddr01
が上書きされてnull
になってしまっていた。
当然、イベントフックで拡張した方を削除すれば無事反映されるようになった。
既存のFormTypeを編集したり、要素を追加、削除したりする
ECCUBE3 の初心者が戸惑う箇所の一つは、既存のFormTypeを編集する方法ではないかと思う。私はこのやり方を把握するまでに時間がかかった。
EC-CUBE本体で定義されているFormTypeを拡張するには、フックポイントを利用する方法と、Form Extensionを利用する方法の2種類があります。 プラグインによるフォームの追加、変更 | EC-CUBE 開発ドキュメント
FormExtension
EC-CUBE3.0.8まではFormExtensionというイベントを使ってFormに対して拡張を行えましたが、3.0.9からは新たなイベントが用意されたため、そのイベントでフォームの拡張を行います。 開発ドキュメント(https://doc.ec-cube.net/plugin_bp_form)
この日本語の書き方だと「3.0.9以降は非標準的」という位置づけに読めるが、既存のフォームに何か追加、削除、上書きするならFormTypeExtension
をまず、一番に検討すべきだ。ECCUBE3のfax項目(TelType
の亜種)のような複雑なものでなければ、FormTypeExtension
で対応できる。
作成手順
function buildForm()
に追加や削除を指定する。
function buildForm(){ $builder->remove('foo'); $builder->add('bar', 'text', array()); }
getExtendedType()
に、拡張したいformの名前(該当するFormType
のgetName()
がreturnしてくる値)を指定する。OrderType
を拡張したいなら'order'。
function getExtendedType(){ return 'order'; }
拡張したformの値をデータベースに保存するには
$this->app['orm.em']->persist($some_entity); $this->app['orm.em']->flush($some_entity);
する必要がある。逆にこれをやらないと値が保存されないので注意。
イベントフックで拡張する
局所的に追加する場合は、イベントフックで拡張する。
FormEventで変更する
ものによっては、FormEventをフックして変更する必要がある。
FormEvents::POST_SET_DATAで入れ替えるのが妥当なようです。 EC-CUBE 開発コミュニティ - フォーラム
他のメソッド
buildForm : createBuilder が呼ばれたタイミングで実行される buildView : createView が呼ばれたタイミングで実行される
【EC-CUBE】プラグインでDBの値をtwigに渡す方法 - Qiita
参考記事
- RepeatedType Field (Symfony Docs)
- repeated フィールドタイプ | Symfony2日本語ドキュメント
- EmailType Field (Symfony Docs)
- php - How to not display label from password field in Twig - Stack Overflow
- EC-CUBE 開発コミュニティ - フォーラム
- choice Field Type (select drop-downs, radio buttons & checkboxes) (Symfony 2.7 Docs)
- 【EC-CUBE】プラグインでDBの値をtwigに渡す方法 - Qiita