? ???? Symfony2 ????? ?? ??? ? ???? ?? ????, Symfony2? ? ??, ??, ?? ? ??? ?? ??? ?? ???? ??? ?????. ??? ??? ???? ???? ?? ? ???.
? ????? HTML ?? ??? ?? ????? ??? ?? ? ?????. Symfony2? ?? ?? ??? ???? ?? ??? ?? ????. ?? ???? ??? ??? ??? ?? ???? ???? ?? ?????? ?? ??? ??? ?????.
Symfony2? Form ?? ??? Symfony2 ???? ???? ??? ? ?? ?? ??? ????????.
??? ?? ???:
??????? ?? ? ? ??? ??? ? ?? ??? ???? ??? ??? ?????. ???? ??? ???? ???? ??? ??? ???? ???. ???? ?? ?? ?? ??? ???? ???? ???? ? ???? ?? Task ???? ?????.
// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\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; } }
??? ???? ??? ?? ???? ?? ?? AcmeTaskBundle? ???? ???.
$ php app/console generate:bundle --namespace=Acme/TaskBundle
This class Symfony ?? ?? ????? ??? ?? ??? ???? PHP ?? ??????. ?? ??? PHP ?? ????, ????? ??? ???? ???? ?? ?????. ?? ? ??? ??? HTML ??? ?? ??? ???? ???? ????, ?? ?? ????, ??????? ??? ? ?? ???.
?? ???
Task ???? ?????? ?? ??? ?? HTML ??? ??? ????? ????. Symfony2??? ?? ??? ???? ?? ???? ????? ?????. ?? ???? ???? ??? ??? ? ????.
//src/Acme/TaskBundle/Controller/DefaultController.php namespace Acme\TaskBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Acme\TaskBundle\Entity\Task; class DefaultController extends Controller { //創(chuàng)建一個任務并給它一些假數(shù)據(jù)作為示例 $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('AcmeTaskBundle:Default:new.html.twig',array( 'form' =>$form->createView(), )); }
?? ?? ?????? ?? ?? ???? ??? ?????. ?? ?????? ??? ??? ??? ?? ???? ???.
Symfony2? ? ?? "? ??"? ?? ? ??? ???? ??? ?? ?? ??? ? ?? ??? ??? ? ????. ?? ???? ??? ??? ?? ?? ??? ???? ??? ??? ??? ? ??? ?? ????.
? ???? ??? ? ?? ??? ??????. ??? ???? ?? ??? DueDate???. ?? Task ???? task ? DueDate ??? ?????. ?? ??? ?? ??(?: ???, ?? ?)? ????? ??? ??? ?? ?? ??? ?? ???? HTML ?? ??? ??? ?????.
Symfony2?? ??? ???? ???, ?? ?? ??? ??? ???????.
?? ???
??? ??? ? ?? ??? ??? ????? ????. ?? ?? ?? "??" ??(? ??? $form->createView()? ?? ??? ?? ??)? ???? ???? ??? ?? ??? ??? ?? ?????.
Twig ??:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} <form action="{{ path('task_new') }}" method ="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form>
PHP ?? ??:
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php --> <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> > <?php echo $view['form']->widget($form) ?> <input type="submit" /> </form>
????? AcmeTaskBundle:Default:new ????? ???? task_new?? ??? ????? ?????.
?????. form_widget(form)? ???? ??? ? ??? ??????. ??? ??? ?? ???? ????. ?? ???? ????? ??? ??? ??? ???? ????. ????? ??? ???? ? ? ??? ? ??? ??? ? ??? ????? ?????? ???. ?? ???? ????? ?? ??? ???? ??????.
???? ?? ??? ???? ?? ?? ??? $task ??? "??? ??? ??" ?? ?? ?? ??? ??????. ??? ??? ? ?? ?????. ???? ???? ??? HTML ???? ????? ?? ??? ???? ???? ????.
?? ???? getTask() ? setTask()? ?? ???? ?? Task ???? ??? ?? ??? ???? ? ?? ?? ?????. ??? ??? ?? ?? ??? ???? ???? ?? ????? ?? ?? ??? ?? getter ? setter ???? ???? ??? ???. ?? ??? ?? getter ???(getPublished()) ?? "isser" ???(?: isPublished())? ??? ? ????.
?? ?? ??
?? ???? ? ?? ??? ???? ??? ???? ??? ???? ?? ???? ????. ??? ??? ???? ??? ???? ??? ????? ???. Controller ???? ?? ??? ?????.
//... public function newAction(Request $request) { //只是創(chuàng)建一個新的$task對象(不需要假數(shù)據(jù)) $task = new Task(); $form= $this->createFormBuilder($task) ->add('task','text') ->add('dueDate','date') ->getForm(); if($request->getMethod() == "POST"){ $form->bindRequest($request); if($form->isValid()){ //執(zhí)行一些行為,比如保持task到數(shù)據(jù)庫 return $this->redirect($this->generateUrl('task_success')); } } //... }
?? ??? ???? Controller? ??? ???? ??? ???? ? ??? ??? ?? ???? $task ??? task ? DueDate ???? ?? ?????. . ??? ??? ?? BindRequest() ????? ?????. BindRequest() ???? ???? ?? ??? ???? ?? ?? ??? ?????. ???? ??? ?????? ??? ???? ????.
????? ????? ??? ???? ???? ??? ????, ???? ? ?? ??? ??? ????:
1. ???? ????? ?? ??? ? ?? ??? GET?? ?? ??? ?? ?? ? ??????.
2.當用戶提交帶有不合法數(shù)據(jù)的表單(方法為POST)時,表單會并綁定然后渲染,這時候顯示所有校驗錯誤。
3.當用戶提交的表單帶有的數(shù)據(jù)均合法時,表單綁定并且在頁面跳轉之前你有機會去使用數(shù)據(jù)去執(zhí)行一些業(yè)務邏輯活動,比如持久化它到數(shù)據(jù)庫)。
表單校驗
在前面我們提到了,如何提交一個帶有合法數(shù)據(jù)和非法數(shù)據(jù)的表單。在Symfony2中,校驗是在底層對象上進行的。換句話說,form表單合法與否不重要,主要看在表單提交數(shù)據(jù)以后,底層對象比如$task對象是否合法。調(diào)用$form->isvalid() 是一個詢問底層對象是否獲得合法數(shù)據(jù)的快捷方式。
校驗是通過添加一些列規(guī)則(約束)到一個類來完成的。我們給Task類添加規(guī)則和約束,使它的task屬性不能為空,duDate字段不能空并且是一個合法的DateTime對象。
YAML格式:
# Acme/TaskBundle/Resources/config/validation.yml Acme\TaskBundle\Entity\Task: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: \DateTime
在Task類中聲明格式:
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Constraints as Assert; class Task { /** * @Assert\NotBlank() */ public $task; /** * @Assert\NotBlank() * @Assert\Type("\DateTime") */ protected $dueDate; }
XML格式:
<!-- Acme/TaskBundle/Resources/config/validation.xml --> <class name="Acme\TaskBundle\Entity\Task"> <property name="task"> <constraint name="NotBlank" /> </property> <property name="dueDate"> <constraint name="NotBlank" /> <constraint name="Type"> <value>\DateTime</value> </constraint> </property> </class>
PHP代碼格式:
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; class Task { // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('task', new NotBlank()); $metadata->addPropertyConstraint('dueDate', new NotBlank()); $metadata->addPropertyConstraint('dueDate', new Type('\DateTime')); } }
就是這樣了,如果你現(xiàn)在再提交包含非法數(shù)據(jù)的表單,你將會看到相應的錯誤被打印在表單上。
HTML5 校驗
作為HTML5,許多瀏覽器都加強了客戶端某些校驗約束。最常用的校驗活動是在一個必須的字段上渲染一個required屬性。對于支持HTML5的瀏覽器來說,如果用戶此時提交一個空字段到表單時,瀏覽器會顯示提示信息。生成的表單廣泛吸收了這些新內(nèi)容的優(yōu)點,通過添加一些HTML屬性來監(jiān)控校驗??蛻舳诵r灴梢酝ㄟ^添加novalidate屬性到form標簽或者formnovalidate 到提交標簽而關閉。這對你想檢查服務端校驗規(guī)則時非常有用。
校驗分組
如果你的對象想從校驗組中受益,你需要指定你的表單使用哪個校驗組。
$form = $this->createFormBuilder($users, array( 'validation_groups' => array('registration'), ))->add(...) ;
如果你創(chuàng)建表單類,你需要添加羨慕的getDefaultOptions()方法:
public function getDefaultOptions(array $options) { return array( 'validation_groups' => array('registration') ); }
在這兩種情況下,只有registration 校驗組將被用于校驗底層對象。
內(nèi)建字段類型
Symfony標準版含有大量的字段類型,它們幾乎涵蓋了所有通用表單的字段和數(shù)據(jù)類型。
文本字段:
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
基礎字段:
field
form
當然,你也可以定義自己的字段類型。
字段類型選項
每一個字段類型都有一定數(shù)量的選項用于配置。比如,dueDate字段當前被渲染成3個選擇框。而日期字段可以被配置渲染成一個單一的文本框,用戶可以輸入字符串作為日期。
->add('dueData','data', array('widget' = 'single_text'))
required選項:
最常用到的選項是required選項,它可以應用于任何字段。默認情況下它被設置為true。這就意味著支持HTML5的瀏覽器會使用客戶端校驗來判斷字段是否為空。如果你不想讓它發(fā)生,或者把在你的字段上把required選項設置為false,或者關閉HTML5校驗。設置required為true并不意味著服務端校驗被應用。換句話說,如果用戶提交一個空數(shù)值到該字段,它將接受這個控制除非你使用Symfony的NotBlank或者NotNull校驗約束。也就是說,required選項是很好,但是服務端校驗還是要繼續(xù)用。
label選項:
表單字段可以使用label選項設置顯示字符標簽,可以應用于任何字段:
->add('dueDate', 'date',array( 'widget' =>'single_text', 'label' => 'Due Date', ))
字段類型猜測:
現(xiàn)在你已經(jīng)添加了校驗元數(shù)據(jù)到Task類,Symfony早已經(jīng)了解一點關于你的字段了。如果你允許,Symfony可以猜到你的字段數(shù)據(jù)類型并為你設置它。在下面的例子中,Symfony可以根據(jù)校驗規(guī)則猜測到task字段是一個標準的text字段,dueDate是date字段。
public function newAction() { $task = new Task(); $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) ->getForm(); }
當你省略了add方法的第二個參數(shù)(或者你輸入null)時,Symfony的猜測能力就起作用了。如果你輸入一個選項數(shù)組作為第三個參數(shù)(比如上面的dueDate),那么這些選項會成為Symfony猜測的依據(jù)。如果你的表單使用了指定的校驗數(shù)組,字段類型猜測器將還是要考慮所有的校驗規(guī)則來綜合猜測你的字段類型。
字段類型可選項猜測
除了猜測字段類型,Symfony還能是這猜測一些可選項字段值。當這些可選項被設置時,字段將會被渲染到特定HTML屬性中,讓HTML5客戶端來提供校驗。
然而,它們不會在服務端生成相應的校驗規(guī)則。盡管你需要手動的在服務端添加這些規(guī)則,但是這些字段類型選項還是能根據(jù)這些信息猜測到。
required: required規(guī)則可以在校驗規(guī)則或者Doctrine元數(shù)據(jù)的基礎上猜測到。這當你的客戶端校驗將自動匹配你的校驗規(guī)則時很有用。
max_length: 如果字段是一些列文本字段,那么max_length選項可以從校驗規(guī)則或者Doctrine元數(shù)據(jù)中猜到。
如果你喜歡改變一個猜到的數(shù)值,你可以通過在可選項數(shù)組中傳遞該選項來重寫它。
->add('task',null, array('max_length'=>4))
在模板中渲染表單
到目前為止,我們已經(jīng)看了一個完整的表單是如何通過一行代碼被渲染的。當然,你通常需要更加靈活的渲染方式:
Twig格式:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> {{ form_errors(form) }} {{ form_row(form.task) }} {{ form_row(form.dueDate) }} {{ form_rest(form) }} <input type="submit" /> </form>
PHP代碼格式:
<!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php --> <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?>> <?php echo $view['form']->errors($form) ?> <?php echo $view['form']->row($form['task']) ?> <?php echo $view['form']->row($form['dueDate']) ?> <?php echo $view['form']->rest($form) ?> <input type="submit" /> </form>
讓我們看看這組代碼的詳細:
form_enctype(form) 只要有一個字段是文件上傳,那么它就會義務的設置為 enctype="multipart/form-data";
form_errors(form) 渲染任何整個form的任何錯誤信息(特定字段的錯誤,會顯示在每個字段的下面一行)。
form_row(form.dueDate) 默認情況下,為給定的字段在一個p中渲染一個文本標簽,任何錯誤,和HTML表單部件。
form_rest(form) 渲染沒有指出的其余任何字段,通常在表單的末尾調(diào)用它防止遺忘或者渲染一些你不愿意手動設置的隱藏字段。它同時還能為我們提供CSRF保護。
大部分工作是由form_row幫助方法類完成的,它默認在一個p中為每個字段渲染顯示標簽,錯誤信息和HTML表單部件。
注意,你可以通過form.vars.value 來訪問你當前是表當數(shù)據(jù):
Twig格式:
{{ form.vars.value.task }}
PHP代碼格式:
<?php echo $view['form']->get('value')->getTask() ?>
手工渲染每一個表單字段
form_row幫助器能讓你很快的渲染你表單中的每一個字段,并且每一行可以被自定義化。但是生活不總是那么簡單的,你也可能要手動的渲染每一個字段。
Twig格式:
{{ form_errors(form) }} <p> {{ form_label(form.task) }} {{ form_errors(form.task) }} {{ form_widget(form.task) }} </p> <p> {{ form_label(form.dueDate) }} {{ form_errors(form.dueDate) }} {{ form_widget(form.dueDate) }} </p> {{ form_rest(form) }}
PHP代碼格式:
<?php echo $view['form']->errors($form) ?> <p> <?php echo $view['form']->label($form['task']) ?> <?php echo $view['form']->errors($form['task']) ?> <?php echo $view['form']->widget($form['task']) ?> </p> <p> <?php echo $view['form']->label($form['dueDate']) ?> <?php echo $view['form']->errors($form['dueDate']) ?> <?php echo $view['form']->widget($form['dueDate']) ?> </p> <?php echo $view['form']->rest($form) ?>
如果自動生成顯示標簽不準確,那么你可以顯式的指定它:
Twig格式:
{{ form_label(form.task, 'Task Description') }}
PHP代碼格式:
<?php echo $view['form']->label($form['task'], 'Task Description') ?>
一些字段類型有一些額外的渲染選項可以傳入widget,一個常用的選項為attr,它允許你修改表單元素的屬性。下面的示例將添加task_field class到渲染的文本輸入字段:
Twig格式:
{{ form_widget(form.task, {'attr': {'class':'task_field'} }) }}
PHP代碼格式:
<?php echo $view['form']->widget($form['task'], array( 'attr' => array('class' => 'task_field'), )) ?>
如果你想手工渲染表單字段,你可以單獨訪問每個字段的值,比如id,name和label,這里我們獲取id
Twig格式:
{{ form.task.vars.id }}
PHP代碼格式:
<?php echo $form['task']->get('id') ?>
需要獲取表單字段名稱屬性你需要使用full_name值:
Twig格式:
{{ form.task.vars.full_name }}
PHP代碼格式:
<?php echo $form['task']->get('full_name') ?>
創(chuàng)建表單類
正如你看到的,一個表單可以直接在controller類中被創(chuàng)建和使用。然而,一個更好的做法是在一個單獨的PHP類中創(chuàng)建表單。它可以被重用到你應用程序的任何地方。創(chuàng)建一個新類來保存生成task表單的邏輯:
// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('widget' => 'single_text')); } public function getName() { return 'task'; } }
這個新類包含了所有創(chuàng)建一個task表單所需要的內(nèi)容,注意getName()方法將返回一個該表單類型的唯一標識,用于快速創(chuàng)建該表單。
// src/Acme/TaskBundle/Controller/DefaultController.php // 在類上添加這個新的引用語句 use Acme\TaskBundle\Form\Type\TaskType; public function newAction() { $task = // ... $form = $this->createForm(new TaskType(), $task); // ... }
設置data_class
每個表單都需要知道它底層保存數(shù)據(jù)的類名稱,(比如Acme\TaskBundle\Entity\Task)。通常情況下,是根據(jù)createForm方法的第二個參數(shù)來猜測的。以后,當你開始嵌入表單時,這個可能就不怎么充分了,所以,通常一個好的方法是通過添加下面代碼到你的表單類型類來顯式的指定data_class 選項。
public function getDefaultOptions(array $options) { return array( 'data_class' => 'Acme\TaskBundle\Entity\Task', ); }
當然,這種做法也不總是必須的。
當你映射表單到一個對象是,所有的字段都被映射。 表單的任何字段如果在映射的對象上不存在那么就會造成拋出異常。在這種情況下,你需要在表單中獲取字段(比如,一個“你同意這些說法嗎?”復選框)將不能映射到底層對象,那么你需要設置property_path為false以避免拋出異常。
public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('property_path' => false)); }
另外,如果有任何的表單字段沒有被包含著提交的數(shù)據(jù)中,那么這些字段需要顯式的設置為null。
在controller類中我們可以訪問字段數(shù)據(jù):
$form->get('dueDate')->getData();
Forms和Doctrine
表單的目的是把數(shù)據(jù)從一個底層對象傳遞給一個HTML表單然后把用戶提交的數(shù)據(jù)傳回到原先的底層對象。因此,底層對象把數(shù)據(jù)持久化到數(shù)據(jù)庫就跟表單沒有任何的關系了。但是,如果你已經(jīng)配置了底層類是通過Doctrine來持久化,(你已經(jīng)定義了映射元數(shù)據(jù)在底層類),接下來當表單提交數(shù)據(jù)后,當表單合法后就可以持久化它了。
if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($task); $em->flush(); return $this->redirect($this->generateUrl('task_success')); }
如果處于某種原因,你不想訪問原有的$task對象,你可以從表單中直接獲取數(shù)據(jù):
$task = $form->getData();
在這里,關鍵要理解當表單跟底層對象綁定后,用戶提交的數(shù)據(jù)會立刻傳遞給底層對象。如果你想持久化這些數(shù)據(jù),你只需要持久化對象本身即可。
嵌入式表單:(Embedded Forms)
通常,你可能想生成一個表單,它包含來自不同對象的字段。比如,一個注冊表單可能包含屬于User對象和Address對象的字段。幸運的是,這些對于form組件來說都是很容易很自然的事。嵌入一個單獨對象:假設每個Task屬于一個Category對象,首先創(chuàng)建這個Category對象:
// src/Acme/TaskBundle/Entity/Category.php namespace Acme\TaskBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Category { /** * @Assert\NotBlank() */ public $name; }
接下來,添加一個新的category屬性到Task類:
// ... class Task { // ... /** * @Assert\Type(type="Acme\TaskBundle\Entity\Category") */ protected $category; // ... public function getCategory() { return $this->category; } public function setCategory(Category $category = null) { $this->category = $category; } }
現(xiàn)在我們來相應我們應用程序的一個新需求,需要創(chuàng)建一個 表單可以讓用戶修改Category對象。
// src/Acme/TaskBundle/Form/Type/CategoryType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class CategoryType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Acme\TaskBundle\Entity\Category', ); } public function getName() { return 'category'; } }
我們的最終目的是能夠讓用戶在Task表單中修改Category對象,所以,我們需要添加一個類型為CategoryType表單類的category字段到TaskType 表單類。
public function buildForm(FormBuilder $builder, array $options) { // ... $builder->add('category', new CategoryType()); }
這時我們可以在TaskType類字段渲染的旁邊渲染CategoryType類的字段了:
Twig格式:
{# ... #} <h3>Category</h3> <p class="category"> {{ form_row(form.category.name) }} </p> {{ form_rest(form) }} {# ... #}
PHP代碼格式:
<!-- ... --> <h3>Category</h3> <p class="category"> <?php echo $view['form']->row($form['category']['name']) ?> </p> <?php echo $view['form']->rest($form) ?> <!-- ... -->
當用戶提交表單時,提交的Category字段數(shù)據(jù)被用于創(chuàng)建一個Category實例,然后被設置到Task實例的category字段。該Category實例可以通過Task實例來訪問,同時也能被持久化到數(shù)據(jù)或者用作它用。
$task->getCategory()
嵌入一個表單集合
你也可以將一個表單集合嵌入到一個表單(想象一個Category 表單和許多Product子表單)。它是通過一個字段類型集合類實現(xiàn)的。
表單主題化
表單的每一部分渲染都是可以被自定義個性化的。你可以自由的改變每一個表單行的渲染,改變渲染錯誤的標志,更或者是textarea標簽應該怎樣顯示等。沒有任何限制,不同的個性化設置能用到不同的區(qū)域。
Symfony使用模板渲染每一個或者部分表單,比如label標簽,input標簽,錯誤信息以及任何其它內(nèi)容。在Twig中,每個表單片段會被一個Twig block來渲染。要個性化渲染表單,你只需要重寫相應的block即可。在PHP模板中,它是通過單獨的模板文件來渲染表單片段的,所以你需要通過編寫新的模板來替代舊的模板即可。在理解了它們是怎么工作的之后,讓我們來個性化form_row片段并添加一個class屬性到包裹每一表單行的p元素。首先創(chuàng)建一個新模板文件用于存放新的標志:
Twig格式:
{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} {% block field_row %} {% spaceless %} <p class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </p> {% endspaceless %} {% endblock field_row %}
PHP代碼格式:
<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php --> <p class="form_row"> <?php echo $view['form']->label($form, $label) ?> <?php echo $view['form']->errors($form) ?> <?php echo $view['form']->widget($form, $parameters) ?> </p>
field_row表單片段會在通過form_row函數(shù)渲染大部分的表單字段時使用。 要告訴你的表單組件使用你的新的field_row片段,需要添加下面的內(nèi)容到你渲染該表單的模板頂部:
Twig格式:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %} {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %} <form ...>
PHP代碼格式:
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php --> <?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form')) ?> <?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?> <form ...>
其中的form_theme 標簽導入前面定義的片段。換句話說,當form_row函數(shù)在模板中被調(diào)用后,它將從你的自定義主題中使用field_row 塊(替代Symfony已有的field_row block)。你的個性化主題不必重寫所有的塊。當渲染一個你沒有重寫過的塊時,主題引起會找全局的主題(定義在bundle級的主題)使用。
在擁有多個個性化主題的情況下,它會在使用全局主題之前查找定制列表。要個性化你表單的任何部分,你只需要重寫相關的片段即可。
表單片段命名
在symfony中,表單的每一部分都會被渲染,HTML表單元素,錯誤消息,顯示標簽等這些都是被定義在基礎主題里的。它組成了一個Twig的塊集合和一個PHP模板集合。
在Twig中,每個需要的塊都被定義到一個單獨的模板文件中(form_pe_layout.html.twig),它們被保存在Twig Bridge里。在這個文件中,你可以看到渲染一個表單多需要的每一個block和默認的字段類型。
在PHP模板中,片段是單獨的模板文件。 默認情況下它們位于框架bundle的Resources/views/Form 目錄下。每個偏度名稱都遵循相同的基本模式,用一個下劃線(_)分為兩部分,比如:
field_row 用于form_row渲染大部分的字段
textarea_widget 用于form_widget渲染一個textarea字段類型
field_errors 用于form_errors渲染一個字段的錯誤信息
每個片段都命名都遵循:type_part 模式。type部分對應被渲染的字段類型(比如textarea,checkbox,date等),而part部分對應著是什么被渲染(比如label,widget,errors等)
默認情況下,有4種可能的表單part被用來渲染:
label 渲染字段的標簽 如field_label
widget 渲染字段的HTML表示 如field_widget
errors 渲染字段的錯誤信息 如field_errors
row 渲染字段的整個行(包括label,widget和errors) 如 filed_row
還有其它3個part類型,分別是rows,rest和enctype,不過這三個一般不會用到。
通過知道字段類型(比如:textarea)和你想渲染那一部分(比如:widget),你可以創(chuàng)建一個你需要重寫的片段名稱(比如:textarea_widget).
模板片段繼承
在某些情況下,你個性化的片段可能會丟失。比如,在Symfony提供的默認主題中沒有提供textarea_errors片段。那么如何來渲染一個textarea字段的錯誤信息呢?
答案是通過field_errors片段。當Symfony渲染一個textarea類型的錯誤時,它首先查找一個textarea_errors片段,如果沒有找到則會回到field_errors片段。
每個field類型有一個parenttype(textarea的父類型為field),Symfony如果沒有發(fā)現(xiàn)本身的片段,就會轉而使用父類片段。
所以,要重寫textarea字段的errors,拷貝field_errors片段,重命名為textarea_errors并個性化它們。為所有字段重寫默認的error渲染,則需要直接拷貝和個性化field_errors片段。
全局表單主題
在上面的示例中,我們使用了form_theme helper來導入自定義個的表單片段到表單。你也可以告訴Symfony在全項目中導入自定義的form。
Twig
為了從所有之前創(chuàng)建的fileds.html.twig模板中自動包含個性化的block,修改你的應用程序配置文件:
YAML格式:
# app/config/config.yml twig: form: resources: - 'AcmeTaskBundle:Form:fields.html.twig' # ...
XML格式:
<!-- app/config/config.xml --> <twig:config ...> <twig:form> <resource>AcmeTaskBundle:Form:fields.html.twig</resource> </twig:form> <!-- ... --> </twig:config>
PHP代碼格式:
// app/config/config.php $container->loadFromExtension('twig', array( 'form' => array('resources' => array( 'AcmeTaskBundle:Form:fields.html.twig', )) // ... ));
現(xiàn)在在fields.html.twig模板中的任何塊都可以被廣泛的使用來定義表單輸出了。
自定義表單輸出到一個單一的Twig文件中
在Twig中,你也可以個性化一個表單塊在模板中
{% extends '::base.html.twig'%} {# 導入"_self" 作為一個表單主題 #} {% form_theme form _self %} {# 個性化表單片段 #} {% block field_row %} {# 自定義字段行輸出 #} {% endblock field_row %} {% block content %} {# ... #} {{ form_row(form.task) }} {% endblock %}
這里{% form_theme form _self %}標簽允許表單塊在使用那些自動化內(nèi)容的模板中被直接自定義化。使用這個方法來快速的生成個性化輸出。
注意,{% form_theme form _self %}的功能只有在繼承自其它模板時才能起作用,如果不是繼承自其它模板,則需要指出form_theme 到單獨模板中。
PHP
從以前在所有模板中創(chuàng)建的Acme/TaskBundle/Resources/views/Form 目錄自動導入個性化模板。修改你的配置文件:
YAML格式:
# app/config/config.yml framework: templating: form: resources: - 'AcmeTaskBundle:Form' # ...
XML格式:
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>AcmeTaskBundle:Form</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>
PHP代碼格式:
// app/config/config.php $container->loadFromExtension('framework', array( 'templating' => array('form' => array('resources' => array( 'AcmeTaskBundle:Form', ))) // ... ));
此時在Acme/TaskBundle/Resources/views/Form目錄中的任何片段都可以全局范圍內(nèi)定義表單輸出了。
CSRF 保護
CSRF--Cross-site request forgery,跨站偽造請求 是惡意攻擊者試圖讓你的合法用戶在不知不覺中提交他們本不想提交的數(shù)據(jù)的一種方法。
幸運的是,CSRF攻擊可以通過在你的表單中使用CSRF 記號來阻止。
默認情況下,Symfony自動為你嵌入一個合法的CSRF令牌。這就意味著你不需要做任何事情就可以得到CSRF保護。CSRF保護是通過在你的表單中添加一個隱藏字段,默認的名叫_token。它包含一個值,這個值只有你和你的用戶知道。這確保了是用戶而不是其它實體在提交數(shù)據(jù)。Symfony自動校驗該token是否存在以及其準確性。
_token 字段是一個隱藏字段并且會自動的渲染,只要你在你的模板中包含了form_rest()函數(shù)。它確保了沒有被渲染過的字段全部渲染出來。CSRF令牌可以按照表單來個性化,比如:
class TaskType extends AbstractType { // ... public function getDefaultOptions(array $options) { return array( 'data_class' => 'Acme\TaskBundle\Entity\Task', 'csrf_protection' => true, 'csrf_field_name' => '_token', // 一個唯一的鍵值來保證生成令牌 'intention' => 'task_item', ); } // ... }
要關閉CSRF保護,設置csrf_protection 選項為false。intentsion選項是可選的,但為不同的表單生成不同的令牌極大的加強了安全性。
使用一個無底層類表單
大多數(shù)情況下,一個表單要綁定一個對象的,并且表單中所有的字段獲取或者保存它們的數(shù)據(jù)到該對象屬性。但有時候,你可能只想使用一個沒有類的表單,返回一個提交數(shù)據(jù)的數(shù)組,這個非常容易實現(xiàn):
// 確認你在類上方導入了Request對象 use Symfony\Component\HttpFoundation\Request // ... public function contactAction(Request $request) { $defaultData = array('message' => 'Type your message here'); $form = $this->createFormBuilder($defaultData) ->add('name', 'text') ->add('email', 'email') ->add('message', 'textarea') ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); // 數(shù)據(jù)是一個數(shù)組并包含 "name", "email", 和"message" 鍵 $data = $form->getData(); } // ... 渲染表單 }
默認情況下,一個表單真的假設你想要一個數(shù)據(jù)數(shù)組而不是數(shù)據(jù)對象。
這里有兩種方式你可以改變它的行為并綁定一個對象;
1.當創(chuàng)建表單時傳入一個對象(作為createFormBuilder的第一個參數(shù)或者createForm的第二個參數(shù))。
2.在你的表單中聲明data_class 選項
如果以上兩種方式都沒有,那么表單會返回一個數(shù)組數(shù)據(jù)。在這個示例中因為$defaultData不是一個對象,又沒有設置data_class選項,則$form->getData()最終返回一個數(shù)組。
你也可以通過Request對象直接訪問POST的值,
$this->get('request')->request->get('name');
注意,大多數(shù)的情況下我們使用getData()方法是更好一點的選擇。因為它返回的是經(jīng)過表單框架轉換過的數(shù)據(jù)。
添加校驗規(guī)則
唯一遺漏的地方就是校驗規(guī)則了,通常當你調(diào)用$form->isvalid()時,對象會調(diào)用你在類東提供的校驗規(guī)則進行校驗。但如果沒有類,你怎么來添加對你表單數(shù)據(jù)的約束規(guī)則呢?答案是自己創(chuàng)建約束,然后傳入到表單。
// 在controller類前導入命名空間 use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); // 創(chuàng)建一個表單沒有默認值,傳入約束選項。 $form = $this->createFormBuilder(null, array( 'validation_constraint' => $collectionConstraint, ))->add('email', 'email') // ... ;
現(xiàn)在,當你調(diào)用$form->bindRequest($request)時,約束就會被創(chuàng)建并作用于你的表單數(shù)據(jù)。如果你使用表單類,重寫getDefaultOptions 方法來指定可選項:
namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; class ContactType extends AbstractType { // ... public function getDefaultOptions(array $options) { $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); return array('validation_constraint' => $collectionConstraint); } }
這樣你有了足夠的靈活性來創(chuàng)建表單類和約束了,它返回一個數(shù)據(jù)數(shù)組而不是一個對象。大多數(shù)情況下,這個是不錯的,而綁定一個表單到一個對象,從某種程度上說更加健壯。對于簡單表單來說是個不錯的選擇。
總結思考
你現(xiàn)在已經(jīng)了解了所有建造復雜功能性的表單所需要的所有建造塊。當生成表單時,記住一個表單的首要目標是從一個對象把數(shù)據(jù)傳遞給一個HTML表單以方便用戶修改它們。第二個目標就是把用戶提交的數(shù)據(jù)重寫提交回對象。
? ??? Symfony2 ????? ??? ???? ?? ??? ??? ?? ?????. ??? ??? PHP ??? ????? ?? ?? ??? ?????!

? AI ??

Undress AI Tool
??? ???? ??

Undresser.AI Undress
???? ?? ??? ??? ?? AI ?? ?

AI Clothes Remover
???? ?? ???? ??? AI ?????.

Clothoff.io
AI ? ???

Video Face Swap
??? ??? AI ?? ?? ??? ???? ?? ???? ??? ?? ????!

?? ??

??? ??

???++7.3.1
???? ?? ?? ?? ???

SublimeText3 ??? ??
??? ??, ???? ?? ????.

???? 13.0.1 ???
??? PHP ?? ?? ??

???? CS6
??? ? ?? ??

SublimeText3 Mac ??
? ??? ?? ?? ?????(SublimeText3)

??? ??











JSP ?? ?? ? ?? ?? JSP ??? ? ?? ???? ?????. ? ? ??: ? ??? ??? ? ?? ??? ? ? ????. ?? ? ??: /*? ???? */? ??? ?? ?? ?? ??? ??? ? ? ????. ? ? ?? ? ?? ? ?? ?/**?? ? ?????*?? ?? ??? ??? ? ? ????*/JSP ?? ?? JSP ??? ???? JSP ??? ??? ?? ?? ?????.

JavaScript? ???? ?? ?? ?? ??? ?? ???? ??? ???? ??? ?????? ??: ?? ?? ?? ??? ?? ???? ??? ? ???????? ?? ????? ???? ??? ??? ??? ???? ? ??? ???. ? ????? JavaScript? ???? ? ??? ???? ??? ???? ???? ?? ??? ?????. HTML ?? ??? ?? ?? ??? ?? ?? ??? ??? HTML ??? ???? ???. ?? ??? ??? ? ????: <!DOCTYP

Python ?? ??: abs ?? ??? ? ? 1. abs ?? ??? ?? Python?? abs ??? ??? ?? ???? ???? ? ???? ?? ?????. ?? ??? ???? ?? ??? ???? ??? ? ????. abs ??? ?? ??? ??? ????: abs(x) ??? x? ?? ?? ?? ??? ??? ? ?? ???? ???? ?? ?? ???????. 2. abs ??? ? ????? ? ?? ???? ?? ?? abs ??? ???? ?????. ? 1: ??

Python ?? ??: isinstance ??? ??? ? ? Python? ?????? ?? ???? ????? ??? ?? ?? ?? ??? ???? ??? ????? ?????. ?? ??? ?? ?? ? ??? isinstance() ?????. ? ????? isinstance ??? ???? ?? ???? ???? ?? ?? ?????. isinstance() ??? ??? ??? ???? ??? ?????? ??? ???? ? ?????. ? ??? ??? ??? ????

C ???? ?? ??? ????? ?? ?? ??? ?????. C ????? ???? ??? ???? ??? ????? ?? ???? ????? ???? ?? ??? ????. C ????? ? ??? ???? ?? exit() ??? ?????. ? ????? exit() ??? ???? ???? ?? ?? ??? ?????. Exit() ??? C ??? ?? ????? ??? ?? ??? ???? ????. ? ??? ???? ??? ???? ??? ??? ?? ? ????.

WPS? ????? ???? ??? ????? ????? WPS ??? ??? ??? ?? ? ??? ?? ?????. WPS ????? ? ?? ??? ??? ???? ? ???? ?? ??? ??? DATEDIF ??? ????. DATEDIF ??? ?? ?? DateDifference? ?????. ??? ??? ????. DATEDIF(start_date,end_date,unit) ??? start_date? ?? ??? ?????.

JavaScript? ???? ??? ?? ?? ??? ?? ??? ??? ???? ??? ?????? ?? ? ???????? ??? ???? ??? ?? ?? ???? ?? ?? ?????. ??? ???? ???? ???? ???? ?? ???? ??? ???? ???? ???? ?? ??? ????. ? ????? JavaScript? ???? ?? ?? ?? ??? ??? ??? ???? ???? ?? ??? ???? ??? ?????. ?? ??? ?? HTML? ??? ???? ???? ???.

HTML, CSS ? jQuery? ???? ?? ?? ??? ?? ??? ???? ?? ??? ?? ? ???????? ?? ???? ?? ? ?????. ???? ?? ???? ??? ? ?? ?? ??? ???? ??? ??? ??? ???? ?? ??? ??? ??? ??? ? ????. ? ???? HTML, CSS, jQuery? ???? ??? ?? ?? ??? ???? ??? ???? ???? ?? ??? ?????. 1. HTML ??? ?? ?? ??? HTML ??? ??? ?????.
