表单类型 #

一、内置表单类型 #

1.1 文本类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\Extension\Core\Type\TelType;

$builder
    ->add('name', TextType::class)
    ->add('email', EmailType::class)
    ->add('password', PasswordType::class)
    ->add('bio', TextareaType::class)
    ->add('search', SearchType::class)
    ->add('website', UrlType::class)
    ->add('phone', TelType::class)
;

1.2 数字类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\RangeType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\PercentType;

$builder
    ->add('quantity', IntegerType::class, [
        'attr' => ['min' => 0],
    ])
    ->add('price', NumberType::class, [
        'scale' => 2,
    ])
    ->add('rating', RangeType::class, [
        'attr' => ['min' => 1, 'max' => 5],
    ])
    ->add('amount', MoneyType::class, [
        'currency' => 'CNY',
    ])
    ->add('discount', PercentType::class, [
        'scale' => 2,
    ])
;

1.3 日期时间类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;

$builder
    ->add('eventDate', DateType::class, [
        'widget' => 'single_text',
    ])
    ->add('startTime', DateTimeType::class, [
        'widget' => 'single_text',
    ])
    ->add('openTime', TimeType::class, [
        'widget' => 'choice',
    ])
    ->add('birthday', BirthdayType::class, [
        'widget' => 'single_text',
    ])
;

1.4 选择类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\LocaleType;
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;

$builder
    ->add('status', ChoiceType::class, [
        'choices' => [
            'Active' => 'active',
            'Inactive' => 'inactive',
        ],
    ])
    ->add('country', CountryType::class)
    ->add('currency', CurrencyType::class)
    ->add('language', LanguageType::class)
    ->add('locale', LocaleType::class)
    ->add('timezone', TimezoneType::class)
;

1.5 其他类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\RadioType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;

$builder
    ->add('agreeTerms', CheckboxType::class, [
        'required' => true,
    ])
    ->add('gender', RadioType::class)
    ->add('avatar', FileType::class, [
        'mapped' => false,
    ])
    ->add('token', HiddenType::class)
    ->add('themeColor', ColorType::class)
;

二、实体字段类型 #

2.1 EntityType #

php
<?php

use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;

$builder
    ->add('category', EntityType::class, [
        'class' => Category::class,
        'choice_label' => 'name',
        'placeholder' => '选择分类',
        'required' => true,
    ])
    ->add('tags', EntityType::class, [
        'class' => Tag::class,
        'choice_label' => 'name',
        'multiple' => true,
        'expanded' => true,
    ])
    ->add('author', EntityType::class, [
        'class' => User::class,
        'choice_label' => function (User $user) {
            return $user->getFullName();
        },
        'query_builder' => function (EntityRepository $er) {
            return $er->createQueryBuilder('u')
                ->where('u.active = :active')
                ->setParameter('active', true)
                ->orderBy('u.name', 'ASC');
        },
    ])
;

2.2 EntityType选项 #

php
<?php

$builder->add('product', EntityType::class, [
    'class' => Product::class,
    'choice_label' => 'name',
    'choice_value' => 'id',
    'choice_name' => 'slug',
    'choice_attr' => function (Product $product) {
        return ['data-price' => $product->getPrice()];
    },
    'group_by' => function (Product $product) {
        return $product->getCategory()->getName();
    },
    'preferred_choices' => function (Product $product) {
        return $product->isFeatured();
    },
    'placeholder' => '选择产品',
    'required' => true,
    'multiple' => false,
    'expanded' => false,
]);

三、按钮类型 #

3.1 按钮类型 #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;

$builder
    ->add('save', SubmitType::class, [
        'label' => '保存',
        'attr' => ['class' => 'btn btn-primary'],
    ])
    ->add('saveAndContinue', SubmitType::class, [
        'label' => '保存并继续',
    ])
    ->add('reset', ResetType::class, [
        'label' => '重置',
    ])
    ->add('cancel', ButtonType::class, [
        'label' => '取消',
        'attr' => ['onclick' => 'history.back()'],
    ])
;

3.2 处理多个提交按钮 #

php
<?php

public function edit(Request $request, User $user): Response
{
    $form = $this->createForm(UserType::class, $user);
    $form->add('save', SubmitType::class, ['label' => '保存']);
    $form->add('delete', SubmitType::class, ['label' => '删除']);
    
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        if ($form->get('save')->isClicked()) {
            $this->entityManager->flush();
            $this->addFlash('success', '用户已更新');
        } elseif ($form->get('delete')->isClicked()) {
            $this->entityManager->remove($user);
            $this->entityManager->flush();
            $this->addFlash('success', '用户已删除');
        }
        
        return $this->redirectToRoute('app_user_list');
    }

    return $this->render('user/edit.html.twig', [
        'form' => $form,
    ]);
}

四、表单集合 #

4.1 CollectionType #

php
<?php

use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class OrderType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('items', CollectionType::class, [
                'entry_type' => OrderItemType::class,
                'entry_options' => ['label' => false],
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'prototype' => true,
                'prototype_name' => '__name__',
            ])
        ;
    }
}

4.2 子表单类型 #

php
<?php

class OrderItemType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('product', EntityType::class, [
                'class' => Product::class,
                'choice_label' => 'name',
            ])
            ->add('quantity', IntegerType::class, [
                'attr' => ['min' => 1],
            ])
            ->add('price', NumberType::class, [
                'scale' => 2,
            ])
        ;
    }
}

4.3 渲染集合 #

twig
{{ form_start(form) }}
    <h3>订单项目</h3>
    <ul class="order-items" data-prototype="{{ form_widget(form.items.vars.prototype)|e('html_attr') }}">
        {% for item in form.items %}
            <li>{{ form_row(item) }}</li>
        {% endfor %}
    </ul>
    
    <button type="button" class="add-item-link">添加项目</button>
    
    <button type="submit">保存订单</button>
{{ form_end(form) }}

<script>
document.querySelector('.add-item-link').addEventListener('click', function() {
    const list = document.querySelector('.order-items');
    const prototype = list.dataset.prototype;
    const index = list.children.length;
    const newForm = prototype.replace(/__name__/g, index);
    const li = document.createElement('li');
    li.innerHTML = newForm;
    list.appendChild(li);
});
</script>

五、表单继承 #

5.1 继承表单 #

php
<?php

class BaseUserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name')
            ->add('email')
        ;
    }
}

class AdminUserType extends BaseUserType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        parent::buildForm($builder, $options);
        
        $builder
            ->add('roles', ChoiceType::class, [
                'multiple' => true,
                'choices' => [
                    'Admin' => 'ROLE_ADMIN',
                    'Editor' => 'ROLE_EDITOR',
                ],
            ])
        ;
    }
}

六、自定义表单类型 #

6.1 创建自定义类型 #

php
<?php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TagsType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'attr' => [
                'class' => 'tags-input',
                'data-role' => 'tagsinput',
            ],
        ]);
    }

    public function getParent(): string
    {
        return TextType::class;
    }
}

6.2 注册自定义类型 #

yaml
# config/services.yaml
services:
    App\Form\Type\TagsType:
        tags: ['form.type']

6.3 使用自定义类型 #

php
<?php

use App\Form\Type\TagsType;

$builder->add('tags', TagsType::class);

七、数据转换器 #

7.1 创建数据转换器 #

php
<?php

namespace App\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;

class TagsTransformer implements DataTransformerInterface
{
    public function transform($tags): string
    {
        if (null === $tags) {
            return '';
        }

        return implode(', ', $tags);
    }

    public function reverseTransform($string): array
    {
        if (!$string) {
            return [];
        }

        return array_map('trim', explode(',', $string));
    }
}

7.2 使用数据转换器 #

php
<?php

use App\Form\DataTransformer\TagsTransformer;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('tags', TextType::class)
        ;

        $builder->get('tags')
            ->addModelTransformer(new TagsTransformer());
    }
}

八、表单扩展 #

8.1 创建表单扩展 #

php
<?php

namespace App\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TextTypeExtension extends AbstractTypeExtension
{
    public static function getExtendedTypes(): iterable
    {
        return [TextType::class];
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'attr' => [
                'class' => 'form-control',
            ],
        ]);
    }
}

8.2 注册扩展 #

yaml
# config/services.yaml
services:
    App\Form\Extension\TextTypeExtension:
        tags: ['form.type_extension']

九、总结 #

本章学习了:

  • 内置表单类型
  • EntityType实体字段
  • 按钮类型
  • CollectionType集合
  • 表单继承
  • 自定义表单类型
  • 数据转换器
  • 表单扩展

下一章将学习 表单验证

最后更新:2026-03-28