對網頁開發來說,處理HTML表單是常見且具挑戰性的任務之一,Symfony結合元件Form來幫助你處理。在本文中,你將從簡單開始到複雜表單,逐步學習表單的最重要特性。
安裝
在使用之前請先執行以下指令安裝:composer require symfony/form
建立簡單的表單
假設你要建立一個簡單的備忘清單應用來顯示任務"tasks",因為使用者需要編輯和新增任務,所以你需要新增一個表格。但在建立表單之前,先建立一個Task的類(class)來儲存任務資料:// src/Entity/Task.php namespace App\Entity; class Task { protected $task; protected $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; } }這個類是個普通老式的PHP物件,因為到目前為止,它與Symfony或任何資源庫都無關,這個普通的PHP物件能夠直接解決你應用中的問題(i.e. 即這個應用裡代表任務的需求)。在本文結束時,你將能把數據提交到Task實例(透過HTML表單),驗證其數據並將此永久儲存到資料庫中。
創建表單
現在你已經有Task,下一步是新增實際的HTML表單,在Symfony中,實作的方式是產生一個表單的物件並給予模板顯示,現在這一切都可以在控制器內完成:// src/Controller/TaskController.php namespace App\Controller; use App\Entity\Task; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; class TaskController extends AbstractController { public function new(Request $request) { // creates a task and gives it some dummy data for this example $task = new Task(); $task->setTask('Write a blog post'); $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Create Task']) ->getForm(); return $this->render('task/new.html.twig', [ 'form' => $form->createView(), ]); } }
新增一個表單需要相對短的程式碼,因為Symfony表單物件是由"form builder"所建立,這個"form builder"目的在方便你寫一個簡單的表單"食譜(recipes)",讓它完成實際構建表格的所有繁重工作。
在這個範例裡,你新增兩個欄位到表單 task 和 dueDate,相應於 Task 類(class)中的兩個屬性 task 和 dueDate,你也用完整的類名指定了每一個欄位的"類型(type)"(e.g. TextType和DateType),除此之外,他還代表此欄位該顯示什麼HTML標籤。
最後,你需要增加一個提交(submit)的按鈕,並可以自訂顯示文字(label),來提交表單給伺服器。
Symfony提供許多類型,將稍後討論(請參考Built-in Field Types)
顯示表單(Rendering the Form)
現在表單已經被創建了,下一步就要顯示它,實作的方式是傳一個特殊的表單"視圖(view)"物件到你的模板(請看上面controller範例程式中寫道$form->createView()),接著使用 form helper functions:{# templates/task/new.html.twig #} {{ form(form) }}
就是這樣!form這個函式將會顯示所有欄位,夾在 <form> 開始和結束的tag。預設情況下,form method是 POST ,預設情況下目標路徑(target URL)和目前顯示表單的路徑相同,根據需求兩者都可以修改。
像這樣簡短的方式缺乏彈性,通常你需要針對整個或部分的欄位有更多調整的需求,Symfony提供一些方式來達成:
- 如果你的應用程式有使用CSS框架如Bootstrap或Foundation,可以善用built-in form themes來讓你的表單達成一致的風格。
- 如果你需要針對少數的欄位進行客製化,或者只是應用中少部分的表單,請閱讀文章How to Customize Form Rendering。
- 如果你希望所有的表單風格都一致,你可建立一個Symfony表單主題(Symfony form theme)(可以以任何內建的主題為基礎或是全部重新建立)。
往下進行下去之前,你會注意到如何顯示task輸入欄位,是因為$task物件有task的值,這是表單的第一個工作:從物件取出資料並轉換成適合在HTML表單中顯示的格式。
處理表單的提交
預設情況下,表格會透過POST的請求提交到與顯示表單同一個控制器。接下來,第二個步驟是要解析使用者傳送資料的屬性,要達到這個目的,這些提交資料必須寫入Form物件內,請將下列程式碼加入你的控制器:
// ... use Symfony\Component\HttpFoundation\Request; public function new(Request $request) { // just setup a fresh $task object (remove the dummy data) $task = new Task(); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Create Task']) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // $form->getData() holds the submitted values // but, the original `$task` variable has also been updated $task = $form->getData(); // ... perform some action, such as saving the task to the database // for example, if Task is a Doctrine entity, save it! // $entityManager = $this->getDoctrine()->getManager(); // $entityManager->persist($task); // $entityManager->flush(); return $this->redirectToRoute('task_success'); } return $this->render('task/new.html.twig', [ 'form' => $form->createView(), ]); }
這個控制器是依照普遍的模式來處理表單,有三種可能的路徑:
- 當瀏覽器初始載入頁面時,表單會被建立並顯示,handleRequest()知道表單尚未提交並不採取任何行動,若表單沒有被提交,則isSubmitted()回傳false。
- 當用戶提交表單時,handleRequest()會知道並立即把提交的資料寫入$task的屬性task和dueDate,接著對這個物件進行驗證,如果這個物件不合法(驗證將在下個章節說明),isValid()回傳false,並重新顯示表單,並附註驗證錯誤。
- 當使用者提交合法資料的表單,被提交的資料又再次被寫入表單,但這一次isValid()會回傳true,在你重新導向到其他頁面之前(e.g. 一個"感謝"或"成功"頁面),你有機會用$task物件來執行一些動作(像是永久保存到資料庫)。
表格驗證(Form Validation)
在前面的章節中,你已經知道表單送出的資料可能是合法的(valid)或不合法的(invalid),在Symfony中,驗證可以用在基礎物件(e.g. Task),換句話說,我們所要驗證的並非這個"表單form",而是表單被提交的這些資料所產生$task這個物件是否合法,呼叫$form->isValid()是個最方便的方法來確認$task物件是否為合法的輸入資料。在使用驗證之前,請先執行下面指令讓你的應用支援這個功能:
composer require symfony/validator所謂的驗證就是在class裡增加一些規定(稱為限制 constraints),為了展示這項功能,我們添加一些驗證限制使得task這個欄位不能是空的,另外dueDate欄位除了不能是空的,也必須是合法的Datetime物件。
// 省略程式碼就是這樣!如果你用非合法的資料重新提交表單,你會看到相應的錯誤顯示在表單上。
驗證是Symfony非常強大的功能,可以參考這篇專門的介紹。(連結)
內建欄位類型(Built-in Field Types)
Symfony囊括需多常見的表單欄位和資料類型:Text Fields
Choice Fields
Date and Time Fields
Other Fields
Field Groups
Hidden Fields
Buttons
Base Fields
Field Type Options
每一個欄位類型都有一些選項可以設定,例如dueDat現在顯示3個選擇方框,然而DateType可以設定成顯示單一的文字方塊(使用者需要在方塊內輸入字串):
->add('dueDate', DateType::class, ['widget' => 'single_text'])
每一個欄位類型都有一些不一樣的選項可以以陣列的方式傳入,有許多欄位類型特有的選項,可以參閱各類型的文件了解細節。
Field Type Options GuessingField Type Guessing
現在你為Task新增驗證metadata,Symfony對於你的欄位已經知道些許,如果你允許的話,Symfony可以"猜測guess"你欄位的類型並替你進行設定。例如,Symfony藉由驗證規則會揣測task是一個普通的TextType,而dueDate則是DateType:public function new() { $task = new Task(); $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, ['widget' => 'single_text']) ->add('save', SubmitType::class) ->getForm(); }當你省略add()第二個參數時會啟動"guessing"(或傳入null),如果你傳入帶有選項的陣列作為第三個參數(如上述的dueDate),這些選項會被應用到需要被猜測的欄位。
Field Type Options Guessing
為了猜測欄位的"類型",Symfony可以試圖猜出數字欄位的正確的數值。required
required這個選項可以guessed,基於驗證的規則(i.e. 欄位是不是NotBlank或是NotNull)或是Doctrine metadata(i.e. 欄位是否nullable),這非常實用,用戶端驗證會自動與你的驗證規則相吻合。
maxlength
如果欄位是一種text field,那maxlength選項就可以guessed藉由驗證限制(若使用Length或Range)或藉由Doctrine metadata(藉由欄位長度)。
如果你想要改變其中一個guessed值,你可以傳入選項的陣列到非必填欄位來覆蓋。
->add('task', null, ['attr' => ['maxlength' => 4]])
建立表單類 Creating Form Classes
就像你看到的,表單可以直接在控制器裡建立並直接使用,然而更好的做法是建立獨立PHP class的表單,並可以在應用中隨處重複使用,建立新的類會包含構建任務表單的邏輯:// src/Form/TaskType.php namespace App\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task') ->add('dueDate', null, ['widget' => 'single_text']) ->add('save', SubmitType::class) ; } }這個新的類(class)包含建立Task表單所有需要的動作,控制器需要建立表單物件時可以使用:
// src/Controller/TaskController.php use App\Form\TaskType; public function new() { $task = ...; $form = $this->createForm(TaskType::class, $task); // ... }將表單邏輯放在它自有的類,表示表單可以在專案中重複使用,這是最好的建立表單方式,但選擇最後終究取決於你。
結語
當建立表單時,第一個要謹記於心的是如何將資料從物件(Task)轉換成HTML表格,讓使用者可以修改資料,表單的第二個目標就是讓使用者提交資料,並使這些更動重新儲存於物件。表單系統還有須多可以學習,也還有很多強大的技巧。
了解更多
- How to Change the Action and Method of a Form
- Bootstrap 4 Form Theme
- How to Choose Validation Groups Based on the Clicked Button
- How to Create a Custom Form Field Type
- How to Create a Form Type Extension
- How to Choose Validation Groups Based on the Submitted Data
- When and How to Use Data Mappers
- How to Use Data Transformers
- How to Use the submit() Function to Handle Form Submissions
- How to Disable the Validation of Submitted Data
- How to Dynamically Modify Forms Using Form Events
- How to Embed Forms
- Form Events
- How to Embed a Collection of Forms
- How to Customize Form Rendering
- How to Access Services or Config from Inside a Form
- How to Work with Form Themes
- How to Reduce Code Duplication with "inherit_data"
- How to Submit a Form with Multiple Buttons
- Creating a custom Type Guesser
- How to Unit Test your Forms
- How to Configure empty Data for a Form Class
- How to Dynamically Configure Form Validation Groups
- How to Define the Validation Groups to Use
- How to Use a Form without a Data Class
- How to Upload Files
- Form Types Reference
- How to Implement CSRF Protection
資料翻譯自:
https://symfony.com/doc/4.2/forms.html
系列文章:
- Symfony4.3 基本應用【第二篇】Doctrine ORM
- Symfony4.3 基本應用【第三篇】主控台指令 Console Commands
- Symfony4.3 基本應用【第四篇】權限與安全 Authentication & Security
沒有留言:
張貼留言