本文目录导读:

我来详细介绍 Symfony Form 组件和自定义表单类型的使用。
基础表单类型
常用内置类型
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\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
// 基本表单构建
$form = $this->createFormBuilder()
->add('name', TextType::class, [
'label' => '用户名',
'attr' => ['class' => 'form-control'],
'required' => true
])
->add('email', EmailType::class)
->add('password', PasswordType::class)
->add('gender', ChoiceType::class, [
'choices' => [
'男' => 'male',
'女' => 'female'
]
])
->add('birthday', DateType::class, [
'widget' => 'single_text'
])
->add('avatar', FileType::class)
->add('submit', SubmitType::class, ['label' => '提交'])
->getForm();
自定义表单类型
创建自定义类型类
// src/Form/Type/AddressType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('street', TextType::class, [
'label' => '街道'
])
->add('city', TextType::class, [
'label' => '城市'
])
->add('province', ChoiceType::class, [
'label' => '省份',
'choices' => [
'北京' => 'beijing',
'上海' => 'shanghai',
'广东' => 'guangdong',
]
])
->add('postalCode', TextType::class, [
'label' => '邮政编码'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null, // 可绑定实体类
'label' => false
]);
}
}
复杂自定义类型
// src/Form/Type/ContactInfoType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
class ContactInfoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('emails', CollectionType::class, [
'entry_type' => EmailType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'entry_options' => [
'attr' => ['class' => 'email-input'],
'constraints' => [
new Assert\Email()
]
]
])
->add('phones', CollectionType::class, [
'entry_type' => TelType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true
]);
}
}
数据绑定自定义类型
创建DTO类
// src/DTO/UserRegistrationDTO.php
namespace App\DTO;
use Symfony\Component\Validator\Constraints as Assert;
class UserRegistrationDTO
{
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 50)]
private string $username;
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
#[Assert\NotBlank]
#[Assert\Length(min: 6)]
private string $password;
private AddressDTO $address;
// Getters and Setters
public function getUsername(): string
{
return $this->username;
}
public function setUsername(string $username): void
{
$this->username = $username;
}
// ... 其他getters/setters
}
绑定DTO到表单
// src/Form/Type/UserRegistrationType.php
namespace App\Form\Type;
use App\DTO\UserRegistrationDTO;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserRegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username', TextType::class, [
'label' => '用户名'
])
->add('email', EmailType::class)
->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => ['label' => '密码'],
'second_options' => ['label' => '确认密码'],
'invalid_message' => '密码不匹配'
])
->add('address', AddressType::class, [
'label' => '地址信息'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => UserRegistrationDTO::class,
'validation_groups' => ['Default', 'registration']
]);
}
}
扩展内置类型
创建更具体类型的扩展
// src/Form/Type/PhoneNumberType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PhoneNumberType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'country_code' => '+86',
'attr' => [
'pattern' => '[0-9]{11}',
'maxlength' => 11
]
]);
$resolver->setAllowedTypes('country_code', 'string');
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['country_code'] = $options['country_code'];
}
public function getParent(): string
{
return TextType::class;
}
}
带AJAX的自定义类型
// src/Form/Type/AjaxSelectType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AjaxSelectType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'ajax_url' => null,
'placeholder' => '请选择...',
'multiple' => false
]);
$resolver->setRequired(['ajax_url']);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['ajax_url'] = $options['ajax_url'];
$view->vars['placeholder'] = $options['placeholder'];
$view->vars['multiple'] = $options['multiple'];
}
public function getParent(): string
{
return HiddenType::class;
}
public function getBlockPrefix(): string
{
return 'ajax_select';
}
}
表单主题与渲染
Twig模板主题
{# templates/form/fields.html.twig #}
{% block ajax_select_widget %}
<div class="ajax-select" data-url="{{ ajax_url }}">
<select
id="{{ id }}"
name="{{ full_name }}"
{% if multiple %}multiple="multiple"{% endif %}
class="ajax-select-input"
data-placeholder="{{ placeholder }}">
<option value="{{ value }}">{{ value_label|default(placeholder) }}</option>
</select>
</div>
{% endblock %}
{% block phone_number_widget %}
<div class="input-group">
<span class="input-group-addon">{{ country_code }}</span>
<input
type="tel"
id="{{ id }}"
name="{{ full_name }}"
value="{{ value }}"
class="phone-input"
{{ block('widget_attributes') }}
/>
</div>
{% endblock %}
注册自定义渲染
# config/packages/twig.yaml
twig:
form_themes:
- 'form/fields.html.twig'
实际使用示例
Controller中使用
// src/Controller/RegistrationController.php
namespace App\Controller;
use App\DTO\UserRegistrationDTO;
use App\Form\Type\UserRegistrationType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class RegistrationController extends AbstractController
{
public function register(Request $request): Response
{
$userDTO = new UserRegistrationDTO();
$form = $this->createForm(UserRegistrationType::class, $userDTO);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 处理注册逻辑
$userData = $form->getData();
// 获取地址数据
$address = $userData->getAddress();
// 保存到数据库等操作
// $this->userService->register($userData);
$this->addFlash('success', '注册成功!');
return $this->redirectToRoute('home');
}
return $this->render('registration/register.html.twig', [
'form' => $form->createView()
]);
}
}
Twig模板
{# templates/registration/register.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-5">
<h1>用户注册</h1>
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.username) }}
{{ form_row(form.email) }}
</div>
<div class="col-md-6">
{{ form_row(form.password.first) }}
{{ form_row(form.password.second) }}
</div>
</div>
<div class="address-section">
<h3>地址信息</h3>
{{ form_row(form.address.street) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.address.city) }}
</div>
<div class="col-md-6">
{{ form_row(form.address.province) }}
</div>
</div>
{{ form_row(form.address.postalCode) }}
</div>
{{ form_end(form) }}
</div>
{% endblock %}
高级功能
事件监听器
// src/Form/EventListener/AddCityFieldListener.php
namespace App\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AddCityFieldListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit',
];
}
public function onPreSetData(FormEvent $event): void
{
$form = $event->getForm();
$data = $event->getData();
$province = $data ? $data->getProvince() : null;
$this->addCityField($form, $province);
}
public function onPreSubmit(FormEvent $event): void
{
$form = $event->getForm();
$data = $event->getData();
$province = $data['province'] ?? null;
$this->addCityField($form, $province);
}
private function addCityField($form, $province): void
{
$cities = $this->getCitiesForProvince($province);
$form->add('city', ChoiceType::class, [
'choices' => $cities,
'placeholder' => '请选择城市'
]);
}
}
这些示例展示了 Symfony Form 组件和自定义类型的各种用法,你可以根据具体需求进行调整和扩展。