PHP项目Symfony form与自定义类型

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与自定义类型

  1. 基础表单类型
  2. 自定义表单类型
  3. 数据绑定自定义类型
  4. 扩展内置类型
  5. 表单主题与渲染
  6. 实际使用示例
  7. 高级功能

我来详细介绍 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 组件和自定义类型的各种用法,你可以根据具体需求进行调整和扩展。

抱歉,评论功能暂时关闭!