Form 組件是獨立函式庫,可脫離 Symfony 專案使用。核心流程:
// src/Entity/Task.php
namespace App\Entity;
class Task {
private $task;
private $dueDate;
public function getTask() { return $this->task; }
public function setTask($task) { $this->task = $task; }
public function getDueDate() { return $this->dueDate; }
public function setDueDate(\DateTime $dueDate = null) { $this->dueDate = $dueDate; }
}
use App\Entity\Task;
use Symfony\Component\HttpFoundation\Request;
public function newAction(Request $request) {
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
return $this->render('task/new.html.twig', ['form' => $form->createView()]);
}
{# templates/task/new.html.twig #}
public function newAction(Request $request) {
$task = new Task();
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
$form->handleRequest($request); // 3.4+ 推薦用法,替代 bindRequest()
if ($form->isSubmitted() && $form->isValid()) {
// 持久化、重導向
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
return $this->redirectToRoute('task_success');
}
return $this->render('task/new.html.twig', ['form' => $form->createView()]);
}
三種請求路徑:
驗證作用於底層物件,而非表單本身。$form->isValid() 實際檢查物件是否符合約束。
// src/Entity/Task.php (Annotations)
use Symfony\Component\Validator\Constraints as Assert;
class Task {
/**
* @Assert\NotBlank()
*/
private $task;
/**
* @Assert\NotBlank()
* @Assert\Type("\DateTime")
*/
private $dueDate;
// ... getter/setter
}
或 YAML/XML/PHP 格式配置。
自動為必填欄位加 required、型別對應 type="email/number/url" 等。可用 novalidate 關閉。
| 分類 | 類型 |
|---|---|
| 文字 | text, textarea, email, integer, money, number, password, percent, search, url |
| 選擇 | choice, entity, country, language, locale, timezone |
| 日期時間 | date, datetime, time, birthday |
| 其他 | checkbox, file, radio |
| 群組 | collection, repeated |
| 隱藏 | hidden, csrf |
$builder->add('dueDate', 'date', [
'widget' => 'single_text', // 單一文字框而非三個 select
'label' => '截止日期', // 自定義標籤
'required' => false, // 關閉 required 屬性 (不影響伺服端驗證)
'attr' => ['class' => 'form-control'], // HTML 屬性
]);
注意:required: true (預設) 僅影響 HTML5 客戶端驗證,伺服端仍需 NotBlank 等約束。
若物件已有驗證元資料,省略類型讓 Symfony 猜測:
$form = $this->createFormBuilder($task)
->add('task') // 猜測為 text
->add('dueDate', null, [ // 猜測為 date
'widget' => 'single_text'
])
->getForm();
猜測依據:驗證約束、Doctrine 元資料。可覆蓋猜測選項:'max_length' => 4。
{# 完整控制 #}
{{ form_errors(form) }}
{{ form_label(form.task) }}
{{ form_errors(form.task) }}
{{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}
{{ form_label(form.dueDate, '截止日期') }}
{{ form_errors(form.dueDate) }}
{{ form_widget(form.dueDate) }}
{{ form_rest(form) }} {# 渲染隱藏欄位、CSRF token 等 #}
{{ form.task.vars.id }} {# id 屬性 #}
{{ form.task.vars.full_name }} {# name 屬性 #}
{{ form.task.vars.value }} {# 當前值 #}
{{ form.task.vars.label }} {# 標籤文字 #}
// src/Form/Type/TaskType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('task')
->add('dueDate', null, ['widget' => 'single_text']);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => Task::class, // 明確指定資料類別
'validation_groups' => ['registration'], // 指定驗證群組
]);
}
public function getBlockPrefix() { return 'task'; } // 3.4+ 替代 getName()
}
控制器使用:$form = $this->createForm(TaskType::class, $task);
// Task 關聯 Category
$builder->add('category', CategoryType::class);
{# 模板 #}
分類
{{ form_row(form.category.name) }}
提交時自動建立 Category 物件並設定到 Task->category。
$builder->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false, // 重要:確保 setter 被呼叫
]);
前端需配合 JavaScript 動態增減欄位 (參考 Symfony 文檔 prototype 模式)。
{# templates/form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock %}
{# 在模板套用 #}
{% form_theme form 'form/fields.html.twig' %}
類型_部分:textarea_widget、field_errors、form_row。父類型回退機制:textarea_errors 不存在 → 用 field_errors。
# config.yml
twig:
form:
resources: ['form/fields.html.twig']
{% extends 'base.html.twig' %}
{% form_theme form _self %}
{% block field_row %} {# 自定義 #} {% endblock %}
{% block content %}{{ form_row(form.task) }}{% endblock %}
預設啟用,隱藏欄位 _token。form_rest(form) 會自動渲染。自定義:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'task_item', // 不同表單用不同 token
]);
}
關閉:'csrf_protection' => false。
public function contactAction(Request $request) {
$defaultData = ['message' => 'Type your message here'];
$form = $this->createFormBuilder($defaultData)
->add('name', 'text')
->add('email', 'email')
->add('message', 'textarea')
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); // 回傳陣列 ['name'=>..., 'email'=>..., 'message'=>...]
// 發送郵件等
}
// ...
}
未指定 data_class 且資料非物件時,自動陣列模式。驗證需手動傳入 validation_constraint 選項 (Collection 約束)。
Symfony Form 組件提供從簡單到複雜的完整表單解決方案。關鍵要點:
form_row 快速、手動控制精細form_rest() 不可省略