3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Symfony(EC-CUBE) FormType クラス備忘録

Posted at

この記事について
EC‑CUBE のカスタマイズ案件で Symfony の FormType を触っていると、毎回「あれ、オプション名なんだっけ?」とド忘れして公式ドキュメントを往復する羽目になります
ここでは 未来の自分が迷子にならないように、ポイントだけを抜き出してメモします。同じ悩みを抱える方の時短にもなれば幸いです。

困った時の参照用 を更新していけたらなと思います。

1. FormType クラスとは

Symfony の Form コンポーネント を司る 中心的クラス
AbstractType を継承して作成し、下記を一括で担う

役割 概要 具体例
フィールドツリーの構築 buildForm() 内で add()create() を呼び、
Input・Select など HTML 要素を ツリー状 に組み立てる
TextType, ChoiceType, CollectionType
ビューの生成・調整 buildView() / finishView()
Twig に渡す変数や属性を上書きし、
テーマにフックさせる
すべてのフィールドに data-hint を付与 など
データ変換 & バリデーション設定 configureOptions()
data_class, constraints, validation_groups 等を宣言し、
Model ↔︎ View ↔︎ HTML の相互変換を制御
CallbackTransformer, NotBlank, Length

1‑1. どう呼び出す?

Controller からはたった 1 行:

$form = $this->formFactory->createBuilder(ArticleType::class, $article, []);
  • 第 1 引数: 作成した FormType クラス名
  • 第 2 引数: 初期データとして束ねるエンティティや DTO。null でも可
  • 第 3 引数: (任意)フォーム全体に渡すオプション配列。例:['csrf_protection' => false]

1‑2. FormType ⇄ FormBuilder ⇄ FormInterface の関係

  • FormType … 設計図
  • FormBuilder … パーツを付け外しする作業員
  • FormInterface … 送信・バリデーションを実行する完成品

1‑3. EC‑CUBE でも必須

Symfony ベース であるため
受注・商品登録など管理画面フォームも すべて FormType で組まれている
独自入力欄を追加する場合も、標準の FormType を継承・拡張するのが王道


2. FormType メソッドと FormBuilder API

2-0. FormBuilderInterface ― 機能別メソッド早見表

カテゴリ 代表メソッド 用途
フィールド操作 add() / create() / remove() / get() / has() / all() フィールドを追加・生成・取得・削除
ビルダー生成 getForm() FormInterface を確定生成
データ操作 setData() / getData() 初期データの注入・取得
イベント addEventListener() / addEventSubscriber() FormEvents::* にリスナ/サブスクライバ登録
データ変換 addModelTransformer() / addViewTransformer() / resetViewTransformers() / getModelTransformers() / getViewTransformers() エンティティ⇔フォーム値⇔表示値 の変換カスタム
属性・HTML setAction() / setMethod() / setAttribute() / setAttributes() / getAttribute() / getAttributes() フォームタグの action, method, 任意属性
マッピング制御 setDataMapper() / getDataMapper() / setCompound() / setMapped() / setPropertyPath() / setByReference() / setInheritData() オブジェクト⇔フィールド間の同期方法指定

Tip : これらは すべてチェーン可能なので、$builder->add(...)->addEventListener(...)->setMethod('PUT') のように繋げて書ける


2-1. buildForm()フィールドとイベントの司令塔

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    // 1) フィールドを静的に追加
    $builder
        ->add('title', TextType::class, ['label' => '記事タイトル'])
        ->add('body',  TextareaType::class, ['attr' => ['rows' => 10]]);

    // 2) イベントで動的にフィールドを操作
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void {
        $article = $event->getData();
        $form    = $event->getForm();

        // 新規作成時のみ「下書き保存」チェックボックスを追加
        if (!$article || null === $article->getId()) {
            $form->add('draft', CheckboxType::class, [
                'mapped'   => false,
                'required' => false,
                'label'    => '下書きとして保存'
            ]);
        }
    });

    // 3) モデル⇆ビュー変換を挟む
    $builder->addModelTransformer(new CallbackTransformer(
        fn (?string $v)         => $v ?? '',           // 変換: モデル→フォーム
        fn (?string $submitted) => trim($submitted)    // 逆変換: フォーム→モデル
    ));
}
  • イベント活用例

    • FormEvents::PRE_SET_DATA : 初期データを基にフィールド構成を変更
    • FormEvents::PRE_SUBMIT : リクエスト値に応じた動的バリデーション
    • FormEvents::SUBMIT : バインド直前にデータを正規化・補完

2-2. configureOptions()フォーム外から渡せるスイッチを定義

public function configureOptions(OptionsResolver $resolver): void
{
    $resolver->setDefaults([
        'data_class'       => Article::class,
        'include_cover'    => false,     // ← 独自オプション
        'csrf_protection'  => true,
    ]);

    $resolver->setAllowedTypes('include_cover', 'bool');
}
  • Controller 側

    $form = $this->createForm(ArticleType::class, $article, [
        'include_cover' => true,   // → buildForm() 側の分岐に利用できる
    ]);
    

2-3. buildView()Twig に追加変数を輸送

public function buildView(FormView $view, FormInterface $form, array $options): void
{
    // 独自オプションをそのまま渡す
    $view->vars['include_cover'] = $options['include_cover'];

    // すべての入力に一括で CSS クラスを追加
    $view->vars['attr']['class'] = trim(($view->vars['attr']['class'] ?? '').' card');
}

2-4. finishView()子ビュー確定後の後処理

public function finishView(FormView $view, FormInterface $form, array $options): void
{
    // 奇数行に z-stripe クラスを付与
    $i = 0;
    foreach ($view->children as $child) {
        if (++$i % 2) {
            $child->vars['row_attr']['class'] = 'z-stripe';
        }
    }
}

2-5. getBlockPrefix()Twig ブロック名を一括変更

public function getBlockPrefix(): string
{
    // デフォルトは `article`
    return 'blog_article';
}

Twig 側で blog_article_rowblog_article_widget などのブロックを上書きできる


2-6. getParent()既存 FormType を薄くラップ

class PriorityChoiceType extends AbstractType
{
    public function getParent(): string
    {
        return ChoiceType::class;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'choices'      => ['High' => 'high', 'Medium' => 'medium', 'Low' => 'low'],
            'placeholder'  => '---',
        ]);
    }
}

Controller では ->add('priority', PriorityChoiceType::class) と書くだけで済み、設定漏れを防止


3. 組み込み FormType

Text 系

TextType TextareaType EmailType IntegerType MoneyType NumberType PasswordType PercentType SearchType UrlType RangeType TelType ColorType

Choice 系

ChoiceType EnumType EntityType CountryType LanguageType LocaleType TimezoneType CurrencyType

Date/Time 系

DateType DateIntervalType DateTimeType TimeType BirthdayType WeekType

その他フィールド

CheckboxType FileType RadioType

Symfony UX フィールド

CropperType DropzoneType

UID フィールド

UuidType UlidType

フィールドグループ

CollectionType RepeatedType

Hidden フィールド

HiddenType

ボタン

ButtonType ResetType SubmitType

ベース

FormType


4. FormType が理解する 共通オプション

前提

  • ここで挙げるオプションは すべての FormType が共通で受け付ける 基本パラメータ
  • フィールド固有のもの(choices など)は 個別で要確認(後でまとめたい)

4-1. データ/マッピング系

オプション 役割 & 典型値 よくある使い方
data 初期値(フォームツリー確定前に直接セット) createForm(MyType::class, null, ['data' => ['foo' => 'bar']])
data_class バインド対象エンティティ FQCN Article::class を指定→Article の setter/getter が使われる
empty_data 送信値が空 のときに入れる値
文字列 / クロージャ / 配列
empty_data => '' で必須文字列を空文字に、
fn() => new User() で新オブジェクト生成
property_path エンティティのプロパティ名(ドット区切り可) user.address.street のようにネスト先へマッピング
mapped false にすると 画面 ↔ エンティティ同期しない 検索フォーム・確認用 hidden などに便利
by_reference true(既定) = セッターを呼ばず配列/コレクションを参照渡し コレクション編集時に setter 経由で検証したい場合 false
inherit_data 親のデータオブジェクトを子に共有 サブフォームが別クラスを持たない場合に重宝
compound false にすると単一値扱い 自作ウィジェットで値をまとめて投げるとき

4-2. バリデーション/エラー制御

オプション 役割
constraints NotBlank, Length … Symfony\Validator 制約配列 ['constraints'=>[new NotBlank(), new Length(max:255)]]
validation_groups Default 以外のバリデーショングループを選択 'validation_groups' => ['Registration']
error_bubbling 子のエラーを親に表示 (true) チェックボックスグループなどでまとめてエラー表示
error_mapping エラーを別フィールドに付け替え ['password' => 'plainPassword']
invalid_message(_parameters) 変換失敗時のメッセージと置換変数 日付パース失敗などで独自文言を出す
extra_fields_message 送信に「未知フィールド」があった場合のメッセージ API などで厳格にしたいとき
post_max_size_message PHP アップロードサイズ超過エラー文言 upload_max_filesize 超過時に表示

4-3. 表示/ラベル/HTML 属性

オプション 効果 サンプル
label / label_format ラベル文言 or プレースホルダで自動生成 label_format => 'form.%name%'
label_attr / row_attr <label>/ 行 <div> への任意属性 ['row_attr'=>['class'=>'mb-3']]
label_html / help_html ラベル・ヘルプを HTML 解釈するか Bootstrap アイコンを埋め込みたいとき true
help, help_attr, help_translation_parameters ヘルプテキスト&属性&翻訳変数
attr, attr_translation_parameters ウィジェット直付け属性 attr => ['placeholder'=>'検索…']
block_name, block_prefix, priority Twig ブロック名と描画順制御 高度なテーマカスタムで使用
translation_domain / label_translation_parameters 翻訳ドメイン切替 & プレースホルダ translation_domain=>'admin'

4-4. 動作/リクエスト関連

オプション 目的 サンプル
required HTML5 required 属性生成 + バリデーション必須化 required=>false で任意入力
disabled 入力不可 (<input disabled>) 編集不可フィールドに
trim 文字列トリム API 用フォームで false にする例も
allow_extra_fields 送信に余計なキーがあっても無視 Webhook 受信フォームなど
action, method <form> の送信先 URL と HTTP メソッド $builder->setAction('/search')->setMethod('GET')
csrf_protection CSRF トークン生成有無 API 専用なら false

4-5. コールバック/HTML5 カスタム

オプション 用途 一言
getter / setter Entity プロパティ名をオーバーライド MagicAccessor を使う場合
form_attr <form> タグに付ける属性 form_attr=>['novalidate'=>'novalidate']

4-6. 代表的な組み合わせ例

$builder
    ->add('cover', FileType::class, [
        'label'         => false,
        'mapped'        => false,          // エンティティに自動保存しない
        'required'      => false,
        'help'          => 'jpeg / png, 最大2MB',
        'help_html'     => true,
        'constraints'   => [
            new File(maxSize: '2M', mimeTypes: ['image/jpeg','image/png']),
        ],
        'attr'          => ['accept'=>'image/*'],
    ])
    ->setAction($options['upload_action'])  // configureOptions 側で注入
    ->setMethod('POST');
  • ポイント

    • mapped:false でファイルストレージ処理を自前でハンドリング
    • help_html:true によりヘルプ文に <br><i class="bi-info"> など挿入可能
    • setAction() / setMethod()FormBuilder のメソッド(オプション指定でも可)

困った時の参照用

# 困りごと / シナリオ 対応 ➕ ヒント
1 エンティティに保存せず画面だけで使いたいフィールド mapped:false / property_path:false 検索フォーム・確認チェック用 hidden に最適
2 必須⇆任意を動的に切り替えたい a. $form->get('foo')->setRequired(false)
b. イベントで required オプション変更
PRE_SET_DATA で初期値を見て分岐
3 CSRF で弾かれる API / Webhook 受信用 ルートフォーム csrf_protection:false 併せて allow_extra_fields:true で未知キー無視
4 エラーを 1 か所(フォーム上部)に集約したい ルートに error_bubbling:true 個別配置なら error_mapping
5 i18n:ラベルを翻訳キーで書きたい label_format:'form.%name%' + translation_domain:'admin' %name% がフィールド名に置換
6 CollectionType の add/remove が効かない 子 Type:entry_options['label'=>false] + prototype:true/親:by_reference:false setter 無い時は allow_add, allow_delete
7 アップロード 2 MB 超過時のメッセージ変更 ルート post_max_size_message:'ファイルが大きすぎます' FileTypeconstraints も要追加
8 PUT/DELETE など HTML 非対応メソッド送信 setMethod('POST') + hidden _method EC-CUBE でも同じ(メソッドスプーフ)
9 環境ごとに送信先 URL を動的変更 controller で $form->setAction($url) setAction() は Builder でも可
10 状態別にバリデーショングループ切替 validation_groups => fn(FormInterface $f) => $f->getData()->isDraft() ? ['Draft'] : ['Default'] Closure で柔軟指定
11 row / label へ属性を一括追加 row_attr / label_attr または $builder->setAttribute() テーマが属性を出力しているか確認
12 特殊 getter/setter を使いたい getter:'isPublished', setter:'markPublished' DTO でも活用可
13 空文字を null にしたくない empty_data:'' + trim:false API で “空文字も値” の時
14 フォーム複数描画で ID 衝突が怖い それぞれで getBlockPrefix() を上書き Twig ブロック名も衝突回避
15 動的フィールド追加時にバリデーションが効かない 追加後に setValidationGroups()POST_SUBMIT で再評価 Choice 依存入力で発生しがち
16 フォーム全体を multipart にしたい $builder->setAttribute('enctype','multipart/form-data') FileType があれば自動設定されるが念のため
17 ArrayCollection の追加が永続化されない 親エンティティ addXxx() 実装 + by_reference:false Doctrine の cascade も確認
18 Ajax で部分バリデーションしたい サブフォーム validation_groups:['Partial'] JS で fetch → JSON エラー返却
19 ラベル/ヘルプを HTML 混在にしたい label_html:true, help_html:true アイコンタグや <br> も使える
20 HTML5 のブラウザ自動検証を無効化したい ルート form_attr:['novalidate'=>'novalidate'] JS で独自検証を行うケース
21 複数フィールドをグループ化 (fieldset) したい add('group', FormType::class,['label'=>false]) 内に子フィールド compound:true でネスト可能
22 ChoiceType の表示順を厳密保持したい choice_loader:null + 配列順に choices 指定 or preferred_choices で先頭固定
23 ChoiceType にプレースホルダーが欲しい placeholder:'---- 選択 ----' required:false にしないと選択不可
24 DateType の年範囲を限定したい years: range(date('Y'), date('Y')+5) 期間外日付はバリデーション NG
25 EntityType でソフトデリートを除外したい query_builderWHERE e.deletedAt IS NULL リポジトリで共通メソッドを切ると◎
26 エラーメッセージを別翻訳ドメインにまとめたい translation_domain:'errors' + 各 invalid_message はキーで指定 バリデーションメッセージにも適用
27 FileType を非活性表示 (値保持) したい disabled:true 編集不可モード用
28 2 フィールドを結合して 1 プロパティへ保存 setDataMapper(new CustomMapper()) 例:電話番号 (市外 + 市内)
29 HTML id をカスタムしたい attr:{id:'my_custom_id'} CSS/JS でピンポイント操作
30 DB レコード数に応じてフィールドを動的生成 addEventSubscriber(new DynamicFieldSubscriber($repo)) PRE_SET_DATA でループ追加
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?