PHP项目Symfony form与主题

wen PHP项目 1

本文目录导读:

PHP项目Symfony form与主题

  1. Symfony表单基础
  2. 表单主题定制
  3. 常用表单主题示例
  4. 最佳实践

我来详细介绍Symfony框架中的表单系统与主题定制。

Symfony表单基础

创建表单

// src/Form/ProductType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class, [
                'label' => '产品名称',
                'attr' => ['class' => 'form-control']
            ])
            ->add('price', NumberType::class, [
                'label' => '价格',
                'attr' => ['class' => 'form-control']
            ])
            ->add('save', SubmitType::class, [
                'label' => '保存',
                'attr' => ['class' => 'btn btn-primary']
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    }
}

控制器中使用

// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use App\Form\ProductType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ProductController extends AbstractController
{
    #[Route('/product/new', name: 'product_new')]
    public function new(Request $request, EntityManagerInterface $entityManager): Response
    {
        $product = new Product();
        $form = $this->createForm(ProductType::class, $product);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->persist($product);
            $entityManager->flush();
            return $this->redirectToRoute('product_success');
        }
        return $this->render('product/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

表单主题定制

全局主题配置

# config/packages/twig.yaml
twig:
    form_themes:
        - 'bootstrap_5_layout.html.twig'  # Bootstrap 5主题
        - 'tailwind_2_layout.html.twig'   # Tailwind CSS主题
        - 'form/my_custom_theme.html.twig' # 自定义主题
        - 'foundation_5_layout.html.twig'  # Foundation CSS主题

内置主题选项

# config/packages/framework.yaml
framework:
    form:
        legacy_error_messages: false
        csrf_protection: true

自定义主题模板

{# templates/form/my_custom_theme.html.twig #}
{% block form_row %}
    <div class="custom-form-group">
        {{ form_label(form) }}
        {{ form_widget(form) }}
        {{ form_errors(form) }}
    </div>
{% endblock %}
{% block form_label %}
    {% if label is not same as(false) %}
        <label class="custom-label">
            {{ label }}
            {% if required %}
                <span class="required">*</span>
            {% endif %}
        </label>
    {% endif %}
{% endblock %}
{% block form_errors %}
    {% if errors|length > 0 %}
        <div class="custom-error">
            <ul>
                {% for error in errors %}
                    <li>{{ error.message }}</li>
                {% endfor %}
            </ul>
        </div>
    {% endif %}
{% endblock %}

每个表单单独使用主题

// 控制器中
$form = $this->createForm(ProductType::class, $product, [
    'theme' => 'form/special_theme.html.twig'
]);
{# 模板中指定主题 #}
{% form_theme form 'form/special_theme.html.twig' %}
{# 或多个主题 #}
{% form_theme form with ['form/theme1.html.twig', 'form/theme2.html.twig'] %}

字段级别主题覆盖

{# 为特定字段自定义 #}
{% block _product_name_widget %}
    <div class="input-group">
        <span class="input-group-text">📦</span>
        {{ parent() }}
    </div>
{% endblock %}
{% block _product_price_row %}
    <div class="price-field">
        {{ form_label(form) }}
        <div class="input-group">
            <span class="input-group-text">¥</span>
            {{ form_widget(form) }}
        </div>
        {{ form_errors(form) }}
    </div>
{% endblock %}

常用表单主题示例

Bootstrap 5 主题

{# templates/product/form.html.twig #}
{% form_theme form 'bootstrap_5_layout.html.twig' %}
{{ form_start(form) }}
    <div class="row">
        <div class="col-md-6">
            {{ form_row(form.name) }}
        </div>
        <div class="col-md-6">
            {{ form_row(form.price) }}
        </div>
    </div>
    <div class="row mt-3">
        <div class="col-12">
            {{ form_row(form.description) }}
        </div>
    </div>
    <div class="row mt-4">
        <div class="col-12">
            {{ form_row(form.save, {
                'attr': {'class': 'btn btn-success'}
            }) }}
        </div>
    </div>
{{ form_end(form) }}

Tailwind CSS 主题

{# templates/product/tailwind_form.html.twig #}
{% form_theme form 'tailwind_2_layout.html.twig' %}
{{ form_start(form, {'attr': {'class': 'space-y-6'}}) }}
    <div class="grid grid-cols-1 gap-6">
        <div>
            {{ form_label(form.name, null, {
                'label_attr': {'class': 'block text-sm font-medium text-gray-700'}
            }) }}
            {{ form_widget(form.name, {
                'attr': {
                    'class': 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500'
                }
            }) }}
            {{ form_errors(form.name) }}
        </div>
        <div>
            {{ form_label(form.price, null, {
                'label_attr': {'class': 'block text-sm font-medium text-gray-700'}
            }) }}
            {{ form_widget(form.price, {
                'attr': {
                    'class': 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500'
                }
            }) }}
            {{ form_errors(form.price) }}
        </div>
    </div>
    <div class="flex justify-end">
        {{ form_widget(form.save, {
            'attr': {
                'class': 'ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'
            }
        }) }}
    </div>
{{ form_end(form) }}

自定义字段渲染

{# templates/form/custom_fields.html.twig #}
{% block text_widget %}
    <div class="custom-text-input">
        <input type="text" 
               {{ block('widget_attributes') }} 
               value="{{ value }}"
               class="form-input {{ errors|length > 0 ? 'is-invalid' : '' }}"
        />
        {% if help %}
            <small class="form-text text-muted">{{ help }}</small>
        {% endif %}
    </div>
{% endblock %}
{% block choice_widget_collapsed %}
    <div class="custom-select-wrapper">
        <select {{ block('widget_attributes') }}
                class="form-select {{ errors|length > 0 ? 'is-invalid' : '' }}">
            {% if placeholder is not none %}
                <option value="" {{ value is empty ? 'selected' : '' }}>
                    {{ placeholder }}
                </option>
            {% endif %}
            {% for choice in choices %}
                <option value="{{ choice.value }}" 
                        {{ choice.value == value ? 'selected' : '' }}>
                    {{ choice.label }}
                </option>
            {% endfor %}
        </select>
    </div>
{% endblock %}

表单帮助文本和验证

{# 在表单类型中添加帮助文本 #}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder
        ->add('email', EmailType::class, [
            'label' => '电子邮箱',
            'help' => '请输入有效的邮箱地址',
            'attr' => [
                'placeholder' => 'example@domain.com',
                'class' => 'form-control'
            ]
        ])
        ->add('password', RepeatedType::class, [
            'type' => PasswordType::class,
            'invalid_message' => '两次输入的密码不一致',
            'options' => ['attr' => ['class' => 'form-control']],
            'required' => true,
            'first_options'  => ['label' => '密码'],
            'second_options' => ['label' => '确认密码'],
        ]);
}

表单事件和动态修改

// 表单类型中添加事件监听
public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
        $product = $event->getData();
        $form = $event->getForm();
        // 根据产品类型动态添加字段
        if ($product && $product->getType() === '电子') {
            $form->add('warranty', IntegerType::class, [
                'label' => '保修期(月)'
            ]);
        }
    });
}

最佳实践

主题模板组织

templates/
├── form/
│   ├── themes/
│   │   ├── bootstrap_5_custom.html.twig
│   │   ├── tailwind_custom.html.twig
│   │   └── admin_theme.html.twig
│   ├── fields/
│   │   ├── custom_text.html.twig
│   │   └── custom_select.html.twig
│   └── layout.html.twig
└── product/
    └── form.html.twig

性能优化

# config/packages/twig.yaml
twig:
    form_themes: ['bootstrap_5_layout.html.twig']
    cache: '%kernel.cache_dir%/twig'  # 启用Twig缓存

表单主题继承

{# 继承内置主题并扩展 #}
{% extends 'bootstrap_5_layout.html.twig' %}
{% block form_row %}
    <div class="mb-3 custom-row">
        {{ form_label(form) }}
        <div class="input-wrapper">
            {{ form_widget(form) }}
            {{ form_errors(form) }}
        </div>
    </div>
{% endblock %}

这些方法可以帮助你创建美观、一致的表单界面,同时保持代码的可维护性,选择合适的主题系统可以显著提升开发效率和用户体验。

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