首頁 > 後端開發 > php教程 > 詳解Symfony2框架表單的用法

詳解Symfony2框架表單的用法

*文
發布: 2023-03-19 10:44:02
原創
2577 人瀏覽過

本文主要介紹了Symfony2框架學習筆記之表單用法,結合實例形式詳細分析了Symfony2針對表單的創建、校驗、提交等各種常用技巧。需要的朋友可以參考下,希望對大家有幫助。

對於一個網頁開發者來說,處理HTML表單是一個最為普通又具挑戰的任務。 Symfony2整合了一個Form元件,讓處理表單變的變得容易。在這一節裡,我們將從基礎開始創建一個複雜的表單,學習表單類別庫中最重要的內容。

Symfony2 的Form元件是一個獨立的類別庫,你可以在Symfony2專案之外使用它。

建立一個簡單的表單:

假設你要建立一個應用程式的todo列表,需要顯示一些任務。因為你的使用者需要編輯和建立任務,所以你需要建立一個表單。在你開始之前,先來看通用的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
登入後複製

該類別是一個普通的PHP物件類,因為他們沒有任何Symfony或其它類庫引用。非常簡單的一個PHP物件類,它直接解決的是你程式中表現task的資料。當然,在本節的最後,你將能夠透過HTML表單提交一個Task實例數據,校驗它的數值,並把它持久化到資料庫。

建立一個Form

現在已經建立了一個Task類,下一步就是建立和渲染一個真正的HTML表單了。在symfony2中,它是透過建立表單物件並渲染到範本的。現在,可以從controller內部處理form。

//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
{
    //创建一个任务并给它一些假数据作为示例
    $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(),
    ));
}
登入後複製

上面的範例顯示如何直接在Controller中建立一個表單,為了可以讓表單重複使用你完全可以在一個單獨的類別檔案中建立表單。

因為Symfony2透過一個表單產生器「form builder"來建立表單對象,所以你可以使用很少的程式碼就能完成建立表單任務。表單產生器的目的是讓你能編寫簡單的表單建立方法,讓它負責繁重的建立任務。

在這個範例中,你已經加入了兩個欄位到你的表單,一個是task一個是dueDate。它們關聯到Task類別的task和dueDate屬性。你已經為它們分別指定了類型(例如,text,date等),由這些類型來決定為這些欄位產生什麼樣的HTML表單標籤。

Symfony2 有許多內建的類型,接下來我們將簡單的介紹。

渲染一個表單

表單建立以後,下一步就是渲染它。這是透過傳遞一個特定的表單”view"物件(就是上例中的 $form->createView()傳回的view物件)到您的範本並透過一些列的表單幫助函數來實現的。

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(&#39;task_new&#39;) }}" 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[&#39;router&#39;]->generate(&#39;task_new&#39;) ?>" method="post" <?php echo $view[&#39;form&#39;]->enctype($form) ?> >
   <?php echo $view[&#39;form&#39;]->widget($form) ?>
   <input type="submit" />
</form>
登入後複製

在這裡假設你已經建立了一個名叫task_new的路由指向AcmeTaskBundle:Default:new Controller。

就是這些了,透過列印form_widget(form),表單中的每個欄位都會被渲染出來。同時還有一個文字標籤和錯誤訊息。是不是很簡單,不過現在它還不夠靈活。通常情況下,我們渴望單獨渲染表單中的每一個字段,這樣我們可以更好的控製表單的樣式。我們會在在範本中渲染表單一節介紹。

在繼續下去之前,我們注意到,為什麼我們渲染出來的task輸入框中有一個來自$task物件的屬性值「Write a blog post"。這是表單的第一個工作:從一個物件中獲取資料並將它轉換為合適的格式渲染到一個HTML表單中。

注意,表單系統已經夠聰明,它們能夠透過像getTask()和setTask()方法來存取Task類別中受保護的屬性。除非一個是公共屬性,否則必須有一個getter和setter方法被定義來用於表單元件從這些屬性中取得和保持資料。對於布林型的屬性,你可以使用一個”isser"方法(例如 isPublished())來取代getter方法(getPublished())。

處理表單提交

表單系統的第二個任務就是傳遞使用者提交的資料回到一個物件的屬性。要做到這一點,使用者提交的資料必須綁定到表單才行。加入以下程式碼到你的Controller類別:

//...
public function newAction(Request $request)
{
     //只是创建一个新的$task对象(不需要假数据)
    $task = new Task();
    $form= $this->createFormBuilder($task)
       ->add(&#39;task&#39;,&#39;text&#39;)
       ->add(&#39;dueDate&#39;,&#39;date&#39;)
       ->getForm();
    if($request->getMethod() == "POST"){
       $form->bindRequest($request);
       if($form->isValid()){
           //执行一些行为,比如保持task到数据库
           return $this->redirect($this->generateUrl(&#39;task_success&#39;));
       }
    }
    //...
}
登入後複製

現在,當表單被提交時,Controller可以綁定被提交的資料到表單,表單會把資料傳回$task物件的task和dueDate屬性。這些都在bindRequest()方法中完成。只要bindRequest()方法被調用,提交的資料就會立刻被傳輸到底層物件。不管資料是否被真正的校驗通過。

controller一般會遵循一個通用的模式來處理表單,它有三個可能的途徑:

1.當在瀏覽器初始載入一個頁面時,請求方法是GET,表單處理僅僅是創建和渲染。

2.当用户提交带有不合法数据的表单(方法为POST)时,表单会并绑定然后渲染,这时候显示所有校验错误。

3.当用户提交的表单带有的数据均合法时,表单绑定并且在页面跳转之前你有机会去使用数据去执行一些业务逻辑活动,比如持久化它到数据库)。

表单校验

在前面我们提到了,如何提交一个带有合法数据和非法数据的表单。在Symfony2中,校验是在底层对象上进行的。换句话说,form表单合法与否不重要,主要看在表单提交数据以后,底层对象比如$task对象是否合法。调用$form->isvalid() 是一个询问底层对象是否获得合法数据的快捷方式。

校验是通过添加一些列规则(约束)到一个类来完成的。我们给Task类添加规则和约束,使它的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(&#39;task&#39;, new NotBlank());
    $metadata->addPropertyConstraint(&#39;dueDate&#39;, new NotBlank());
    $metadata->addPropertyConstraint(&#39;dueDate&#39;, new Type(&#39;\DateTime&#39;));
  }
}
登入後複製

就是这样了,如果你现在再提交包含非法数据的表单,你将会看到相应的错误被打印在表单上。

HTML5 校验

作为HTML5,许多浏览器都加强了客户端某些校验约束。最常用的校验活动是在一个必须的字段上渲染一个required属性。对于支持HTML5的浏览器来说,如果用户此时提交一个空字段到表单时,浏览器会显示提示信息。生成的表单广泛吸收了这些新内容的优点,通过添加一些HTML属性来监控校验。客户端校验可以通过添加novalidate属性到form标签或者formnovalidate 到提交标签而关闭。这对你想检查服务端校验规则时非常有用。

校验分组

如果你的对象想从校验组中受益,你需要指定你的表单使用哪个校验组。

$form = $this->createFormBuilder($users, array(
  &#39;validation_groups&#39; => array(&#39;registration&#39;),
))->add(...)
;
登入後複製

如果你创建表单类,你需要添加羡慕的getDefaultOptions()方法:

public function getDefaultOptions(array $options)
{
  return array(
    &#39;validation_groups&#39; => array(&#39;registration&#39;)
  );
}
登入後複製

在这两种情况下,只有registration 校验组将被用于校验底层对象。

内建字段类型

Symfony标准版含有大量的字段类型,它们几乎涵盖了所有通用表单的字段和数据类型。

文本字段:
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

当然,你也可以定义自己的字段类型。

字段类型选项

每一个字段类型都有一定数量的选项用于配置。比如,dueDate字段当前被渲染成3个选择框。而日期字段可以被配置渲染成一个单一的文本框,用户可以输入字符串作为日期。

->add(&#39;dueData&#39;,&#39;data&#39;, array(&#39;widget&#39; = &#39;single_text&#39;))
登入後複製

required选项:

最常用到的选项是required选项,它可以应用于任何字段。默认情况下它被设置为true。这就意味着支持HTML5的浏览器会使用客户端校验来判断字段是否为空。如果你不想让它发生,或者把在你的字段上把required选项设置为false,或者关闭HTML5校验。设置required为true并不意味着服务端校验被应用。换句话说,如果用户提交一个空数值到该字段,它将接受这个控制除非你使用Symfony的NotBlank或者NotNull校验约束。也就是说,required选项是很好,但是服务端校验还是要继续用。

label选项:

表单字段可以使用label选项设置显示字符标签,可以应用于任何字段:

->add(&#39;dueDate&#39;, &#39;date&#39;,array(
  &#39;widget&#39; =>&#39;single_text&#39;,
  &#39;label&#39; => &#39;Due Date&#39;,
))
登入後複製

字段类型猜测:

现在你已经添加了校验元数据到Task类,Symfony早已经了解一点关于你的字段了。如果你允许,Symfony可以猜到你的字段数据类型并为你设置它。在下面的例子中,Symfony可以根据校验规则猜测到task字段是一个标准的text字段,dueDate是date字段。

public function newAction()
{
  $task = new Task();
  $form = $this->createFormBuilder($task)
    ->add(&#39;task&#39;)
    ->add(&#39;dueDate&#39;, null, array(&#39;widget&#39; => &#39;single_text&#39;))
    ->getForm();
}
登入後複製

当你省略了add方法的第二个参数(或者你输入null)时,Symfony的猜测能力就起作用了。如果你输入一个选项数组作为第三个参数(比如上面的dueDate),那么这些选项会成为Symfony猜测的依据。如果你的表单使用了指定的校验数组,字段类型猜测器将还是要考虑所有的校验规则来综合猜测你的字段类型。

字段类型可选项猜测

除了猜测字段类型,Symfony还能是这猜测一些可选项字段值。当这些可选项被设置时,字段将会被渲染到特定HTML属性中,让HTML5客户端来提供校验。

然而,它们不会在服务端生成相应的校验规则。尽管你需要手动的在服务端添加这些规则,但是这些字段类型选项还是能根据这些信息猜测到。

required: required规则可以在校验规则或者Doctrine元数据的基础上猜测到。这当你的客户端校验将自动匹配你的校验规则时很有用。
max_length: 如果字段是一些列文本字段,那么max_length选项可以从校验规则或者Doctrine元数据中猜到。
如果你喜欢改变一个猜到的数值,你可以通过在可选项数组中传递该选项来重写它。

->add(&#39;task&#39;,null, array(&#39;max_length&#39;=>4))
登入後複製

在模板中渲染表单

到目前为止,我们已经看了一个完整的表单是如何通过一行代码被渲染的。当然,你通常需要更加灵活的渲染方式:

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(&#39;task_new&#39;) }}" 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[&#39;router&#39;]->generate(&#39;task_new&#39;) ?>" method="post" <?php echo $view[&#39;form&#39;]->enctype($form) ?>>
  <?php echo $view[&#39;form&#39;]->errors($form) ?>
  <?php echo $view[&#39;form&#39;]->row($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->row($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->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) 渲染没有指出的其余任何字段,通常在表单的末尾调用它防止遗忘或者渲染一些你不愿意手动设置的隐藏字段。它同时还能为我们提供CSRF保护。

大部分工作是由form_row帮助方法类完成的,它默认在一个p中为每个字段渲染显示标签,错误信息和HTML表单部件。

注意,你可以通过form.vars.value 来访问你当前是表当数据:

Twig格式:

{{ form.vars.value.task }}
登入後複製

PHP代码格式:

<?php echo $view[&#39;form&#39;]->get(&#39;value&#39;)->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[&#39;form&#39;]->errors($form) ?>
<p>
  <?php echo $view[&#39;form&#39;]->label($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->errors($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->widget($form[&#39;task&#39;]) ?>
</p>
<p>
  <?php echo $view[&#39;form&#39;]->label($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->errors($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->widget($form[&#39;dueDate&#39;]) ?>
</p>
<?php echo $view[&#39;form&#39;]->rest($form) ?>
登入後複製

如果自动生成显示标签不准确,那么你可以显式的指定它:

Twig格式:

{{ form_label(form.task, &#39;Task Description&#39;) }}
登入後複製

PHP代码格式:

<?php echo $view[&#39;form&#39;]->label($form[&#39;task&#39;], &#39;Task Description&#39;) ?>
登入後複製

一些字段类型有一些额外的渲染选项可以传入widget,一个常用的选项为attr,它允许你修改表单元素的属性。下面的示例将添加task_field class到渲染的文本输入字段:

Twig格式:

{{ form_widget(form.task, {&#39;attr&#39;: {&#39;class&#39;:&#39;task_field&#39;} }) }}
登入後複製

PHP代码格式:

<?php echo $view[&#39;form&#39;]->widget($form[&#39;task&#39;], array(
&#39;attr&#39; => array(&#39;class&#39; => &#39;task_field&#39;),
)) ?>
登入後複製

如果你想手工渲染表单字段,你可以单独访问每个字段的值,比如id,name和label,这里我们获取id

Twig格式:

{{ form.task.vars.id }}
登入後複製

PHP代码格式:

<?php echo $form[&#39;task&#39;]->get(&#39;id&#39;) ?>
登入後複製

需要获取表单字段名称属性你需要使用full_name值:

Twig格式:

{{ form.task.vars.full_name }}
登入後複製

PHP代码格式:

<?php echo $form[&#39;task&#39;]->get(&#39;full_name&#39;) ?>
登入後複製

创建表单类

正如你看到的,一个表单可以直接在controller类中被创建和使用。然而,一个更好的做法是在一个单独的PHP类中创建表单。它可以被重用到你应用程序的任何地方。创建一个新类来保存生成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(&#39;task&#39;);
    $builder->add(&#39;dueDate&#39;, null, array(&#39;widget&#39; => &#39;single_text&#39;));
  }
  public function getName()
  {
    return &#39;task&#39;;
  }
}
登入後複製

这个新类包含了所有创建一个task表单所需要的内容,注意getName()方法将返回一个该表单类型的唯一标识,用于快速创建该表单。

// src/Acme/TaskBundle/Controller/DefaultController.php
// 在类上添加这个新的引用语句
use Acme\TaskBundle\Form\Type\TaskType;
public function newAction()
{
    $task = // ...
    $form = $this->createForm(new TaskType(), $task);
    // ...
}
登入後複製

设置data_class

每个表单都需要知道它底层保存数据的类名称,(比如Acme\TaskBundle\Entity\Task)。通常情况下,是根据createForm方法的第二个参数来猜测的。以后,当你开始嵌入表单时,这个可能就不怎么充分了,所以,通常一个好的方法是通过添加下面代码到你的表单类型类来显式的指定data_class 选项。

public function getDefaultOptions(array $options)
{
   return array(
        &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Task&#39;,
   );
}
登入後複製

当然,这种做法也不总是必须的。

当你映射表单到一个对象是,所有的字段都被映射。 表单的任何字段如果在映射的对象上不存在那么就会造成抛出异常。在这种情况下,你需要在表单中获取字段(比如,一个“你同意这些说法吗?”复选框)将不能映射到底层对象,那么你需要设置property_path为false以避免抛出异常。

public function buildForm(FormBuilder $builder, array $options)
{
     $builder->add(&#39;task&#39;);
     $builder->add(&#39;dueDate&#39;, null, array(&#39;property_path&#39; => false));
}
登入後複製

另外,如果有任何的表单字段没有被包含着提交的数据中,那么这些字段需要显式的设置为null。

在controller类中我们可以访问字段数据:


$form->get(&#39;dueDate&#39;)->getData();
登入後複製

Forms和Doctrine

表单的目的是把数据从一个底层对象传递给一个HTML表单然后把用户提交的数据传回到原先的底层对象。因此,底层对象把数据持久化到数据库就跟表单没有任何的关系了。但是,如果你已经配置了底层类是通过Doctrine来持久化,(你已经定义了映射元数据在底层类),接下来当表单提交数据后,当表单合法后就可以持久化它了。

if ($form->isValid()) {
  $em = $this->getDoctrine()->getEntityManager();
  $em->persist($task);
  $em->flush();
  return $this->redirect($this->generateUrl(&#39;task_success&#39;));
}
登入後複製

如果处于某种原因,你不想访问原有的$task对象,你可以从表单中直接获取数据:

$task = $form->getData();
登入後複製

在这里,关键要理解当表单跟底层对象绑定后,用户提交的数据会立刻传递给底层对象。如果你想持久化这些数据,你只需要持久化对象本身即可。

嵌入式表单:(Embedded Forms)

通常,你可能想生成一个表单,它包含来自不同对象的字段。比如,一个注册表单可能包含属于User对象和Address对象的字段。幸运的是,这些对于form组件来说都是很容易很自然的事。嵌入一个单独对象:假设每个Task属于一个Category对象,首先创建这个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;
  }
}
登入後複製

现在我们来相应我们应用程序的一个新需求,需要创建一个 表单可以让用户修改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(&#39;name&#39;);
  }
  public function getDefaultOptions(array $options)
  {
    return array(
      &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Category&#39;,
    );
  }
  public function getName()
  {
    return &#39;category&#39;;
  }
}
登入後複製

我们的最终目的是能够让用户在Task表单中修改Category对象,所以,我们需要添加一个类型为CategoryType表单类的category字段到TaskType 表单类。

public function buildForm(FormBuilder $builder, array $options)
{
  // ...
  $builder->add(&#39;category&#39;, 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[&#39;form&#39;]->row($form[&#39;category&#39;][&#39;name&#39;]) ?>
</p>
<?php echo $view[&#39;form&#39;]->rest($form) ?>
<!-- ... -->
登入後複製

当用户提交表单时,提交的Category字段数据被用于创建一个Category实例,然后被设置到Task实例的category字段。该Category实例可以通过Task实例来访问,同时也能被持久化到数据或者用作它用。

$task->getCategory()
登入後複製

嵌入一个表单集合

你也可以将一个表单集合嵌入到一个表单(想象一个Category 表单和许多Product子表单)。它是通过一个字段类型集合类实现的。

表单主题化

表单的每一部分渲染都是可以被自定义个性化的。你可以自由的改变每一个表单行的渲染,改变渲染错误的标志,更或者是textarea标签应该怎样显示等。没有任何限制,不同的个性化设置能用到不同的区域。

Symfony使用模板渲染每一个或者部分表单,比如label标签,input标签,错误信息以及任何其它内容。在Twig中,每个表单片段会被一个Twig block来渲染。要个性化渲染表单,你只需要重写相应的block即可。在PHP模板中,它是通过单独的模板文件来渲染表单片段的,所以你需要通过编写新的模板来替代旧的模板即可。在理解了它们是怎么工作的之后,让我们来个性化form_row片段并添加一个class属性到包裹每一表单行的p元素。首先创建一个新模板文件用于存放新的标志:

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[&#39;form&#39;]->label($form, $label) ?>
  <?php echo $view[&#39;form&#39;]->errors($form) ?>
  <?php echo $view[&#39;form&#39;]->widget($form, $parameters) ?>
</p>
登入後複製

field_row表单片段会在通过form_row函数渲染大部分的表单字段时使用。 要告诉你的表单组件使用你的新的field_row片段,需要添加下面的内容到你渲染该表单的模板顶部:

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form &#39;AcmeTaskBundle:Form:fields.html.twig&#39; %}
{% form_theme form &#39;AcmeTaskBundle:Form:fields.html.twig&#39; &#39;AcmeTaskBundle:Form:fields2.html.twig&#39; %}
<form ...>
登入後複製

PHP代码格式:

<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<?php $view[&#39;form&#39;]->setTheme($form, array(&#39;AcmeTaskBundle:Form&#39;)) ?>
<?php $view[&#39;form&#39;]->setTheme($form, array(&#39;AcmeTaskBundle:Form&#39;, &#39;AcmeTaskBundle:Form&#39;)) ?>
<form ...>
登入後複製

其中的form_theme 标签导入前面定义的片段。换句话说,当form_row函数在模板中被调用后,它将从你的自定义主题中使用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),你可以创建一个你需要重写的片段名称(比如:textarea_widget).

模板片段继承

在某些情况下,你个性化的片段可能会丢失。比如,在Symfony提供的默认主题中没有提供textarea_errors片段。那么如何来渲染一个textarea字段的错误信息呢?

答案是通过field_errors片段。当Symfony渲染一个textarea类型的错误时,它首先查找一个textarea_errors片段,如果没有找到则会回到field_errors片段。

每个field类型有一个parenttype(textarea的父类型为field),Symfony如果没有发现本身的片段,就会转而使用父类片段。

所以,要重写textarea字段的errors,拷贝field_errors片段,重命名为textarea_errors并个性化它们。为所有字段重写默认的error渲染,则需要直接拷贝和个性化field_errors片段。

全局表单主题

在上面的示例中,我们使用了form_theme helper来导入自定义个的表单片段到表单。你也可以告诉Symfony在全项目中导入自定义的form。

Twig

为了从所有之前创建的fileds.html.twig模板中自动包含个性化的block,修改你的应用程序配置文件:

YAML格式:

# app/config/config.yml
twig:
  form:
    resources:
      - &#39;AcmeTaskBundle:Form:fields.html.twig&#39;
  # ...
登入後複製

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(&#39;twig&#39;, array(
  &#39;form&#39; => array(&#39;resources&#39; => array(
    &#39;AcmeTaskBundle:Form:fields.html.twig&#39;,
   ))
  // ...
));
登入後複製

现在在fields.html.twig模板中的任何块都可以被广泛的使用来定义表单输出了。

自定义表单输出到一个单一的Twig文件中

在Twig中,你也可以个性化一个表单块在模板中

{% extends &#39;::base.html.twig&#39;%}
{# 导入"_self" 作为一个表单主题 #}
{% form_theme form _self %}
{# 个性化表单片段 #}
{% block field_row %}
    {# 自定义字段行输出 #}
{% endblock field_row %}
{% block content %}
    {# ... #}
    {{ form_row(form.task) }}
{% endblock %}
登入後複製

这里{% form_theme form _self %}标签允许表单块在使用那些自动化内容的模板中被直接自定义化。使用这个方法来快速的生成个性化输出。

注意,{% form_theme form _self %}的功能只有在继承自其它模板时才能起作用,如果不是继承自其它模板,则需要指出form_theme 到单独模板中。

PHP

从以前在所有模板中创建的Acme/TaskBundle/Resources/views/Form 目录自动导入个性化模板。修改你的配置文件:

YAML格式:

# app/config/config.yml
framework:
  templating:
    form:
      resources:
        - &#39;AcmeTaskBundle:Form&#39;
# ...
登入後複製

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(&#39;framework&#39;, array(
  &#39;templating&#39; => array(&#39;form&#39; =>
    array(&#39;resources&#39; => array(
      &#39;AcmeTaskBundle:Form&#39;,
   )))
  // ...
));
登入後複製

此时在Acme/TaskBundle/Resources/views/Form目录中的任何片段都可以全局范围内定义表单输出了。

CSRF 保护

CSRF--Cross-site request forgery,跨站伪造请求 是恶意攻击者试图让你的合法用户在不知不觉中提交他们本不想提交的数据的一种方法。

幸运的是,CSRF攻击可以通过在你的表单中使用CSRF 记号来阻止。

默认情况下,Symfony自动为你嵌入一个合法的CSRF令牌。这就意味着你不需要做任何事情就可以得到CSRF保护。CSRF保护是通过在你的表单中添加一个隐藏字段,默认的名叫_token。它包含一个值,这个值只有你和你的用户知道。这确保了是用户而不是其它实体在提交数据。Symfony自动校验该token是否存在以及其准确性。

_token 字段是一个隐藏字段并且会自动的渲染,只要你在你的模板中包含了form_rest()函数。它确保了没有被渲染过的字段全部渲染出来。CSRF令牌可以按照表单来个性化,比如:

class TaskType extends AbstractType
{
   // ...
   public function getDefaultOptions(array $options)
   {
      return array(
          &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Task&#39;,
          &#39;csrf_protection&#39; => true,
          &#39;csrf_field_name&#39; => &#39;_token&#39;,
          // 一个唯一的键值来保证生成令牌
          &#39;intention&#39; => &#39;task_item&#39;,
      );
   }
   // ...
}
登入後複製

要关闭CSRF保护,设置csrf_protection 选项为false。intentsion选项是可选的,但为不同的表单生成不同的令牌极大的加强了安全性。

使用一个无底层类表单

大多数情况下,一个表单要绑定一个对象的,并且表单中所有的字段获取或者保存它们的数据到该对象属性。但有时候,你可能只想使用一个没有类的表单,返回一个提交数据的数组,这个非常容易实现:

// 确认你在类上方导入了Request对象
use Symfony\Component\HttpFoundation\Request
// ...
public function contactAction(Request $request)
{
    $defaultData = array(&#39;message&#39; => &#39;Type your message here&#39;);
    $form = $this->createFormBuilder($defaultData)
       ->add(&#39;name&#39;, &#39;text&#39;)
       ->add(&#39;email&#39;, &#39;email&#39;)
       ->add(&#39;message&#39;, &#39;textarea&#39;)
       ->getForm();
     if ($request->getMethod() == &#39;POST&#39;) {
        $form->bindRequest($request);
        // 数据是一个数组并包含 "name", "email", 和"message" 键
        $data = $form->getData();
     }
    // ... 渲染表单
}
登入後複製

默认情况下,一个表单真的假设你想要一个数据数组而不是数据对象。

这里有两种方式你可以改变它的行为并绑定一个对象;

1.当创建表单时传入一个对象(作为createFormBuilder的第一个参数或者createForm的第二个参数)。

2.在你的表单中声明data_class 选项

如果以上两种方式都没有,那么表单会返回一个数组数据。在这个示例中因为$defaultData不是一个对象,又没有设置data_class选项,则$form->getData()最终返回一个数组。

你也可以通过Request对象直接访问POST的值,

$this->get(&#39;request&#39;)->request->get(&#39;name&#39;);
登入後複製

注意,大多数的情况下我们使用getData()方法是更好一点的选择。因为它返回的是经过表单框架转换过的数据。

添加校验规则

唯一遗漏的地方就是校验规则了,通常当你调用$form->isvalid()时,对象会调用你在类东提供的校验规则进行校验。但如果没有类,你怎么来添加对你表单数据的约束规则呢?答案是自己创建约束,然后传入到表单。

// 在controller类前导入命名空间
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Collection;
$collectionConstraint = new Collection(array(
      &#39;name&#39; => new MinLength(5),
      &#39;email&#39; => new Email(array(&#39;message&#39; => &#39;Invalid email address&#39;)),
));
// 创建一个表单没有默认值,传入约束选项。
$form = $this->createFormBuilder(null, array(
           &#39;validation_constraint&#39; => $collectionConstraint,
    ))->add(&#39;email&#39;, &#39;email&#39;)
// ...
;
登入後複製

现在,当你调用$form->bindRequest($request)时,约束就会被创建并作用于你的表单数据。如果你使用表单类,重写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(
      &#39;name&#39; => new MinLength(5),
      &#39;email&#39; => new Email(array(&#39;message&#39; => &#39;Invalid email address&#39;)),
    ));
    return array(&#39;validation_constraint&#39; => $collectionConstraint);
  }
}
登入後複製

这样你有了足够的灵活性来创建表单类和约束了,它返回一个数据数组而不是一个对象。大多数情况下,这个是不错的,而绑定一个表单到一个对象,从某种程度上说更加健壮。对于简单表单来说是个不错的选择。

总结思考

你现在已经了解了所有建造复杂功能性的表单所需要的所有建造块。当生成表单时,记住一个表单的首要目标是从一个对象把数据传递给一个HTML表单以方便用户修改它们。第二个目标就是把用户提交的数据重写提交回对象。

以上是詳解Symfony2框架表單的用法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板