<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Symfony中文教程 &#187; book 2.6</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/category/the-symfony-book/book-2-6/feed" rel="self" type="application/rss+xml" />
	<link>http://www.newlifeclan.com/symfony</link>
	<description>站在巨人肩膀上的phpweb框架</description>
	<lastBuildDate>Fri, 12 Dec 2025 00:58:27 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.0.38</generator>
	<item>
		<title>第十一章：验证Validation</title>
		<link>http://www.newlifeclan.com/symfony/archives/502</link>
		<comments>http://www.newlifeclan.com/symfony/archives/502#comments</comments>
		<pubDate>Tue, 19 May 2015 10:59:34 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=502</guid>
		<description><![CDATA[<p>验证是在Web应用程序中一种很常见的任务。在表单中输入数据时需要被验证。数据还需要在写入到数据库或传送到Web [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/502">第十一章：验证Validation</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>验证是在Web应用程序中一种很常见的任务。在表单中输入数据时需要被验证。数据还需要在写入到数据库或传送到Web服务时进行验证。</p>
<p><span id="more-502"></span></p>
<p>Symfony2 配备了一个Validator 组件，它让校验工作变得简单易懂。该组件是基于JSR303 Bean校验规范。</p>
<p>&nbsp;</p>
<h2>验证的基础知识</h2>
<p>了解验证的最好方法就是看它在行动。开始，假设在你的应用程序中创建一个原始的php对象：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;

class Author
{
    public $name;
}</pre><p>到目前为止，这只是给你程序提供某些目的的普通类。验证的目的就是要告诉你，一个对象的数据是有效的。为了这个目的，你需要配置一个对象必须遵守规则或者约束列表来让自己的数据合法。这些规则可以通过许多不同的格式（YAML，XML、注释或PHP）来指定。</p>
<p>比如，我们保证属性$name不能为空，来添加下面的规则：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}</pre><p></p>
<blockquote><p> Protected和private属性以及getter方法也都可以被校验。</p></blockquote>
<p>&nbsp;</p>
<h2>使用验证服务</h2>
<p>接下来，使用validator服务（class <span style="color: #ff0000"><tt class="docutils literal"><code><a class="reference external" style="color: #ff0000" title="Symfony\Component\Validator\Validator" href="http://api.symfony.com/2.6/Symfony/Component/Validator/Validator.html">Validator</a></code></tt></span>）的validate方法来真正的校验Author对象。 validator的工作很简单：读取一个类的约束规则来校验一个对象的数据是否符合这些规则约束。如果校验失败，一个错误数组（class <tt class="docutils literal"><code><a class="reference external" title="Symfony\Component\Validator\ConstraintViolationList" href="http://api.symfony.com/2.6/Symfony/Component/Validator/ConstraintViolationList.html">ConstraintViolationList</a></code></tt>）将被返回。现在我们在一个controller中来执行它：</p><pre class="crayon-plain-tag">// ...
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Author;

// ...
public function authorAction()
{
    $author = new Author();

    // ... do something to the $author object

    $validator = $this-&gt;get('validator');
    $errors = $validator-&gt;validate($author);

    if (count($errors) &gt; 0) {
        /*
         * Uses a __toString method on the $errors variable which is a
         * ConstraintViolationList object. This gives us a nice string
         * for debugging.
         */
        $errorsString = (string) $errors;

        return new Response($errorsString);
    }

    return new Response('The author is valid! Yes!');
}</pre><p>如果$name属性是空的，你会看到一个错误信息：</p><pre class="crayon-plain-tag">AppBundle\Author.name:
    This value should not be blank</pre><p>如果你为$name属性插入一个值，那么你会获得快乐的成功信息。</p>
<blockquote><p>大多数时候，你不需要直接跟validator服务交流或者根本不需要担心打印出错误来。大多数情况下，你将在处理提交表单数据时，间接使用校验。想了解更多请阅读(验证与表单).</p></blockquote>
<p>你也可以传递一个错误信息集合到一个模版：</p><pre class="crayon-plain-tag">if (count($errors) &gt; 0) {
    return $this-&gt;render('author/validation.html.twig', array(
        'errors' =&gt; $errors,
    ));
}</pre><p>在模版中，你可以根据需要精确的输出错误列表：</p><pre class="crayon-plain-tag">{# app/Resources/views/author/validation.html.twig #}
&lt;h3&gt;The author has the following errors&lt;/h3&gt;
&lt;ul&gt;
{% for error in errors %}
    &lt;li&gt;{{ error.message }}&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;</pre><p></p>
<blockquote><p> 每个验证错误（都称为“约束冲突”），由一个 <tt class="docutils literal"><code><a class="reference external" title="Symfony\Component\Validator\ConstraintViolation" href="http://api.symfony.com/2.6/Symfony/Component/Validator/ConstraintViolation.html">ConstraintViolation</a>对象代表。</code></tt></p></blockquote>
<p>&nbsp;</p>
<h2>验证和表单</h2>
<p>validator服务可以在任何时候使用，来验证任何对象。 事实上，你将经常在处理表单时，间接使用validator。Symfony的表单类库间接使用validator服务来在数据被提交和绑定后验证底层对象。对象违反约束信息将被转化到FieldError对象，该对象可以很容易的被展示在你的表单中。在一个controller中的传统表单提交流程</p><pre class="crayon-plain-tag">// ...
use AppBundle\Entity\Author;
use AppBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;

// ...
public function updateAction(Request $request)
{
    $author = new Author();
    $form = $this-&gt;createForm(new AuthorType(), $author);

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        // the validation passed, do something with the $author object

        return $this-&gt;redirectToRoute(...);
    }

    return $this-&gt;render('author/form.html.twig', array(
        'form' =&gt; $form-&gt;createView(),
    ));
}</pre><p></p>
<blockquote><p> 该实例使用一个 <tt class="docutils literal"><code>AuthorType表单类，更多信息请查看表单章。</code></tt></p></blockquote>
<p>&nbsp;</p>
<h2>配置</h2>
<p>Symfony2 的validator默认情况下是启用的。但是如果你使用了annotations方法来指定你的约束，那么你需要显式的开启annotations功能：</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    validation: { enable_annotations: true }</pre><p></p>
<h3> 约束规则</h3>
<p>Validator是设计了的目的是用来按照约束规则验证对象的。为了验证一个对象，只需要映射一个或者多个约束到它要验证的类，然后把它传递给validator服务即可。</p>
<p>本质上，一个约束就是一个简单的PHP对象，它可以生成一个决断语句。 在现实生活中，一个约束可以是&#8221;蛋糕不能烤焦了&#8221; 这样的规则约束。在Symfony2中，约束都差不多：他们断言某个条件是否成立。给定一个值，约束会告诉你这个值是否遵守了你的约束规则。</p>
<h2>支持的约束</h2>
<p>symfony封装了许多最常用的约束：</p>
<h3>基础的约束</h3>
<p>首先是基础约束规则：使用他们来决断非常基本的事，比如你对象属性的值或者方法的返回值。</p>
<div id="basic-constraints" class="section">
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/NotBlank.html"><em>NotBlank</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Blank.html"><em>Blank</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/NotNull.html"><em>NotNull</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Null.html"><em>Null</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/True.html"><em>True</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/False.html"><em>False</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Type.html"><em>Type</em></a></li>
</ul>
</div>
<div id="string-constraints" class="section">
<h3>字符串约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Email.html"><em>Email</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Length.html"><em>Length</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Url.html"><em>Url</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Regex.html"><em>Regex</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Ip.html"><em>Ip</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Uuid.html"><em>Uuid</em></a></li>
</ul>
</div>
<div id="number-constraints" class="section">
<h3>数字约束<a class="headerlink" title="Permalink to this headline" href="http://symfony.com/doc/current/book/validation.html#number-constraints">¶</a></h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Range.html"><em>Range</em></a></li>
</ul>
</div>
<div id="comparison-constraints" class="section">
<h3>比较约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/EqualTo.html"><em>EqualTo</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/NotEqualTo.html"><em>NotEqualTo</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/IdenticalTo.html"><em>IdenticalTo</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/NotIdenticalTo.html"><em>NotIdenticalTo</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/LessThan.html"><em>LessThan</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/LessThanOrEqual.html"><em>LessThanOrEqual</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/GreaterThan.html"><em>GreaterThan</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/GreaterThanOrEqual.html"><em>GreaterThanOrEqual</em></a></li>
</ul>
</div>
<div id="date-constraints" class="section">
<h3>日期约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Date.html"><em>Date</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/DateTime.html"><em>DateTime</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Time.html"><em>Time</em></a></li>
</ul>
</div>
<div id="collection-constraints" class="section">
<h3>集合约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Choice.html"><em>Choice</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Collection.html"><em>Collection</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Count.html"><em>Count</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/UniqueEntity.html"><em>UniqueEntity</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Language.html"><em>Language</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Locale.html"><em>Locale</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Country.html"><em>Country</em></a></li>
</ul>
</div>
<div id="file-constraints" class="section">
<h3>文件约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/File.html"><em>File</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Image.html"><em>Image</em></a></li>
</ul>
</div>
<div id="financial-and-other-number-constraints" class="section">
<h3>金融和其他数字约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/CardScheme.html"><em>CardScheme</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Currency.html"><em>Currency</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Luhn.html"><em>Luhn</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Iban.html"><em>Iban</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Isbn.html"><em>Isbn</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Issn.html"><em>Issn</em></a></li>
</ul>
</div>
<div id="other-constraints" class="section">
<h3>其他约束</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Callback.html"><em>Callback</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Expression.html"><em>Expression</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/All.html"><em>All</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/UserPassword.html"><em>UserPassword</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints/Valid.html"><em>Valid</em></a></li>
</ul>
</div>
<p>你也可以创建自己的自定义约束。请查看cookbook的&#8221;<a class="reference internal" href="http://symfony.com/doc/current/cookbook/validation/custom_constraint.html"><em>How to Create a custom Validation Constraint</em></a>&#8220;。</p>
<p>&nbsp;</p>
<h2>约束的配置</h2>
<p>一些约束，比如NotBlank,很简单，但是其它的比如Choice约束，有许多配置项需要设置。假设Author类有另外一个属性，gender可以被设置为”male&#8221;或者&#8221;female&#8221;:</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\Choice(
     *     choices = { "male", "female" },
     *     message = "Choose a valid gender."
     * )
     */
    public $gender;

    // ...
}</pre><p>一个约束的选项通常都是通过一个数组来传递的。有些约束也允许你通过一个值指定，&#8221;default&#8221;数组是可以替换的。在Choice约束时，choices选项就可以通过这种方式指定。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\Choice({"male", "female"})
     */
    protected $gender;

    // ...
}</pre><p>这纯粹是让最常见的配置选项用起来更加简单和快速。</p>
<p>如果你不确定如何指定一个配置，你可以检查api文档或者为了保险起见你还是通过数组配置吧（上面第一种方式）。</p>
<p>&nbsp;</p>
<h2>翻译约束信息</h2>
<p>更多约束的翻译请查看 <a class="reference internal" href="http://symfony.com/doc/current/book/translation.html#book-translation-constraint-messages"><em>Translating Constraint Messages</em></a>.</p>
<p>&nbsp;</p>
<h2>约束目标</h2>
<p>约束可以被用于一个类的属性或者一个公共的getter方法。属性约束最常用也最简单，而公共的getter方法约束则允许你指定一个复杂的约束规则。</p>
<h3>属性约束</h3>
<p>属性验证一个最常规的验证技术。Symfony2允许你校验private,protected或者public属性。下面代码显示如何配置Author对象的$firstName属性至少有3个字符</p><pre class="crayon-plain-tag">// AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\NotBlank()
     * @Assert\Length(min=3)
     */
    private $firstName;
}</pre><p></p>
<h3>Getters约束</h3>
<p>约束也可以应用于一个方法的返回值。Symfony2 允许你添加一个约束到任何&#8221;get&#8221;、&#8221;is&#8221;或者&#8221;has&#8221;开头的public方法。这种类型的方法被称为“getters”。</p>
<blockquote><p>has开头的方法在symfony2.5被引入。</p></blockquote>
<p>该技术的好处是允许你动态的校验你的对象。比如，假设你想确认密码字段不匹配用户的first name(因为安全原因)。你可以通过创建一个isPasswordLegal 方法，然后决断这个方法必须返回true:</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\True(message = "The password cannot match your first name")
     */
    public function isPasswordLegal()
    {
        // ... return true or false
    }
}</pre><p>现在我们创建一个isPasswordLegal()方法，并且包含你需要逻辑：</p><pre class="crayon-plain-tag">public function isPasswordLegal()
{
    return $this-&gt;firstName !== $this-&gt;password;
}</pre><p></p>
<blockquote><p> 眼尖的人可能会注意到getter的前缀(&#8220;get&#8221;或者&#8221;is&#8221;)在映射时被忽略了。这允许你在不改变验证规则的前提下，把一个约束移动到一个具有同名属性上，反之亦然。</p></blockquote>
<p>&nbsp;</p>
<h2>类约束</h2>
<p>有一些约束可以应用到被验证的整个类。例如，回调（Callback）类型的约束，就是一个通用的，用于类自身的约束。当类被验证之后，约束所指定的方法将被直接执行，以便提供更多的自定义验证。</p>
<h2>验证组</h2>
<p>到目前为止，你已经能够添加约束到类，并询问是否该类传入所有定义的约束规则。一些情况下，你只需要使用该类的其中某些规则来验证一个对象。要做到这些，你可以组织每一个约束到一个或者多个验证组中，然后应用使用其中一组验证。</p>
<p>比如，假设你有一个User类，它会在用户注册和用户更新他们的联系信息时使用：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

class User implements UserInterface
{
    /**
    * @Assert\Email(groups={"registration"})
    */
    private $email;

    /**
    * @Assert\NotBlank(groups={"registration"})
    * @Assert\Length(min=7, groups={"registration"})
    */
    private $password;

    /**
    * @Assert\Length(min=2)
    */
    private $city;
}</pre><p>在这个配置中，有三个验证组</p>
<p>Default</p>
<p>包含当前类的约束和所有没有分配到任何组的约束规则</p>
<p>User</p>
<p>等于在<tt>Default组中的User对象所有的约束。他总是以类的名称命名。它和default组的不同如下所示。</tt></p>
<p>registration</p>
<p>只包含了email和password字段的校验规则</p>
<p>默认组中的约束是一个没有明确组配置的类的约束，或者是被配置为一组等于类名Default字符串的约束。</p>
<blockquote><p> 当只是User对象验证的时候，没有Default组和User组的差别。但是，如果User嵌入了对象就有区别了。例如，User有一个address属性，它包含很多的Address对象，并且你要添加有效的约束到这个属性，所以当你验证User对象时，你就要对他进行验证。</p>
<p>如果你验证User使用Default组，Address类将使用Default组约束。但是，如果你验证的User使用这个User验证组，那么address类将被User组验证。</p>
<p>换句话说，在default组和类名组（例如User）是一样的，除了在类被嵌入到另一个对象时，会根据另一个类的约束验证。</p>
<p>如果你有继承（如，User extends BaseUser）并且你去验证子类的类名（如User），那么所有<tt class="docutils literal"><code>User和</code></tt><tt class="docutils literal"><code>BaseUser</code></tt>的约束都将被验证。但是，如果你验证使用基类（如BaseUser），那么只有默认约束和BaseUser类被验证；</p></blockquote>
<p>告诉validator使用指定的验证组，传一个或者多个组名作为validate()方法的第三个参数即可：</p><pre class="crayon-plain-tag">// If you're using the new 2.5 validation API (you probably are!)
$errors = $validator-&gt;validate($author, null, array('registration'));

// If you're using the old 2.4 validation API, pass the group names as the second argument
// $errors = $validator-&gt;validate($author, array('registration'));</pre><p>如果你没有指定组，所有的约束都属于Default.</p>
<p>当然，你通常是间接验证表单库，如何在表单使用验证组请看<a class="reference internal" href="http://symfony.com/doc/current/book/forms.html#book-forms-validation-groups"><em>Validation Groups</em></a>.</p>
<p>&nbsp;</p>
<h2>约束组顺序</h2>
<p>在某些情况下，需要按照步骤验证你的组。要做到这一点，你可以使用GroupSequence功能。在这种情况下，给一个对象定义一组顺序，也就是确定组验证的顺序。</p>
<p>例如，假设你有一个User类，并希望他验证用户名和密码不能重复，只有在其他验证都通过后在验证（为了避免多个错误消息）。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @Assert\GroupSequence({"User", "Strict"})
 */
class User implements UserInterface
{
    /**
    * @Assert\NotBlank
    */
    private $username;

    /**
    * @Assert\NotBlank
    */
    private $password;

    /**
     * @Assert\True(message="The password cannot match your username", groups={"Strict"})
     */
    public function isPasswordLegal()
    {
        return ($this-&gt;username !== $this-&gt;password);
    }
}</pre><p>在这个案例中，他会先验证所有验证中的User组（就好像以前的Default组一样）。只有当这组所有约束都通过时，才会进行第二组验证，strict。</p>
<blockquote><p>正如你已经从上节中看到的，default组和类名组（如User组）都是相同的。然而，使用组顺序时，他们不再相同了。Default组将引用组序列,而不是所有不属于任何群体的约束。</p>
<p>这意味着，当你指定一组顺序，你必须要使用 <tt class="docutils literal"><code>{ClassName}（例如User）组。当你使用Default，你会得到一个无线递归。（default组引用组顺序，其中将包含default，他会引用同组的组顺序，...组）。</code></tt></p></blockquote>
<p>&nbsp;</p>
<h2>约束组顺序Providers</h2>
<p>想象一下一个User实体可以是一个普通的用户也可以是一个高级用户。当他是一个高级用户，user实体应该添加一些额外的约束（例如 信用卡详细信息）。去动态的确认哪个组被激活，你需要创建一个验证组顺序Providers。首先，创建实体并给他一个新的约束组Premium:</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class User
{
    /**
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @Assert\CardScheme(
     *     schemes={"VISA"},
     *     groups={"Premium"},
     * )
     */
    private $creditCard;

    // ...
}</pre><p>现在，改写User类并实现 <tt class="docutils literal"><code><a class="reference external" title="Symfony\Component\Validator\GroupSequenceProviderInterface" href="http://api.symfony.com/2.6/Symfony/Component/Validator/GroupSequenceProviderInterface.html">GroupSequenceProviderInterface</a>并且添加<tt class="docutils literal"><code><a class="reference external" title="Symfony\Component\Validator\GroupSequenceProviderInterface::getGroupSequence()" href="http://api.symfony.com/2.6/Symfony/Component/Validator/GroupSequenceProviderInterface.html#getGroupSequence()">getGroupSequence()</a>方法，他能以一个数组形式返回我们使用的组。</code></tt></code></tt></p><pre class="crayon-plain-tag">// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

// ...
use Symfony\Component\Validator\GroupSequenceProviderInterface;

class User implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence()
    {
        $groups = array('User');

        if ($this-&gt;isPremium()) {
            $groups[] = 'Premium';
        }

        return $groups;
    }
}</pre><p>最后，你必须告诉你的验证组件，你的User类使用组顺序进行验证：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

// ...

/**
 * @Assert\GroupSequenceProvider
 */
class User implements GroupSequenceProviderInterface
{
    // ...
}</pre><p>&nbsp;</p>
<p>&nbsp;</p>
<h2>值和数组的验证</h2>
<p>到目前为止，你已经看到了如何验证整个对象。但有时，你只是想验证一个简单的数值 &#8211; 想验证一个字符串是一个有效的电子邮件地址。这其实是很容易做到的。<span class="goog-text-highlight">从控制器里面，它看起来像这样：</span></p><pre class="crayon-plain-tag">// ...
use Symfony\Component\Validator\Constraints as Assert;

// ...
public function addEmailAction($email)
{
    $emailConstraint = new Assert\Email();
    // all constraint "options" can be set this way
    $emailConstraint-&gt;message = 'Invalid email address';

    // use the validator to validate the value
    // If you're using the new 2.5 validation API (you probably are!)
    $errorList = $this-&gt;get('validator')-&gt;validate(
        $email,
        $emailConstraint
    );

    // If you're using the old 2.4 validation API
    /*
    $errorList = $this-&gt;get('validator')-&gt;validateValue(
        $email,
        $emailConstraint
    );
    */

    if (0 === count($errorList)) {
        // ... this IS a valid email address, do something
    } else {
        // this is *not* a valid email address
        $errorMessage = $errorList[0]-&gt;getMessage();

        // ... do something with the error
    }

    // ...
}</pre><p>通过调用validator的validate方法，你可以传入一个原始值和一个你要使用的校验对象。可用约束完整列表-以及每个约束的完整类名-查看 <em><a class="reference internal" href="http://symfony.com/doc/current/reference/constraints.html">constraints reference</a>章。</em></p>
<p>该<tt class="docutils literal"><code>validate</code></tt>方法会返回一个<a href="http://api.symfony.com/2.6/Symfony/Component/Validator/ConstraintViolationList.html">ConstraintViolationList</a>对象，它扮演的只是一个错误信息数组的角色。集合中的每一个错误是一个<a href="http://api.symfony.com/2.6/Symfony/Component/Validator/ConstraintViolation.html">ConstraintViolation</a>对象，使用对象的getMessage方法可以获取错误信息。</p>
<p>&nbsp;</p>
<h2>总结：</h2>
<p>Symfony2 的validator是一个强大的工具，它可以被用来保证任何对象数据的合法性。它的强大来源于约束规则，你可以把它们应用于你对象的属性和getter方法。其实，你大多数情况下都是在使用表单时，间接的应用了验证框架，记住它可以被应用于任何地方验证任何对象。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/502">第十一章：验证Validation</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/502/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>第八章：数据库和Doctrine</title>
		<link>http://www.newlifeclan.com/symfony/archives/491</link>
		<comments>http://www.newlifeclan.com/symfony/archives/491#comments</comments>
		<pubDate>Mon, 18 May 2015 09:32:34 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=491</guid>
		<description><![CDATA[<p>对于任何应用程序来说最为普遍最具挑战性的任务，就是从数据库中读取和持久化数据信息。尽管symfony完整的框架 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/491">第八章：数据库和Doctrine</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>对于任何应用程序来说最为普遍最具挑战性的任务，就是从数据库中读取和持久化数据信息。尽管symfony完整的框架没有默认集成ORM，但是symfony标准版，集成了很多程序，还自带集成了Doctrine这样一个库，主要的目的是给开发者一个强大的工具，让你工作起来更加容易。在本章，你会学会doctrine的基本理念并且能够了解如何轻松使用数据库。</p>
<p><span id="more-491"></span></p>
<blockquote><p>Doctrine可以完全脱离symfony使用，并且在symfony中是否使用也是可选的。本章主要了解Doctrine的ORM，其目的是让你的对象映射到数据库中（如<em>MySQL</em>, <em>PostgreSQL</em>和<em>Microsoft SQL</em>）。如果你喜欢使用原始的数据库查询，这很容易，可以了解cookbook 中的&#8221;<a class="reference internal" href="http://symfony.com/doc/current/cookbook/doctrine/dbal.html"><em>How to Use Doctrine DBAL</em></a>&#8220;。</p>
<p>你也可以使用Doctrine ODM库将数据持久化到MongoDB。更多信息请参阅&#8221;<a class="reference external" href="http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html">DoctrineMongoDBBundle</a>&#8220;。</p></blockquote>
<p>&nbsp;</p>
<h2>一个简单的例子：一个产品</h2>
<p>了解Doctrine是如何工作的最简单的方式就是看一个实际的应用。在本章，你需要配置你的数据库，创建一个Product对象，持久化它到数据库并且把它抓取回来。</p>
<h3>配置数据库</h3>
<p>在你真正开始之前，你需要配置你的数据库链接信息。按照惯例，这些信息通常配置在app/config/parameters.yml文件中：</p><pre class="crayon-plain-tag"># app/config/parameters.yml
parameters:
    database_driver:    pdo_mysql
    database_host:      localhost
    database_name:      test_project
    database_user:      root
    database_password:  password

# ...</pre><p></p>
<blockquote><p> 将配置信息定义到parameters.yml仅仅是一个惯例。定义在该文件中的配置信息将会被主配置文件在安装Doctrine时引用。</p><pre class="crayon-plain-tag"># app/config/config.yml
doctrine:
    dbal:
        driver:   "%database_driver%"
        host:     "%database_host%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"</pre><p>通过把数据库信息分离到一个特定的文件中，你可以很容易的为每个服务器保存不同的版本。你也可以在项目外轻松存储数据库配置（一些敏感信息），就像apache配置一样。更多信息请参阅<a class="reference internal" href="http://symfony.com/doc/current/cookbook/configuration/external_parameters.html"><em>How to Set external Parameters in the Service Container</em></a>.</p></blockquote>
<p>现在Doctrine知道你的数据库配置了，你可以用它来创建一个数据库了。</p><pre class="crayon-plain-tag">$ php app/console doctrine:database:create</pre><p></p>
<blockquote><p>设置数据库为UTF8</p>
<p>即便对于经验丰富的程序员来说，一个常犯的错误是，在Symfony项目开始后，忘记设置他们的数据库默认字符集和校对规则，仅把大部分数据库给出的latin类型的校对作为默认。他们也许在第一次操作时会记得，但到了后面敲打两行相关的常规命令之后，就完全忘掉了。</p><pre class="crayon-plain-tag">$ php app/console doctrine:database:drop --force
$ php app/console doctrine:database:create</pre><p>在Doctrine里直接指派默认字符集是不可能的，因为doctrine会根据环境配置，尽可能多地去适应各种“不可知”情形。解决办法之一，是去配置“服务器级别”的默认信息。</p>
<p>设置UTF8为MySql的默认字符集是非常简单的，只要在数据库配置文件中加几行代码就可以了（一般是my.cnf文件）</p><pre class="crayon-plain-tag">[mysqld]
# Version 5.5.3 introduced "utf8mb4", which is recommended
collation-server     = utf8mb4_general_ci # Replaces utf8_general_ci
character-set-server = utf8mb4            # Replaces utf8</pre><p>我们推荐避免使用Mysql的uft8字符集，因为它并不兼容4-byte unicode字符，如果字符串中有这种字符会被清空。不过这种情况被修复了，参考《<em><span style="color: #ff0000"><a style="color: #ff0000" href="https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html" target="_blank">新型utf8mb4字符集</a></span></em>》</p></blockquote>
<p>&nbsp;</p>
<blockquote><p>如果你想要使用SQLite作为数据库，你需要设置path为你的数据库路径</p><pre class="crayon-plain-tag"># app/config/config.yml
doctrine:
    dbal:
        driver: pdo_sqlite
        path: "%kernel.root_dir%/sqlite.db"
        charset: UTF8</pre><p>&nbsp;</p></blockquote>
<p>&nbsp;</p>
<h2>创建一个实体类</h2>
<p>假设你创建一个应用程序，其中有些产品需要展示。即时不考虑Doctrine或者数据库，你也应该知道你需要一个Product对象来表现这些产品。在你的AppBundle的Entity目录下创建一个类。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;

class Product
{
    protected $name;
    protected $price;
    protected $description;
}</pre><p>这样的类经常被称为“Entity&#8221;,意味着一个基础类保存数据。它们简单来满足你应用程序的业务需要。不过现在它还不能被保存到数据库中，因为现在它只不过还是个简单的PHP类。</p>
<p>一旦你学习了Doctrine背后的概念，你可以让Doctrine来为你创建实体类。他会问你一些问题来创建entity：</p><pre class="crayon-plain-tag">$ php app/console doctrine:generate:entity</pre><p>&nbsp;</p>
<h2>添加映射信息</h2>
<p>Doctrine允许你使用一种更加有趣的方式对数据库进行操作，而不是只是获取基于列表的行到数组中。Doctrine允许你保存整个对象到数据库或者把对象从数据库中取出。这些都是通过映射PHP类到一个数据库表，PHP类的属性对应数据库表的列来实现的。</p>
<p><img class="alignnone size-full wp-image-492" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/05/doctrine_image_1.png" alt="doctrine_image_1" width="690" height="230" /></p>
<p>因为Doctrine能够做这些，所以你仅仅只需要创建一个meatdata，或者配置告诉Doctrine的Product类和它的属性应该如何映射到数据库。这些metadata可以被定义成各种格式，包括YAML，XML或者通过声明直接定义到Product类中。</p>
<p>annotations：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="product")
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;

    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    protected $price;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;
}</pre><p></p>
<blockquote><p> 一个bundle只可以接受一种metadata定义格式。比如，不能把YAML定义的metadata和声明PHP实体类一起混用。</p>
<p>表名是可选的，如果省略，将基于entity类的名称自动确定。</p></blockquote>
<p>Doctrine允许你去选择各种不同的字段类型，每个字段都有自己的配置。有关字段类型信息，请看<em><a class="reference internal" href="http://symfony.com/doc/current/book/doctrine.html#book-doctrine-field-types">Doctrine Field Types Reference</a>。</em></p>
<blockquote><p>你也可以查看Doctrine官方文档<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html">Basic Mapping Documentation</a>关于映射信息的所有细节。如果你使用annotations，你需要所有的注释都有ORM（例如 ORM\Column()）,这些doctrine模板并没有。你还需要去引入use Doctrine\ORM\Mapping as ORM;声明，它是用来引进ORM注册前缀的。</p>
<p>小心你的类名和属性很可能就被映射到一个受保护的SQL字段（如group和user）。举例，如果你的entity类名称为Group，那么，在默认情况下，你的表名为group，在一些引擎中可能导致SQL错误。请查看 <a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words">Reserved SQL keywords documentation</a>，他会告诉你如何正确的规避这些名称。另外，你可以自由简单的映射到不同的表名和字段名，来选择你的数据库纲要。请查看Doctrine的<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes">Persistent classes</a>和<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping">Property Mapping</a>文档。</p>
<p>当使用其他的库或者程序（例如 Doxygen）它们使用了注释，你应该把@IgnoreAnnotation注释添加到该类上来告诉Symfony忽略它们。</p>
<p>比如我们要阻止@fn 声明抛出异常，可以这样：</p><pre class="crayon-plain-tag">/**
 * @IgnoreAnnotation("fn")
 */
class Product
// ...</pre><p>&nbsp;</p></blockquote>
<h2>生产Getters和Setters</h2>
<p>尽管Doctrine现在知道了如何持久化Product对象到数据库，但是类本身是不是有用呢。因为Product仅仅是一个标准的PHP类，你需要创建getter和setter方法（比如getName(),setName())来访问它的属性（因为它的属性是protected)，幸运的是Doctrine可以为我们做这些：</p><pre class="crayon-plain-tag">$ php app/console doctrine:generate:entities AppBundle/Entity/Product</pre><p>该命令可以确保Product类所有的getter和setter都被生成。这是一个安全的命令行，你可以多次运行它，它只会生成那些不存在的getters和setters，而不会替换已有的。</p>
<blockquote><p>请记住doctrine entity引擎生产简单的getters/setters。你应该检查生成的实体，调整getter/setter逻辑为自己想要的。</p></blockquote>
<p>&nbsp;</p>
<blockquote>
<h2>关于doctrine:generate:entities命令</h2>
<p>用它你可以生成getters和setters。</p>
<p>用它在配置@ORM\Entity(repositoryClass=&#8221;&#8230;&#8221;)声明的情况下，生成repository类。</p>
<p>用它可以为1:n或者n:m生成合适的构造器。</p>
<p>该命令会保存一个原来Product.php文件的备份Product.php~。 有些时候可也能够会造成“不能重新声明类”错误，你可以放心的删除它，来消除错误。您还可以使用&#8211;no-backup选项，来防止产生这些配置文件。</p>
<p>当然你没有必要依赖于该命令行，Doctrine不依赖于代码生成，像标准的PHP类，你只需要保证它的protected/private属性拥有getter和setter方法即可。主要由于用命令行去创建是，一种常见事。</p></blockquote>
<p>你也可以为一个bundle或者整个实体命名空间内的所有已知实体（任何包含Doctrine映射声明的PHP类）来生成getter和setter：</p><pre class="crayon-plain-tag"># generates all entities in the AppBundle
$ php app/console doctrine:generate:entities AppBundle

# generates all entities of bundles in the Acme namespace
$ php app/console doctrine:generate:entities Acme</pre><p></p>
<blockquote><p> Doctrine不关心你的属性是protected还是private，或者这些属性是否有getter或setter。之所以生成这些getter或者setter完全是因为你需要跟你的PHP对象进行交流需要它们。</p></blockquote>
<p>&nbsp;</p>
<h2>创建数据库表和模式</h2>
<p>现在我们有了一个可用的Product类和它的映射信息，所以Doctrine知道如何持久化它。当然，现在Product还没有相应的product数据库表在数据库中。幸运的是，Doctrine可以自动创建所有的数据库表。</p><pre class="crayon-plain-tag">$ php app/console doctrine:schema:update --force</pre><p></p>
<blockquote><p> 说真的，这条命令是出奇的强大。它会基于你的entities的映射信息，来比较现在的数据库，并生成所需要的新数据库的更新SQl语句。换句话说，如果你想添加一个新的属性映射元数据到Product并运行该任务，它将生成一个alert table 语句来添加新的列到已经存在的product表中。</p>
<p>一个更好的发挥这一优势的功能是通过migrations，它允许你生成这些SQL语句。并存储到一个迁移类，并能有组织的运行在你的生产环境中，系统为了安全可靠地跟踪和迁移数据库。</p></blockquote>
<p>现在你的数据库中有了一个全功能的product表，它的每个列都会被映射到你指定的元数据。</p>
<p>&nbsp;</p>
<h2>持久化对象到数据库</h2>
<p>现在我们有了一个Product实体和与之映射的product数据库表。你可以把数据持久化到数据库里。在Controller内，它非常简单。添加下面的方法到bundle的DefaultController中。</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/DefaultController.php

// ...
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;

// ...
public function createAction()
{
    $product = new Product();
    $product-&gt;setName('A Foo Bar');
    $product-&gt;setPrice('19.99');
    $product-&gt;setDescription('Lorem ipsum dolor');

    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $em-&gt;persist($product);
    $em-&gt;flush();

    return new Response('Created product id '.$product-&gt;getId());
}</pre><p></p>
<blockquote><p> 如果你想演示这个案例，你需要去创建一个路由指向这个action，让他工作。</p>
<p>本文展示了在控制器中使用Doctrine的getDoctrine()方法。这个方法是获取doctrine服务最便捷的方式。你能在服务中的任何其他地方使用doctrine注入该服务。更多关于常见自己的服务信息，请参阅<em><a class="reference internal" href="http://symfony.com/doc/current/book/service_container.html">Service Container</a>。</em></p></blockquote>
<p>在看看前面例子的详情：</p>
<p>在本节10-13行，你实例化$product对象，就像其他任何普通的php对象一样。</p>
<p>15行获取doctrine实体管理对象，这是负责处理数据库持久化过程和读取对象的。</p>
<p>16行persist()方法告诉Doctrine去“管理”这个$product对象。还没有在数据库中使用过语句。</p>
<p>17行党这个flush()方法被调用，Doctrine会查看它管理的所有对象，是否需要被持久化到数据库。在本例子中，这个$product对象还没有持久化，所以这个entity管理就会执行一个insert语句并且会在product表中创建一行数据。</p>
<blockquote><p>事实上，Doctrine了解你所有的被管理的实体，当你调用flush()方法时，它会计算出所有的变化，并执行最有效的查询可能。 他利用准备好的缓存略微提高性能。比如，你要持久化总是为100的产品对象，然后调用flush()方法。Doctrine会创建一个唯一的预备语句并重复使用它插入。</p></blockquote>
<p>在创建和更新对象时，工作流是相同的。在下一节中，如果记录已经存在数据库中，您将看到Doctrine如何聪明的自动发出一个Update语句。</p>
<p>Doctrine提供了一个类库允许你通过编程，加载测试数据到你的项目。该类库为DoctrineFixturesBundle(http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html)</p>
<p>&nbsp;</p>
<h2>从数据库中获取对象</h2>
<p>从数据库中获取对象更容易，举个例子，假如你配置了一个路由来，用它的ID显示特定的product。</p><pre class="crayon-plain-tag">public function showAction($id)
{
    $product = $this-&gt;getDoctrine()
        -&gt;getRepository('AppBundle:Product')
        -&gt;find($id);

    if (!$product) {
        throw $this-&gt;createNotFoundException(
            'No product found for id '.$id
        );
    }

    // ... do something, like pass the $product object into a template
}</pre><p></p>
<blockquote><p>你可以使用@ParamConverter注释不用编写任何代码就可以实现同样的功能。更多信息请查看<a class="reference external" href="http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html">FrameworkExtraBundle</a>文档。</p></blockquote>
<p>当你查询某个特定的产品时，你总是需要使用它的&#8221;respository&#8221;。你可以认为Respository是一个PHP类，它的唯一工作就是帮助你从某个特定类哪里获取实体。你可以为一个实体对象访问一个repository对象，如下：</p><pre class="crayon-plain-tag">$repository = $this-&gt;getDoctrine()
    -&gt;getRepository('AppBundle:Product');</pre><p>其中appBundle:Product是简洁写法，你可以在Doctrine中任意使用它来替代实体类的全限定名称（例如AppBundle\Entity\Product）。只要你的entity在你的bundle的Entity命名空间下它就会工作。</p>
<p>你一旦有了Repository,你就可以访问其所有分类的帮助方法了。</p><pre class="crayon-plain-tag">// query by the primary key (usually "id")
$product = $repository-&gt;find($id);

// dynamic method names to find based on a column value
$product = $repository-&gt;findOneById($id);
$product = $repository-&gt;findOneByName('foo');

// find *all* products
$products = $repository-&gt;findAll();

// find a group of products based on an arbitrary column value
$products = $repository-&gt;findByPrice(19.99);</pre><p></p>
<blockquote><p> 当然，你也可以使用复杂的查询，想了解更多请阅读<a class="reference internal" href="http://symfony.com/doc/current/book/doctrine.html#book-doctrine-queries"><em>Querying for Objects</em></a> <em>。</em></p></blockquote>
<p>你也可以有效利用findBy和findOneBy方法的优势，很容易的基于多个条件来获取对象。</p><pre class="crayon-plain-tag">// query for one product matching by name and price
$product = $repository-&gt;findOneBy(
    array('name' =&gt; 'foo', 'price' =&gt; 19.99)
);

// query for all products matching the name, ordered by price
$products = $repository-&gt;findBy(
    array('name' =&gt; 'foo'),
    array('price' =&gt; 'ASC')
);</pre><p></p>
<blockquote><p>当你去渲染页面，你可以在网页调试工具的右下角看到许多的查询。</p>
<p><img class="alignnone size-full wp-image-495" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/05/doctrine_web_debug_toolbar.png" alt="doctrine_web_debug_toolbar" width="600" height="352" /></p>
<p>如果你单机该图标，分析页面将打开，显示你的精确查询。</p>
<p>如果你的页面查询超过了50个它会变成黄色。这可能表明你的程序有问题。</p></blockquote>
<p>&nbsp;</p>
<h2></h2>
<h2>更新对象</h2>
<p>一旦你从Doctrine中获取了一个对象，那么更新它就变得很容易了。假设你有一个路由映射一个产品id到一个controller的updateaction。</p><pre class="crayon-plain-tag">public function updateAction($id)
{
    $em = $this-&gt;getDoctrine()-&gt;getManager();
    $product = $em-&gt;getRepository('AppBundle:Product')-&gt;find($id);

    if (!$product) {
        throw $this-&gt;createNotFoundException(
            'No product found for id '.$id
        );
    }

    $product-&gt;setName('New product name!');
    $em-&gt;flush();

    return $this-&gt;redirectToRoute('homepage');
}</pre><p>更新一个对象包括三步：</p>
<p>1.从Doctrine取出对象<br />
2.修改对象<br />
3.在实体管理者上调用flush()方法</p>
<p>注意调用 $em-&gt;persist($product) 在这里没有必要。我们回想一下，调用该方法的目的主要是告诉Doctrine来管理或者“观察&#8221;$product对象。在这里，因为你已经取到了$product对象了，说明已经被管理了。</p>
<p>&nbsp;</p>
<h2>删除对象</h2>
<p>删除一个对象，需要从实体管理者那里调用remove()方法。</p><pre class="crayon-plain-tag">$em-&gt;remove($product);
$em-&gt;flush();</pre><p>正如你想的那样，remove()方法告诉Doctrine你想从数据库中移除指定的实体。真正的删除查询没有被真正的执行，直到flush()方法被调用。</p>
<p>&nbsp;</p>
<h2>查询对象</h2>
<p>你已经看到了repository对象允许你执行一些基本的查询而不需要你做任何的工作。</p><pre class="crayon-plain-tag">$repository-&gt;find($id);

$repository-&gt;findOneByName('Foo');</pre><p>当然，Doctrine 也允许你使用Doctrine Query Language(DQL)写一些复杂的查询，DQL类似于SQL，只是它用于查询一个或者多个实体类的对象，而SQL则是查询一个数据库表中的行。</p>
<p>在Doctrinez中查询时，你有两种选择：写纯Doctrine查询 或者 使用Doctrine的查询创建器。</p>
<p>&nbsp;</p>
<h2></h2>
<h2>使用Doctrine&#8217;s Query Builder查询对象</h2>
<p>假设你想查询产品，需要返回价格高于19.99的产品，并且要求按价格从低到高排列。你可以使用Doctrine的QueryBuilder：</p><pre class="crayon-plain-tag">$repository = $this-&gt;getDoctrine()
    -&gt;getRepository('AppBundle:Product');

$query = $repository-&gt;createQueryBuilder('p')
    -&gt;where('p.price &gt; :price')
    -&gt;setParameter('price', '19.99')
    -&gt;orderBy('p.price', 'ASC')
    -&gt;getQuery();

$products = $query-&gt;getResult();</pre><p>QueryBuilder对象包含了创建查询的所有必须的方法。通过调用getQuery()方法，查询创建器将返回一个标准的Query对象。它跟我们直接写查询对象效果相同。</p>
<blockquote><p>记住setParameter()方法。当Doctrine工作时，外部的值，会通过“占位符”（上面例子的:price）传入，来防止SQL注入攻击。</p></blockquote>
<p>该getResult()方法返回一个结果数组。想要得到一个结果，你可以使用getSingleResult()（这个方法在没有结果时会抛出一个异常）或者getOneOrNullResult()：</p><pre class="crayon-plain-tag">$product = $query-&gt;getOneOrNullResult();</pre><p>更多Doctrine&#8217;s Query Builder的信息请阅读<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html">Query Builder</a>。</p>
<p>&nbsp;</p>
<h2>使用DQL查询对象</h2>
<p>不爱使用QueryBuilder，你还可以直接使用DQL查询：</p><pre class="crayon-plain-tag">$em = $this-&gt;getDoctrine()-&gt;getManager();
$query = $em-&gt;createQuery(
    'SELECT p
    FROM AppBundle:Product p
    WHERE p.price &gt; :price
    ORDER BY p.price ASC'
)-&gt;setParameter('price', '19.99');

$products = $query-&gt;getResult();</pre><p>如果你习惯了写SQL，那么对于DQL也应该不会感到陌生。它们之间最大的不同就是你需要思考对象，而不是数据库表行。正因为如此，所以你从AppBundle:Product选择并给它定义别名p。（你看和上面完成的结果一样）。</p>
<p>该DQL语法强大到令人难以置信，允许您轻松地在之间加入实体（稍后会介绍关系）、组等。更多信息请参阅<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html">Doctrine Query Language</a>文档。</p>
<p>&nbsp;</p>
<h2>自定义Repository类</h2>
<p>在上面你已经开始在controller中创建和使用负责的查询了。为了隔离，比阿育测试和重用这些查询，一个好的办法是为你的实体创建一个自定义的repository类并添加相关逻辑查询方法。</p>
<p>要定义repository类，首先需要在你的映射定义中添加repository类的声明：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
 */
class Product
{
    //...
}</pre><p>然后通过运行跟之前生成丢失的getter和setter方法同样的命令行，Doctrine会为你自动生成repository类。</p><pre class="crayon-plain-tag">$ php app/console doctrine:generate:entities AppBundle</pre><p>下面，添加一个新方法findAllOrderedByName() 到新生成的repository类。该方法将查询所有的Product实体，并按照字符顺序排序。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/ProductRepository.php
namespace AppBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this-&gt;getEntityManager()
            -&gt;createQuery(
                'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
            )
            -&gt;getResult();
    }
}</pre><p></p>
<blockquote><p> 在Repository类中可以通过$this-&gt;getEntityManager()方法类获取entity管理。</p></blockquote>
<p>你就可以像使用默认的方法一样使用这个新定义的方法了：</p><pre class="crayon-plain-tag">$em = $this-&gt;getDoctrine()-&gt;getManager();
$products = $em-&gt;getRepository('AppBundle:Product')
    -&gt;findAllOrderedByName();</pre><p></p>
<blockquote><p> 当使用一个自定义的repository类时，你依然可以访问原有的默认查找方法，比如find() 和findAll()等。</p></blockquote>
<p>&nbsp;</p>
<h2>实体的关系／关联</h2>
<p>假设你应用程序中的产品属于一确定的分类。这时你需要一个分类对象和一种把Product和Category对象联系在一起的方式。首先我们创建Category实体，我们最终要通过Doctrine来对其进行持久化，所以我们这里让Doctrine来帮我们创建这个类。</p><pre class="crayon-plain-tag">$ php app/console doctrine:generate:entity \
    --entity="AppBundle:Category" \
    --fields="name:string(255)"</pre><p>该命令行为你生成一个Category实体，包含id字段和name字段以及相关的getter和setter方法。</p>
<p>&nbsp;</p>
<h2>关系映射</h2>
<p>关联Category和Product两个实体，首先在Category类中创建一个products属性：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Category.php

// ...
use Doctrine\Common\Collections\ArrayCollection;

class Category
{
    // ...

    /**
     * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
     */
    protected $products;

    public function __construct()
    {
        $this-&gt;products = new ArrayCollection();
    }
}</pre><p>首先，由于一个Category对象将涉及到多个Product对象，一个products数组属性被添加到Category类保存这些Product对象。其次，这不是因为Doctrine需要它，而是因为在应用程序中为每一个Category来保存一个Product数组非常有用。</p>
<blockquote><p>代码中__construct()方法非常重要，因为Doctrine需要$products属性成为一个ArrayCollection对象，它跟数组非常类似，但会灵活一些。如果这让你感觉不舒服，不用担心。试想他是一个数组，你会欣然接受它。</p>
<p>上面注释所用的targetEntity 的值可以使用合法的命名空间引用任何实体，而不仅仅是定义在同一个类中的实体。 如果要关系一个定义在不同的类或者bundle中的实体则需要输入完全的命名空间作为目标实体。</p></blockquote>
<p>接下来，因为每个Product类可以关联一个Category对象，所有添加一个$category属性到Product类：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Product.php

// ...
class Product
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;
}</pre><p>到现在为止，我们添加了两个新属性到Category和Product类。现在告诉Doctrine来为它们生成getter和setter方法。</p><pre class="crayon-plain-tag">$ php app/console doctrine:generate:entities AppBundle</pre><p>我们先不看Doctrine的元数据，你现在有两个类Category和Product，并且拥有一个一对多的关系。该Category类包含一个数组Product对象，Product包含一个Category对象。换句话说，你已经创建了你所需要的类了。事实上把这些需要的数据持久化到数据库上是次要的。</p>
<p>现在，让我们来看看在Product类中为$category配置的元数据。它告诉Doctrine关系类是Category并且它需要保存category的id到product表的category_id字段。换句话说，相关的分类对象将会被保存到$category属性中，但是在底层，Doctrine会通过存储category的id值到product表的category_id列持久化它们的关系。</p>
<p><img class="alignnone size-full wp-image-498" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/05/doctrine_image_2.png" alt="doctrine_image_2" width="394" height="442" /></p>
<p>Category类中$product属性的元数据配置不是特别重要，它仅仅是告诉Doctrine去查找Product.category属性来计算出关系映射是什么。</p>
<p>在继续之前，一定要告诉Doctrine添加一个新的category表和product.category_id列以及新的外键。</p><pre class="crayon-plain-tag">$ php app/console doctrine:schema:update --force</pre><p></p>
<blockquote><p> 这个任务只能在开发期间使用。有一种更健壮的方法来有条理的更新你的生产数据库，请查看<a class="reference external" href="http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html">migrations</a>.</p></blockquote>
<p>&nbsp;</p>
<h2>保存相关实体</h2>
<p>现在让我们来看看Controller内的代码如何处理：</p><pre class="crayon-plain-tag">// ...

use AppBundle\Entity\Category;
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category-&gt;setName('Main Products');

        $product = new Product();
        $product-&gt;setName('Foo');
        $product-&gt;setPrice(19.99);
        $product-&gt;setDescription('Lorem ipsum dolor');
        // relate this product to the category
        $product-&gt;setCategory($category);

        $em = $this-&gt;getDoctrine()-&gt;getManager();
        $em-&gt;persist($category);
        $em-&gt;persist($product);
        $em-&gt;flush();

        return new Response(
            'Created product id: '.$product-&gt;getId()
            .' and category id: '.$category-&gt;getId()
        );
    }
}</pre><p>现在，一个单独的行被添加到category和product表中。新产品的product.categroy_id列被设置为新category表中的id的值。Doctrine会为你管理这些持久化关系。</p>
<p>&nbsp;</p>
<h2>获取相关对象</h2>
<p>当你需要获取相关的对象时，你的工作流跟以前一样。首先获取$product对象，然后访问它的相关Category。</p><pre class="crayon-plain-tag">public function showAction($id)
{
    $product = $this-&gt;getDoctrine()
        -&gt;getRepository('AppBundle:Product')
        -&gt;find($id);

    $categoryName = $product-&gt;getCategory()-&gt;getName();

    // ...
}</pre><p>在这个例子中，你首先基于产品id查询一个Product对象。他仅仅查询产品数据并把数据给$product对象。接下来，当你调用$product-&gt;getCategory()-&gt;getName() 时，Doctrine默默的为你执行了第二次查询，查找一个与该产品相关的category，它生成一个$category对象返回给你。</p>
<p><img class="alignnone size-full wp-image-499" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/05/doctrine_image_3.png" alt="doctrine_image_3" width="690" height="404" /></p>
<p>重要的是你很容易的访问到了product的相关category对象。但是category的数据并不会被取出来而直到你请求category的时候。这就是延迟加载。</p>
<p>你也可以从其它方向进行查询：</p><pre class="crayon-plain-tag">public function showProductsAction($id)
{
    $category = $this-&gt;getDoctrine()
        -&gt;getRepository('AppBundle:Category')
        -&gt;find($id);

    $products = $category-&gt;getProducts();

    // ...
}</pre><p>在这种情况下，同样的事情发生了。你首先查查一个category对象，然后Doctrine制造了第二次查询来获取与之相关联的所有Product对象。只有在你调用-&gt;getProducts()时才会执行一次。 $products变量是一个通过它的category_id的值跟给定的category对象相关联的所有Product对象的集合。</p>
<p>&nbsp;</p>
<blockquote>
<h3>关系和代理类</h3>
<p>“延迟加载”成为可能，是因为Doctrine返回一个代理对象来代替真正的对象：</p><pre class="crayon-plain-tag">$product = $this-&gt;getDoctrine()
    -&gt;getRepository('AppBundle:Product')
    -&gt;find($id);

$category = $product-&gt;getCategory();

// prints "Proxies\AppBundleEntityCategoryProxy"
echo get_class($category);</pre><p>该代理对象继承了Category对象，从外表到行为都非常像category对象。所不同的是，通过这个代理对象，Doctrine可以延迟查询真正的Category对象数据，直到真正需要它时（调用$category-&gt;getName())。</p>
<p>Doctrine生成了代理对象并把它存储到cache目录中，尽管你可能从来没有发现过它。记住它这一点很重要。</p>
<p>我们可以通过join连接来一次性取出product和category数据。这时Doctrine将会返回真正的Category对象，因为不需要延迟加载。</p>
<p>&nbsp;</p></blockquote>
<h2>join相关记录</h2>
<p>在之前的我们的查询中，会产生两次查询操作，一次是获取原对象，一次是获取关联对象。</p>
<blockquote><p>请记住，你可以通过网页调试工具查看请求的所有查询。</p></blockquote>
<p>当然，如果你想一次访问两个对象，你可以通过一个join连接来避免二次查询。把下面的方法添加到ProductRepository类中：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
    $query = $this-&gt;getEntityManager()
        -&gt;createQuery(
            'SELECT p, c FROM AppBundle:Product p
            JOIN p.category c
            WHERE p.id = :id'
        )-&gt;setParameter('id', $id);

    try {
        return $query-&gt;getSingleResult();
    } catch (\Doctrine\ORM\NoResultException $e) {
        return null;
    }
}</pre><p>现在你就可以在你的controller中一次性查询一个产品对象和它关联的category对象信息了。</p><pre class="crayon-plain-tag">public function showAction($id)
{
    $product = $this-&gt;getDoctrine()
        -&gt;getRepository('AppBundle:Product')
        -&gt;findOneByIdJoinedToCategory($id);

    $category = $product-&gt;getCategory();

    // ...
}</pre><p>&nbsp;</p>
<h2>更多关联信息</h2>
<p>本节中已经介绍了一个普通的实体关联，一对多关系。对于更高级的关联和如何使用其他的关联（例如 一对一，多对一），请参见 doctrine 的<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html">Association Mapping Documentation</a>.</p>
<p>如果你使用注释，你需要预先在所有注释加<tt class="docutils literal"><code>ORM\（如ORM\OneToMany），这些在doctrine官方文档里没有。你还需要声明use Doctrine\ORM\Mapping as ORM;才能使用annotations的ORM。</code></tt></p>
<p>&nbsp;</p>
<h2>配置</h2>
<p>Doctrine是高度可配置的，但是你可能永远不用关心他们。要想了解更多关于Doctrine的配置信息，请查看<em><a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/doctrine.html">config reference</a>。</em></p>
<p>&nbsp;</p>
<h2>生命周期回调</h2>
<p>有时候你可能需要在一个实体被创建，更新或者删除的前后执行一些操作。这些操作方法处在一个实体不同的生命周期阶段，所以这些行为被称为&#8221;生命周期回调“。</p>
<p>如果你用annotations方式，开启一个生命周期回调，需要如下设置：（如果你不喜欢你也可以使用yaml和xml方式）</p><pre class="crayon-plain-tag">/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...
}</pre><p>现在你可以告诉Doctrine在任何可用的生命周期事件上来执行一个方法了。比如，假设你想在一个新的实体第一次被创建时设置创建日期列（created）为当前日期。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Product.php

/**
 * @ORM\PrePersist
 */
public function setCreatedAtValue()
{
    $this-&gt;createdAt = new \DateTime();
}</pre><p></p>
<blockquote><p>上面的例子假设你已经创建了createdAt属性（为在此处显示）。</p></blockquote>
<p>现在在实体第一次被保存时，Doctrine会自动调用这个方法使created日期自动设置为当前日期。</p>
<p>还有一些其他的生命周期事件，你可以使用它。更多生命周期事件和生命周期回调，请查看Doctrine的<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events">Lifecycle Events documentation</a>。</p>
<blockquote>
<h2>生命周期回调和事件监听</h2>
<p>注意到setCreatedValue()方法不需要接收任何参数。这是生命周期回调通常的做法和惯例：生命周期回调应该是简单方法，更关注于实体内部传输数据。比如设置一个创建/更新字段，生成一个定量值等。</p>
<p>如果你需要一些比较大的行为活动，像执行日志或者发送邮件，你应该注册一个扩展类作为事件监听器或接收器给它赋予访问所需资源的权利。想了解更多，请参阅<a class="reference internal" href="http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html"><em>How to Register Event Listeners and Subscribers</em></a>.</p></blockquote>
<p>&nbsp;</p>
<h2>Doctrine字段类型参考</h2>
<p>Doctrine配备了大量可用的字段类型。它们每一个都能映射PHP数据类型到特定的列类型，无论你使用什么数据库。对于每一个字段类型，Column都可以被进一步配置，可以设置<tt class="docutils literal"><code>length</code></tt>, <tt class="docutils literal"><code>nullable行为，name或者其他配置。想查看更多信息请参阅Doctrine的<a class="reference external" href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping">Mapping Types documentation</a>。</code></tt></p>
<p>&nbsp;</p>
<h2>总结</h2>
<p>有了Doctrine，你可以集中精力到你的对象以及怎样把它应用于你的应用程序中，而不必担心数据库持久化。因为Doctrine允许你使用任何的PHP对象保存你的数据并依靠映射元数据信息来联系一个对象到特定的数据库表。</p>
<p>尽管Doctrine围绕着一个简单的概念发展而来，但是它不可思议的强大。允许你创建复杂的查询和订阅事件，通过订阅事件你可以在整个持久化过程中执行一些不同的行为。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/491">第八章：数据库和Doctrine</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/491/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第七章：创建并使用模板</title>
		<link>http://www.newlifeclan.com/symfony/archives/483</link>
		<comments>http://www.newlifeclan.com/symfony/archives/483#comments</comments>
		<pubDate>Sat, 16 May 2015 03:19:31 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=483</guid>
		<description><![CDATA[<p>就像你知道的，controller负责处理每一个进入symfony2应用程序的请求。实际上，controlle [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/483">第七章：创建并使用模板</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>就像你知道的，controller负责处理每一个进入symfony2应用程序的请求。实际上，controller把大部分的繁重工作都委托给了其它地方，以使代码能够被测试和重用。当一个controller需要生成HTML，CSS或者其他内容时，它把这些工作给了一个模板化引擎。在本章中，您将学习如何编写功能强大的模板，用于把内容返回给用户，填充email，等等。您还将学会快捷方式，用聪明的方法扩展模板，以及如何重用模板代码。<span id="more-483"></span></p>
<blockquote><p>如何渲染模板请查看book的controller。</p></blockquote>
<p>&nbsp;</p>
<h2>模板</h2>
<p>模板是很简单的，可以生成基于文本格式（html,xml,csv,LaTex&#8230;）的任何文本文件。我们最熟悉的模板类型就是php模板-包含文字和php代码的混合模式的一个文本文件：</p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Welcome to Symfony!&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;&lt;?php echo $page_title ?&gt;&lt;/h1&gt;

        &lt;ul id="navigation"&gt;
            &lt;?php foreach ($navigation as $item): ?&gt;
                &lt;li&gt;
                    &lt;a href="&lt;?php echo $item-&gt;getHref() ?&gt;"&gt;
                        &lt;?php echo $item-&gt;getCaption() ?&gt;
                    &lt;/a&gt;
                &lt;/li&gt;
            &lt;?php endforeach ?&gt;
        &lt;/ul&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p>但是，symfony packages 有一个更加强大的模板语言Twig。Twig模板学法简明、易读，对页面设计师更友好，在许多方面比PHP模板更加强大。</p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Welcome to Symfony!&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;{{ page_title }}&lt;/h1&gt;

        &lt;ul id="navigation"&gt;
            {% for item in navigation %}
                &lt;li&gt;&lt;a href="{{ item.href }}"&gt;{{ item.caption }}&lt;/a&gt;&lt;/li&gt;
            {% endfor %}
        &lt;/ul&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p>Twig定义了三种特殊的语法：</p>
<p>{{ &#8230; }}</p>
<p>“说事”：打印一个变量或者一个表达式的值到模板。</p>
<p>{% &#8230; %}</p>
<p>“做某些事”，控制模板逻辑的标签，它用于执行比如for循环语句等。</p>
<p>{# &#8230; #}</p>
<p>“注释一些东西”：他相当于php的/* comment */语法。它用于注释单行和多行。注释的内容不会呈现在页面中。</p>
<p>twig也包含filters，他可以在模板渲染之前改变内容。下面让title变量渲染前全部大写：</p><pre class="crayon-plain-tag">{{ title|upper }}</pre><p>Twig内置就有一大群的标签(tags)和过滤器（filters)可以使用。当然你也可以根据需求扩展自己的Twig。</p>
<blockquote><p>注册一个Twig扩展非常容易，创建一个新服务并把它标记为Twig.extension 标签。</p></blockquote>
<p>正如你会在本文档中看到的一样，Twig还支持功能和新功能的添加。比如，下面使用一个标准的for标签和cycle功能函数来打印10个div 标签，用odd,even 属性代替。</p><pre class="crayon-plain-tag">{% for i in 0..10 %}
    &lt;div class="{{ cycle(['odd', 'even'], i) }}"&gt;
      &lt;!-- some HTML here --&gt;
    &lt;/div&gt;
{% endfor %}</pre><p>在本章中，模板案例将展示twig和php方式。</p>
<p>如果你不使用Twig或者禁用它，你需要通过kernel.execption事件实现自己的一个异常处理程序。</p>
<blockquote>
<h3>为什么使用twig？</h3>
<p>twig模板是为了尽量简单，不去处理php标签。这样设计：twig模板只负责呈现，而不去考虑逻辑。你用twig越多，你就越会欣赏它，并从这种方式中受益。当然，你也会被无数的网页设计者喜欢。</p>
<p>还有很多twig可以做但是php不能的事情，如空格控制、沙盒、自动html转义、手动上下文输出转义还有可以包含定制的（函数和fiters），这些只影响模板。twig包含一些小功能，使得写模板更加方便和简洁。看看下面的例子，他结合了逻辑循环，if语句：</p><pre class="crayon-plain-tag">&lt;ul&gt;
    {% for user in users if user.active %}
        &lt;li&gt;{{ user.username }}&lt;/li&gt;
    {% else %}
        &lt;li&gt;No users found&lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;</pre><p>&nbsp;</p></blockquote>
<p>&nbsp;</p>
<h2>Twig模板缓存</h2>
<p>twig是很快的。每个Twig模板被编译到原生的PHP类，它将在运行时被渲染。编译过的类被保存在app/cache/{environment}/twig 目录下（其中<tt class="docutils literal"><code>{environment}</code></tt> 是环境，如<tt class="docutils literal"><code>dev<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif">和</span></code></tt><tt class="docutils literal"><code>prod</code></tt>），并在某些情况下可以同时调试。</p>
<p>当debug模式可用时，一个twig模板如果发生改变将会被自动重新编译。这就意味着你可以在开发过程中随意的修改模板，而不必担心需要去清除内存了。</p>
<p>当debug模式被关闭时，你必须手动的清除Twig缓存目录，以便能够重新生成Twig模板。记住在部署程序时一定要做到这一点。</p>
<p>&nbsp;</p>
<h2>模板继承和布局</h2>
<p>大多数的时候，模板在项目中都有通用的元素，比如header，footer，sidebar等等。在Symfony2中，我们将采用不同的思考角度来对待这个问题。一个模板可以被另外的模板装饰。这个的工作原理跟PHP类非常像，模板继承让你可以创建一个基础”layout&#8221;模板，它包含你的站点的所有通用元素并被定义成blocks。这里的block可以比作是为PHP基类的方法。 一个子模板可以继承基础的layout模板并重写它任何一个block。</p>
<p>现在首先创建一个base layout文件：</p><pre class="crayon-plain-tag">{# app/Resources/views/base.html.twig #}
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
        &lt;title&gt;{% block title %}Test Application{% endblock %}&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="sidebar"&gt;
            {% block sidebar %}
                &lt;ul&gt;
                      &lt;li&gt;&lt;a href="/"&gt;Home&lt;/a&gt;&lt;/li&gt;
                      &lt;li&gt;&lt;a href="/blog"&gt;Blog&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            {% endblock %}
        &lt;/div&gt;

        &lt;div id="content"&gt;
            {% block body %}{% endblock %}
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p></p>
<blockquote><p> 虽然在讨论关于Twig的模板继承，从原理上其实twig和php模板之间是相同的。</p></blockquote>
<p>该模板定义了一个简单的两列式的html页面。在本例中，有三处{% block %}分别定义了<tt class="docutils literal"><code>title<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif">，</span></code></tt><tt class="docutils literal"><code>sidebar和</code></tt><tt class="docutils literal"><code>body。每个block都可以被继承它的子模板重写或者保留它现在的默认实现。该模板也能被直接渲染，只不过只是显示基础模板的定义内容。</code></tt></p>
<p>一个子模板看起来是这样的：</p><pre class="crayon-plain-tag">{# app/Resources/views/blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}My cool blog posts{% endblock %}

{% block body %}
    {% for entry in blog_entries %}
        &lt;h2&gt;{{ entry.title }}&lt;/h2&gt;
        &lt;p&gt;{{ entry.body }}&lt;/p&gt;
    {% endfor %}
{% endblock %}</pre><p>父模板由一个特殊的字符串语法表示 base.html.twig ,它表示该模板在项目的 app/Resources/views 目录下。你也可以使用逻辑名称相同的::base.html.twig。具体可查看模板名称和位置。</p>
<p>模板继承的关键字 {% extends %}标签。 该标签告诉模板引擎首先评估父模板，它会设置布局和定义多个blocks。然后是子模板，上例中父模板中定义的title和body 两个blocks将会被子模板中的定义所取代。根据blog_entries的值，输出的内容如下：</p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
        &lt;title&gt;My cool blog posts&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="sidebar"&gt;
            &lt;ul&gt;
                &lt;li&gt;&lt;a href="/"&gt;Home&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href="/blog"&gt;Blog&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;

        &lt;div id="content"&gt;
            &lt;h2&gt;My first post&lt;/h2&gt;
            &lt;p&gt;The body of the first post.&lt;/p&gt;

            &lt;h2&gt;Another post&lt;/h2&gt;
            &lt;p&gt;The body of the second post.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p>请注意，由于子模板中没有定义sidebar这个block，所以来自父模板的内容被显示出来，而没有被子模板替代。位于父模板中的{% block %}标签是默认值，如果没有被子模板重写覆盖，它将作为默认值使用。</p>
<p>只要你想，你可以根据你的需要进行多层继承。 Symfony2项目中一般使用一种三层继承模式来组织模板和页面。</p>
<p>当我们使用模板继承时，需要注意：</p>
<ul>
<li>　　如果在模板中使用{% extends %}，那么它必须是模板的第一个标签。</li>
<li>　　你父模板中{% block %}越多越好，记住，子模板不必定义父模板中所有的block。你父模板中block定义的越多，你的布局就越灵活。</li>
<li>　　如果你发现在多个模板中有重复的内容，这可能就意味着你需要为该内容在父模板中定义一个{% block %}。有些时候更好的解决方案可能是把这些内容放到一个新模板中，然后在该模板中include它。</li>
<li>　　如果你需要从父模板中获取一个block的内容，你可以使用{{ parent() }}函数。如果你只是想在父模板上添加新的内容，而不是覆盖它，这就非常有用了：</li>
</ul>
<p></p><pre class="crayon-plain-tag">{% block sidebar %}
    &lt;h3&gt;Table of Contents&lt;/h3&gt;

    {# ... #}

    {{ parent() }}
{% endblock %}</pre><p>&nbsp;</p>
<h2>模板的命名和存储位置</h2>
<p>默认情况下，模板可以被保存到两个不同的位置上：</p>
<p>app/Resources/views</p>
<p>这个应用程序的<tt class="docutils literal"><code>views</code></tt> 目录可以存放整个应用程序级的基础模板（你应用程序的布局和bundle的模板）以及那些重写bundle模板的模板（查看 <a class="reference internal" href="http://symfony.com/doc/current/book/templating.html#overriding-bundle-templates"><em>Overriding Bundle Templates</em></a>）。</p>
<p>path/to/bundle/Resources/views</p>
<p>每个第三方bundle的模板都会放在他自己的Resources/views/目录（或者子目录）。当你打算分享你的bundle，你应该把它放在bundle模板中，而不是app/目录。</p>
<p>更多的时候，你会把模板放到app/Resources/views/目录下。这个目录还可以创建相对目录。例如，去渲染和继承app/Resources/views/base.html.twig，你需要使用base.html.twig的路径，并且去渲染app/Resources/views/blog/index.html.twig时，你需要使用blog/index.html.twig路径。</p>
<p>&nbsp;</p>
<h2>在Bundle中引入模板</h2>
<p>Symfony2使用bundle:directory:filename字符串语法表示模板。这可以表示许多不同类型的模板，每种都存放在特定的路径下：<br />
AcmeBlogBundle:Blog:index.html.twig 用于指定一个特定页面的模板。字符串分为三个部分，每个部分由冒号（:）隔开，含义如下：</p>
<p>AcmeBlogBundle ：（bundle）说明模板位于AcmeBlogBundle，比如src/Acme/BlogBundle。</p>
<p>Blog：（目录）指定模板位于Resourcs/views的Blog子目录中。</p>
<p>index.html.twig：（文件名）文件的实际名称为index.html.twig。</p>
<p>假设AcmeBlogBundle位于src/Acme/BlogBundle, 最终的路径将是：src/Acme/BlogBundle/Resources/views/Blog/index.html.twig</p>
<p>AcmeBlogBundle::layout.html.twig 该表示法指向AcmeBlogBundle的父模板。没有中间的“目录”部分（如blog），所以模板应该位于AcmeBlogBundle的Resources/views/layout.html.twig。是的，该语句两个冒号，意味着没有“控制器”目录部分。<br />
在重写Bundle模板一节，你将发现位于AcmeBlogBundle的模板，是如何被app/Resources/AcmeBlogBundle/views/目录下的所有同名模板所重写的，这种方式给了我们一个有力的途径来重写供应商提供的bundle的模板。</p>
<blockquote><p>模版的命名语法看起来很熟悉－它类似于控制器的命名模式规则。</p></blockquote>
<p>&nbsp;</p>
<h2>模版后缀</h2>
<p>每个模版都有两个扩展名，来指定格式和模版引擎。</p>
<table class="docutils" style="height: 105px" border="1" width="331">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">文件名</th>
<th class="head">格式</th>
<th class="head">引擎</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td><tt class="docutils literal"><code>blog/index.html.twig</code></tt></td>
<td>HTML</td>
<td>Twig</td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>blog/index.html.php</code></tt></td>
<td>HTML</td>
<td>PHP</td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>blog/index.css.twig</code></tt></td>
<td>CSS</td>
<td>Twig</td>
</tr>
</tbody>
</table>
<p>默认情况下，Symfony2的任何模板都可以被写成Twig或者PHP引擎的，它由后缀决定。其中后缀的前一部分(.html,.css）表示最终生成的格式。不像引擎，它是决定symfony如何解析模板，这是一个很简单的使用策略，你可以使用HTML(index.html.twig),XML(index.xml.twig)或任何其他格式作为渲染的资源。更多的细节，请阅读模版格式部分。</p>
<blockquote><p>这个“engines”变量是可以配置的，甚至可以添加一个新的引擎。请查看 模版配置 更多细节。</p></blockquote>
<p>&nbsp;</p>
<h2>标签和助手</h2>
<p>您已经了解了模板，它们如何命名以及如何使用模板继承的基础知识。最难的部分已经过去。接下来，我们将了解大量的可用工具来帮助我们执行最通用的模板任务，比如包含其他模板，链接一个页面或者包含图片。</p>
<p>Symfony2 中包含了几个专业的Twig标签和功能函数来帮助设计者更容易的完成工作。在PHP中，模板系统提供了一个可扩展的helper系统，它可以在模板上下文中提供许多有用的内容。</p>
<p>你已经看到了一些内建的Twig标签，比如{% block %}{% extends %}等，还有PHP 助手$view[&#8216;slots&#8217;]。在这里，你还会学到更多。</p>
<h3>引入其他模版</h3>
<p>你可能经常想在多个不同的页面中包含同一个模板或者代码片段。比如“新闻文章”会显示在一个应用程序的模板中，那么这一篇文章，可能会被用到文章详细页面，一个现实最流行文章的页面，或者一个最新文章的列表页面等。</p>
<p>当你需要重用一些PHP代码，你通常都是把这些代码放到一个PHP类或者函数中。同样在模板中你也可以这么做。通过把可重用的代码放到一个它自己的模板中，然后把这个模板包含到其他模板中。比如我们创建一个可重用模板如下：</p><pre class="crayon-plain-tag">{# app/Resources/views/article/article_details.html.twig #}
&lt;h2&gt;{{ article.title }}&lt;/h2&gt;
&lt;h3 class="byline"&gt;by {{ article.authorName }}&lt;/h3&gt;

&lt;p&gt;
    {{ article.body }}
&lt;/p&gt;</pre><p>在其他模板引入这个模板很简单：</p><pre class="crayon-plain-tag">{# app/Resources/views/article/list.html.twig #}
{% extends 'layout.html.twig' %}

{% block body %}
    &lt;h1&gt;Recent Articles&lt;h1&gt;

    {% for article in articles %}
        {{ include('article/article_details.html.twig', { 'article': article }) }}
    {% endfor %}
{% endblock %}</pre><p>这个模板包含使用{% include %}标签。请注意，模板名称要遵循通用方式。在article_details.html.twig模板中使用article变量，来传入值。这种情况下，你也可以避免这样做，因为在list.html.twig模板中变量也可以在article_details.html.twig中使用（除非你设置with_context为false）。</p>
<p>{&#8216;article':article}语法是标准Twig哈希映射的写法。如果我们需要传递多个元素，可以写成{&#8216;foo': foo, &#8216;bar': bar}。</p>
<p>&nbsp;</p>
<p>嵌入Controllers</p>
<p>在某些情况下，你需要做的不止包含一个简单的模板。假设你有一个菜单栏sidebar在你的布局文件中来显示最新的文章。获取三篇最新文章可能需要查询数据库或者执行其它包含很多逻辑的操作，这样就不能在一个模板中完成了。</p>
<p>这种情况的解决方案是简单的嵌入一个完整的controller结果到你的模板。首先，创建一个controller来渲染特定数量的最近文章：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;

// ...

class ArticleController extends Controller
{
    public function recentArticlesAction($max = 3)
    {
        // make a database call or other logic
        // to get the "$max" most recent articles
        $articles = ...;

        return $this-&gt;render(
            'article/recent_list.html.twig',
            array('articles' =&gt; $articles)
        );
    }
}</pre><p>这个recentList模板很简单：</p><pre class="crayon-plain-tag">{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
    &lt;a href="/article/{{ article.slug }}"&gt;
        {{ article.title }}
    &lt;/a&gt;
{% endfor %}</pre><p></p>
<blockquote><p> 请注意，这篇文章的URL被硬编码在案例中（例如 /article/*slug*）。这是不好的做法。在下一节中，您将学习如何正确做。</p></blockquote>
<p>为了能包含controller，你需要使用一个标准的字符语法来表示controller，格式类似bundle:controller:action</p><pre class="crayon-plain-tag">{# app/Resources/views/base.html.twig #}

{# ... #}
&lt;div id="sidebar"&gt;
    {{ render(controller(
        'AcmeArticleBundle:Article:recentArticles',
        { 'max': 3 }
    )) }}
&lt;/div&gt;</pre><p>你需要一个变量或者一些列信息时，你不必在模板中访问，而是考虑渲染一个controller。因为Controller能够更快的执行并且很好的提高了代码的组织和重用。当然，像所有的控制器，它们都应该“瘦身”，这意味着尽可能多的代码应该被做成可重用的服务。</p>
<p>&nbsp;</p>
<h2>异步内容hinclude.js</h2>
<p>使用hinclude.js-JavaScript库能够异步嵌入Controllers。这个嵌入的内容来自其他页面（或者控制器），symfony使用一个标准版本的render函数去配置hinclude标签：</p><pre class="crayon-plain-tag">{{ render_hinclude(controller('...')) }}
{{ render_hinclude(url('...')) }}</pre><p></p>
<blockquote><p> hinclude.js需要包含在你的网页中才能工作。</p>
<p>当使用一个控制器作为一个URL，你启用symfony的fragments配置：</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    # ...
    fragments: { path: /_fragment }</pre><p>
</p></blockquote>
<p>可以应用程序配置中设置全局默认内容（正在加载或者禁用javascript）：</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    # ...
    templating:
        hinclude_default_template: hinclude.html.twig</pre><p>你能够定义每一个默认模板的render函数（覆盖任何已经定义的全局默认模板）：</p><pre class="crayon-plain-tag">{{ render_hinclude(controller('...'),  {
    'default': 'default/content.html.twig'
}) }}</pre><p>或者你也可以指定一个字符串，以现实默认内容：</p><pre class="crayon-plain-tag">{{ render_hinclude(controller('...'), {'default': 'Loading...'}) }}</pre><p>&nbsp;</p>
<h2></h2>
<h2>页面<strong>链接</strong></h2>
<p>在你的应用程序中创建一个链接到其它页面对于一个模板来说是再普通不过的事情了。我们采用path Twig函数基于路由配置来生成URL而非在模板中硬编码URL。以后如果你想修改一个特定页面的URL，你只需要改变路由配置即可，模板将自动生成新的URL。</p>
<p>比如我们打算链接到&#8221;_welcome&#8221;页面，首先定义其路由配置：</p><pre class="crayon-plain-tag"># app/config/routing.yml
_welcome:
    path:     /
    defaults: { _controller: AppBundle:Welcome:index }</pre><p>我们可以在模板中使用Twig的path函数来配置这个路由生成该页面的链接。</p><pre class="crayon-plain-tag">&lt;a href="{{ path('_welcome') }}"&gt;Home&lt;/a&gt;</pre><p>正如预期的那样，它生成了URL /。现在，弄一个更复杂的路由：</p><pre class="crayon-plain-tag"># app/config/routing.yml
article_show:
    path:     /article/{slug}
    defaults: { _controller: AppBundle:Article:show }</pre><p>这种情况下，你需要指定路由名称(article_show)还要一个参数值{slug}。现在让我们再来看上面的recentList模板 ，我们修改以前的硬编码而使用path链接正确的文章。</p><pre class="crayon-plain-tag">{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
    &lt;a href="{{ path('article_show', {'slug': article.slug}) }}"&gt;
        {{ article.title }}
    &lt;/a&gt;
{% endfor %}</pre><p>你能够使用Twig的URL生成绝对路径：</p><pre class="crayon-plain-tag">&lt;a href="{{ url('_welcome') }}"&gt;Home&lt;/a&gt;</pre><p>PHP代码模板中，你需要给generate()方法传入第三个参数true 来实现生成绝对路径：</p><pre class="crayon-plain-tag">&lt;a href="&lt;?php echo $view['router']-&gt;generate(
    '_welcome',
    array(),
    true
) ?&gt;"&gt;Home&lt;/a&gt;</pre><p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Assets链接</h2>
<p>模板通常也需要一些图片，Javascript，样式文件和其它资产。当然你可以硬编码它们的路径。比如/images/logo.png。 但是Symfony2 提供了一个更加动态的Twig函数 asset()。</p><pre class="crayon-plain-tag">&lt;img src="{{ asset('images/logo.png') }}" alt="Symfony!" /&gt;

&lt;link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" /&gt;</pre><p>asset函数的主要目的是让你的应用程序更加便捷。如果你的应用程序在你的主机根目录下(比如:http://example.com),那么它会渲染出的路径为 /images/logo.png 。但是如果你的应用程序位于一个子目录中(比如：http://example.com/my_app），这时它会为你渲染出的路径变为 /my_app/images/logo.png 。asset函数能够根据你的应用程序来生成正确的路径。</p>
<p>另外，如果你使用asset函数，symfony可以自动追加一个查询字符串到你的资产，以保证被更新的静态资源不会在部署时被缓存。比如：/images/logo.png 可能看起来是 /images/logo.png?v2 。想了解更多内容，请参阅<em><span class="goog-text-highlight"><a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/framework.html#ref-framework-assets-version">assets_version</a>配置选项。</span></em></p>
<blockquote><p>在一个asset-by-asset基础上设置URL版本是在symfony2.5被引入。</p></blockquote>
<p>如果你需要设置特定的资源版本，你可以设置第四个参数（或者是这个版本参数）为所需的版本：</p><pre class="crayon-plain-tag">&lt;img src="{{ asset('images/logo.png', version='3.0') }}" alt="Symfony!" /&gt;</pre><p>如果你没有设置一个版本或者设置为null，默认的软件版本（从assets_version）将被使用。如果传入false，url版本将被停用。</p>
<blockquote><p>assets绝对URL在symfony2.5被引入。</p></blockquote>
<p>如果你需要assets绝对URL，你可以设置第三个参数（或者是这个absolute参数）为true：</p><pre class="crayon-plain-tag">&lt;img src="{{ asset('images/logo.png', absolute=true) }}" alt="Symfony!" /&gt;</pre><p></p>
<blockquote><p> 私人注释：想要查看asset参数信息请阅读http://symfony.com/doc/current/reference/twig_reference.html#asset</p></blockquote>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>包含样式表和Javascript文件到Twig模板</h2>
<p>每个网站中都不可能缺少了样式表和javascript文件。在Symfony中，这些内容可以通过模板继承优势优雅的处理。</p>
<blockquote><p>这章会教你如何包含stylesheet和javaScript资源。Symfony打包了另外一个类库叫做Assetic, 它允许你对这些资源做更多的有趣操作。更多细节请查看<a class="reference internal" href="http://symfony.com/doc/current/cookbook/assetic/asset_management.html"><em>How to Use Assetic for Asset Management</em></a>.</p></blockquote>
<p>首先在你的基模板中添加两个blocks来保存你的资源，一个叫stylesheets,放在head标签里，另一个叫javascript，放在body结束标签上面一行。这些blocks将包含你整个站点所需要的所有stylesheets和javascripts。</p><pre class="crayon-plain-tag">{# app/Resources/views/base.html.twig #}
&lt;html&gt;
    &lt;head&gt;
        {# ... #}

        {% block stylesheets %}
            &lt;link href="{{ asset('css/main.css') }}" rel="stylesheet" /&gt;
        {% endblock %}
    &lt;/head&gt;
    &lt;body&gt;
        {# ... #}

        {% block javascripts %}
            &lt;script src="{{ asset('js/main.js') }}"&gt;&lt;/script&gt;
        {% endblock %}
    &lt;/body&gt;
&lt;/html&gt;</pre><p>这太简单了！但如果你想从子模板中包含一个额外的stylesheet或者javascript怎么办呢？比如假设你有一个联系页面需要包含一个contact.css样式表只用于该页面。在你的contact页面模板内部，你可以这样实现</p><pre class="crayon-plain-tag">{# app/Resources/views/contact/contact.html.twig #}
{% extends 'base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}

    &lt;link href="{{ asset('css/contact.css') }}" rel="stylesheet" /&gt;
{% endblock %}

{# ... #}</pre><p>在子模板中你只需要重写stylesheets block并把你新的样式表标签放到该块里。当然，因为你想添加到父模板该块内容中（而不是替代它们），所以你需要在此之前先使用parent()函数来获取父模板中的所有stylesheets。</p>
<p>你也可以包含资源位置到你的bundle的Resources/public 文件夹。你需要执行php app/console assets:install target [&#8211;symlink]命令，它会把文件移动到正确的位置，默认情况下的目标位置是web文件夹。</p><pre class="crayon-plain-tag">&lt;link href="{{ asset('bundles/acmedemo/css/contact.css') }}" rel="stylesheet" /&gt;</pre><p>上面代码最终结果是页面会包含main.css和contact.css样式表。</p>
<p>&nbsp;</p>
<h2>全局模板变量</h2>
<p>在每个请求中，Symfony2 将会在Twig引擎和PHP引擎默认设置一个全局模板变量app。该app变量是一个GlobalVariables实例，它将让你自动访问到应用程序一些特定的变量。比如：</p>
<p>app.security 安全上下文</p>
<p>app.user 当前用户对象</p>
<p>app.request 当前Request对象</p>
<p>app.session Session对象</p>
<p>app.environment 当前应用程序的环境(dev,prod等）</p>
<p>app.debug 如果是true说明是调试模式，false则不是。</p><pre class="crayon-plain-tag">&lt;p&gt;Username: {{ app.user.username }}&lt;/p&gt;
{% if app.debug %}
    &lt;p&gt;Request method: {{ app.request.method }}&lt;/p&gt;
    &lt;p&gt;Application Environment: {{ app.environment }}&lt;/p&gt;
{% endif %}</pre><p>这个全局app.security变量（或者在php模板中的$app-getSecurity()方法）在symfony2.6被弃用。使用app-user($app-getUser())或者is_granted()($view[&#8216;security&#8217;]-&gt;isGranted())来代替。</p>
<p>你也可以向其添加你自己的全局模板变量 。查看cookbook例子<a class="reference internal" href="http://symfony.com/doc/current/cookbook/templating/global_variables.html"><em>Global Variables</em></a>.</p>
<p>&nbsp;</p>
<p>配置和使用模板服务</p>
<p>Symfony2模板系统的核心是模板化引擎。这个特殊的对象负责渲染模板并返回他们正确的内容。当你在controller中渲染一个模板时，其实你是使用了模板化引擎服务。举例</p><pre class="crayon-plain-tag">return $this-&gt;render('article/index.html.twig');</pre><p>相当于</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Response;

$engine = $this-&gt;container-&gt;get('templating');
$content = $engine-&gt;render('article/index.html.twig');

return $response = new Response($content);</pre><p>该模板化引擎（服务）在Symfony2内部是预先配置好，自动工作的。当然，它也可以在应用程序的配置文件中进行配置。</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    # ...
    templating: { engines: ['twig'] }</pre><p></p>
<blockquote><p>还有几个配置选项是可用的，包含在<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/reference/configuration/framework.html">附件的配置</a></span></em>。</p></blockquote>
<p>twig强制使用webprofiler（以及许多第三方包）。</p>
<p>&nbsp;</p>
<h2>重写bundle模板</h2>
<p>Symfony2社区现在正在为他们创建和维护了很多不同内容的高质量的Bundle而感到骄傲（见KnpBundle.com）。一旦你使用第三方的bundle你可能想重写或者个性化它们的一个或者多个模板。</p>
<p>假设你已经包含了开源AcmeBlogBundle到你的项目中，比如目录是src/Acme/BlogBundle，你想重写blog列表（list)页面来按照你自己的应用程序风格个性化它。我们打开AcmeBlogBundle的Blog controller，可以发现如下内容：</p><pre class="crayon-plain-tag">public function indexAction()
{
    // some logic to retrieve the blogs
    $blogs = ...;

    $this-&gt;render(
        'AcmeBlogBundle:Blog:index.html.twig',
        array('blogs' =&gt; $blogs)
    );
}</pre><p>当AcmeBlogBundle:Blog:index.html.twig被渲染时，Symfony2 会查找两个位置来定位模板：</p>
<p>1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig<br />
2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig</p>
<p>要重写该bundle的模板，仅仅从bundle中拷贝index.html.twig 模板到app/Resources/AcmeBlogBundle/views/Blog/index.html.twig。<br />
注意，如果app/Resources/AcmeBlogBundle目录不存在，可以手工建立。之后你就可以随心所欲的个性化处理该模板了。</p>
<blockquote><p>如果你在一个新的位置添加模板，你可能需要清楚一下缓存（php app/console cache:clear），即使是在调试模式下。</p></blockquote>
<p>该逻辑同样适用于基bundle模板，假设AcmeBlogBundle中每个模板都是继承于基模板AcmeBlogBundle::layout.html.twig。Symfony2 将从下面两个地方寻找模板：<br />
1.app/Resources/AcmeBlogBundle/vews/layout.html.twig<br />
2.src/Acme/BlogBundle/Resources/views/layout.html.twig<br />
同样的，如果要重写该模板，你仅仅需要从bundle中拷贝到app/Resources/AcmeBlogBundle/views/layout.html.twig 就可以轻松个性化它了。你也可以通过bundle继承来从bundle内部重写模版。</p>
<p>如果你退一步，你会看到symfony总是从app/Resources/{BUNDLE_NAME}/views/目录下的模板开始。如果这个模板不存在，它就会到自己bundle的Resources/views目录寻找。这意味着，所有的bundle模板都能够通过正确的app/Resources子目录覆盖。</p>
<p>你还可以使用bundle继承从一个bundle内部继承模板。想了解更多，请参阅<a class="reference internal" href="http://symfony.com/doc/current/cookbook/bundles/inheritance.html"><em>How to Use Bundle Inheritance to Override Parts of a Bundle</em></a>.</p>
<p>&nbsp;</p>
<h2>重写核心模板</h2>
<p>因为Symfony2 框架本身就是一个bundle,所以其核心模板也可以按照同样的方式进行重写。比如，核心模板TwigBundle包含很多不同”execption&#8221;和“error&#8221; 的模板，你可以通过复制Resources/views/Exception 目录下每一项到app/Resources/TwigBundle/Views/Exception 目录下 。</p>
<p>&nbsp;</p>
<h2>三级继承</h2>
<p>Symfony2中一般采用三级继承来完成模板创建。她可以完美的覆盖三种不同类型的模板：<br />
首先，创建一个app/Resources/views/base.html.twig 文件，它包含你应用程序主要的布局，该模板被调用时写成 ::base.html.twig。<br />
然后，为你站点的每个部分“section”创建一个模板。比如，一个博客功能, 它有自己的一个模板blog/layout.html.twig。该模板只包含特定的元素，比如body。</p><pre class="crayon-plain-tag">{# app/Resources/views/blog/layout.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    &lt;h1&gt;Blog Application&lt;/h1&gt;

    {% block content %}{% endblock %}
{% endblock %}</pre><p>最后，是为每个页面创建一个单独的模板并继承合适的section模板。比如，为index页创建的模板 Blog/index.html.twig 用来列出所有的blog。</p><pre class="crayon-plain-tag">{# app/Resources/views/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}

{% block content %}
    {% for entry in blog_entries %}
        &lt;h2&gt;{{ entry.title }}&lt;/h2&gt;
        &lt;p&gt;{{ entry.body }}&lt;/p&gt;
    {% endfor %}
{% endblock %}</pre><p>注意该模板是继承了section模板（blog/layout.html.twig),而section模板又继承了应用程序基模板(base.html.twig)。这就是通常说的三级继承模式。</p>
<p>在你创建你的应用程序时，你可以选择这种模式，也可以为每个页面创建模板时直接继承应用程序的基模板(比如: {% extends &#8216;base.html.twig&#8217; %} 。三级继承模板模式对于一个bundle开发商来说是最好的方法，因为这样bundle使用者可以很容易的重写bundle的基模板来适合自己应用程序的基本布局。</p>
<p>&nbsp;</p>
<h2>输出转义</h2>
<p>当我们从模板中生成HTML时，总会有风险存在，比如一些模板变量可能输出一些意外的HTML或者危险的客户端代码。结果这些动态内容会打破结果页面的HTML或者允许一些恶意的访问者执行一些页面攻击。举个例子：</p><pre class="crayon-plain-tag">Hello {{ name }}</pre><p>你想象一下这个用户输入下面的代码：</p><pre class="crayon-plain-tag">&lt;script&gt;alert('hello!')&lt;/script&gt;</pre><p>如果没有任何的转义，此模板会导致弹出一个js警告框</p><pre class="crayon-plain-tag">Hello &lt;script&gt;alert('hello!')&lt;/script&gt;</pre><p>这种情况看上去，没多大害处，但是如果一个用户知道这一步，它完全有能力写一个javascript在我们未知的安全区域来执行一些恶意行为。</p>
<p>这个问题的解决办法是输出安全转义。 如果添加了输出安全转义，同样的模板会渲染出无害的内容，script标签会作为普通文本输出到屏幕上。</p><pre class="crayon-plain-tag">Hello &amp;lt;script&amp;gt;alert('helloe')&amp;lt;/script&amp;gt;</pre><p>PHP和Twig模板化系统采用了不同的方式来解决这个问题。如果你使用Twig，默认情况下是输出安全转义的，你的输出是受到保护的。如果是PHP，则输出安全转义不是自动的，需要你手工的进行处理。</p>
<h3>Twig中的输出转义</h3>
<p>如果你使用Twig模板，那么输出转义是默认的。你不需要对用户提交的输出内容进行手动保护。</p>
<p>在某些情况下，你需要关闭输出转义保护，当你渲染某个可信变量或者包含某个标签时。假设管理员用户可以编写一些代码有HTML标签的文章。默认情况下，Twig将转义文章体。</p>
<p>为了渲染正常，需要添加一个raw 过滤器：</p><pre class="crayon-plain-tag">{{ article.body|raw }}</pre><p>你也可以在{% block %}区域或者整个模板中关闭输出转义。想了解更多，请参见Twig文档<a class="reference external" href="http://twig.sensiolabs.org/doc/api.html#escaper-extension">Output Escaping</a>。</p>
<h3>PHP中输出转义</h3>
<p>在PHP中安全输出保护不是自动完成的，这就意味着除非你显示的选择来对某个输出变量进行保护，否则输出都是不安全的。我们使用view的方法escape()来对输出变量进行安全输出保护：</p><pre class="crayon-plain-tag">Hello &lt;?php echo $view-&gt;escape($name) ?&gt;</pre><p>默认情况下，escape()方法默认情况下假设变量是被渲染在一个HTML上下文中的。也就是说只针对HTML安全。 它的第二个参数让你能够改变它针对的上下文。比如需要在javascript上下文中输出某些东西，就是用 js 上下文。</p><pre class="crayon-plain-tag">ar myMsg = 'Hello &lt;?php echo $view-&gt;escape($name, 'js') ?&gt;';</pre><p>&nbsp;</p>
<h2>调试</h2>
<p>当使用php代码时，我们可以使用dump()来快速的查看一个值的变量传递。他非常有用，例如，在你的控制器里：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;

// ...

class ArticleController extends Controller
{
    public function recentListAction()
    {
        $articles = ...;
        dump($articles);

        // ...
    }
}</pre><p></p>
<blockquote><p>工具条会渲染dump函数</p></blockquote>
<p>在twig模板中使用dump函数达到相同的机制</p><pre class="crayon-plain-tag">{# app/Resources/views/article/recent_list.html.twig #}
{{ dump(articles) }}

{% for article in articles %}
    &lt;a href="/article/{{ article.slug }}"&gt;
        {{ article.title }}
    &lt;/a&gt;
{% endfor %}</pre><p>如果Twig的debug设置（config.yml）为true，这个变量才会抛出。默认情况下，这意味着变量只能</p>
<p>在dev环境抛出，而不能在prod环境。</p>
<p>&nbsp;</p>
<h2>语法检查</h2>
<p>你可以使用twig:lint命令检查twig模板语法错误：</p><pre class="crayon-plain-tag"># You can check by filename:
$ php app/console twig:lint app/Resources/views/article/recent_list.html.twig

# or by directory:
$ php app/console twig:lint app/Resources/views</pre><p>&nbsp;</p>
<h2>模板格式</h2>
<p>模板是一个渲染任何格式内容的通用的方式。大多数情况下，我们使用模板来渲染HTML内容。模板同样也能渲染想javascript,CSS,XML以及你能想象到的其它格式内容。</p>
<p>比如，同一个资源resource经常被渲染为不同的格式。把文章目录页渲染为XML，你需要在模板的名称中包含相应的格式即可。</p>
<p>XML 模板名： Article/index.xml.twig<br />
XML 模板文件名：index.xml.twig</p>
<p>事实上，这里只是命名上有了变化，而模板并没有真正的基于不同的格式渲染不同。</p>
<p>在大多数情况下，你可能想让单一的controller根据请求的格式不同渲染多个不同的格式。下面是一个通常的写法：</p><pre class="crayon-plain-tag">public function indexAction(Request $request)
{
    $format = $request-&gt;getRequestFormat();

    return $this-&gt;render('article/index.'.$format.'.twig');
}</pre><p>Request对象的getRequestFormat()方法默认返回值为html, request的格式通常是在路由时决定的。比如/contact 设置的请求格式是html,而 /contact.xml 设置的请求格式则是 XML。想了解更多请查看<em><a class="reference internal" href="http://symfony.com/doc/current/book/routing.html#advanced-routing-example">Advanced Example in the Routing chapter</a>。</em></p>
<p>创建一个包含请求格式的链接，只需要在参数哈希表中包含 _format键值即可。</p><pre class="crayon-plain-tag">&lt;a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}"&gt;
    PDF Version
&lt;/a&gt;</pre><p>&nbsp;</p>
<h2>总结</h2>
<p>Symfony2中的模板引擎是一个强大的工具，你可以用它来根据需要生成包括HTML，XML以及其它任何格式的内容。虽然模板以controller生产是一种常见的方式，但是不是必须的。controller返回的Response对象可以使用模板也可以没有模板。</p><pre class="crayon-plain-tag">// creates a Response object whose content is the rendered template
$response = $this-&gt;render('article/index.html.twig');

// creates a Response object whose content is simple text
$response = new Response('response content');</pre><p>Symfony2的模板引起非常灵活，默认情况下支持传统的PHP模板和圆滑强大的Twig模板，他们都拥有非常丰富的帮助函数来执行一些常见任务。Symfony2推荐使用Twig模板，因为它更加简洁，高效，能更好的处理继承等。</p>
<p>总体而言，在你处理模板问题的时候，它是一个强大的工具。在某些情况下，你可能不需要渲染模板，在symfony中，这绝对是没有问题的。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/483">第七章：创建并使用模板</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/483/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第十三章：安全security</title>
		<link>http://www.newlifeclan.com/symfony/archives/242</link>
		<comments>http://www.newlifeclan.com/symfony/archives/242#comments</comments>
		<pubDate>Sat, 21 Mar 2015 07:25:31 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=242</guid>
		<description><![CDATA[<p>symfony的安全系统是非常强大的，但它去设置时也可能会造成混淆。在本章中，您将学会如何一步一步的设置应用程 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/242">第十三章：安全security</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>symfony的安全系统是非常强大的，但它去设置时也可能会造成混淆。在本章中，您将学会如何一步一步的设置应用程序的安全性，从配置防火墙和你怎样拒绝这些用户进入和获取用户对象。根据你所需要的复杂程度，可能有时初始设置也很困难。但是，它一旦完成，symfony的安全系统是即灵活又可以（像你希望的一样）有乐趣的去工作。</p>
<p><span id="more-242"></span></p>
<p>由于有很多话要说，这一章主要分为以下几大内容：</p>
<p>1.开始设置security.yml(认证)；</p>
<p>2.拒绝访问你的应用(授权)；</p>
<p>3.获取当前用户对象；</p>
<p>其余还有很多小的部分（但仍然令人着迷），像注销和编译用户密码。</p>
<p>&nbsp;</p>
<h2>1）开始设置security.yml(认证)</h2>
<p>这个安全系统是配置在app/config/security.yml。默认配置是这样的：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    providers:
        in_memory:
            memory: ~

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        default:
            anonymous: ~</pre><p>这个firewalls键是你安全系统的心脏。这个dev防火墙并不重要，他只是确保symfony开发工具－像网址/_profiler和/_wdt不会被你的安全策略所阻止。</p>
<blockquote><p>你也可以匹配其他细节的请求（例如 host主机）。更多细节和案例请阅读<span style="text-decoration: underline"><span style="color: #ff0000"><em style="color: #ff0000"><a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/security/firewall_restriction.html">How to Restrict Firewalls to a Specific Request</a>。</em></span></span></p></blockquote>
<p>所有的其他URL都可以在default防火墙中处理（没有pattern键意味着它可以匹配所有URL）。你可以想象这个防火墙如同你的安全系统，因此它通常只有一个主要的防火墙是有意义的。但这并不意味着每一个URL都要求验证身份－该anonymous键重点用于完成这个功能。事实上，如果你现在直接访问首页，你可以访问成功并且你会看到你的“authenticated”为anon.。不要被Authenticated旁边的“Yes”愚弄，你只是一个匿名用户：</p>
<p><img class="alignnone size-full wp-image-245" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/01/security_anonymous_wdt.png" alt="security_anonymous_wdt" width="645" height="145" /></p>
<p>稍后您将学习如何拒绝用户访问某些URL或控制器。</p>
<blockquote><p>Security是高度可配置的，并有一个<span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/configuration/security.html" target="_blank">安全配置手册</a></span></span>，这个手册显示所有的选项和一些额外的解释。</p></blockquote>
<h2>A）如何让你的用户进行身份验证的配置</h2>
<p>防火墙的主要工作是如何配置你的用户进行身份验证。他们将使用一个登录表单？基础的Http？一个api令牌？所有上面的方式？</p>
<p>让我们从基本的http工作开始（老旧的http弹出方式）。想要激活此功能，需要添加http_basic到firewall（防火墙）下：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    firewalls:
        # ...
        default:
            anonymous: ~
            http_basic: ~</pre><p>简单吧！去试试吧，你需要要求这个用户去看一个页面中的登陆。为了让事情变的有趣，创建一个新页面 /admin。例如，如果你使用annotations，应该这样创建：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/DefaultController.php
// ...

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    /**
     * @Route("/admin")
     */
    public function adminAction()
    {
        return new Response('Admin page!');
    }
}</pre><p>下一步，添加一个access_control到security.yml需要这个用户访问网址时，先登录。</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...
    firewalls:
        # ...

    access_control:
        # require ROLE_ADMIN for /admin*
        - { path: ^/admin, roles: ROLE_ADMIN }</pre><p></p>
<blockquote><p> 你需要学习更多关于ROLE_ADMIN的东西和后面会遇到的拒绝访问方式，在后面的拒绝访问,角色和其他授权章节</p></blockquote>
<p>太好了，你现在就去访问 /admin ，你会看到HTTP Basic方式弹出登录：</p>
<p><img class="alignnone size-full wp-image-289" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/03/security_http_basic_popup.png" alt="security_http_basic_popup" width="601" height="290" /></p>
<p>但是谁能够登录？用户从哪里来呢？</p>
<blockquote><p>你要使用一个传统的form表单吗？太好了！可以看看 <em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/security/form_login_setup.html" target="_blank">How to Build a Traditional Login Form</a>。</span></span></em>还支持什么方法？请查看 <em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/configuration/security.html" target="_blank">Configuration Reference</a></span></span></em>或者<em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html" target="_blank">建设我们自己的</a></span></span></em>。</p></blockquote>
<p>&nbsp;</p>
<p>B）怎么样配置用户</p>
<p>当你输入你的用户名，symfony就需要加载用户的信息。这就是所谓的“user provider”，而你要配置他。symfony有一个内置的方式可以从<span style="text-decoration: underline"><em><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/security/entity_provider.html" target="_blank">数据库加载用户</a></span></em></span>，还可以<em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/security/custom_provider.html" target="_blank">创建自己的user provider</a></span></span></em>。</p>
<p>我们使用最简单的方式（他有很多限制），这种配置是在security.yml里用硬编码的方式来加载用户。</p>
<p>这就是所谓的“in memory” provider，但最好是把它作为一个“配置”提供者:</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    providers:
        in_memory:
            memory:
                users:
                    ryan:
                        password: ryanpass
                        roles: 'ROLE_USER'
                    admin:
                        password: kitten
                        roles: 'ROLE_ADMIN'
    # ...</pre><p>像firewalls，你能够有多个providers，但你只需要一个。如果你有多个，你可以配置一个provider来使用，你防火墙下的provider健（例如provider：in_memory）。</p>
<p>尝试使用用户名admin和密码kitten来登录。你应该看到一个错误！</p>
<blockquote><p>No encoder has been configured for account</p>
<p>&#8220;SymfonyComponentSecurityCoreUserUser&#8221;</p></blockquote>
<p>为了解决这个问题，需要添加一个encoders 健：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
    # ...</pre><p>User providers加载用户信息并且把它变成一个User对象。如果你从数据库加载用户（<a href="http://symfony.com/doc/current/cookbook/security/entity_provider.html" target="_blank">load users from the database</a>）或者使用一些其他的方式（<a href="http://symfony.com/doc/current/cookbook/security/custom_provider.html" target="_blank">some other source</a>），你需要使用你自己定义的User类。当你使用“in memory” provider时，你就要给他一个Symfony\Component\Security\Core\User\User对象。</p>
<p>无论你使用什么用户类，你都需要去告诉symfony他们的密码的编码算法是什么。在本例中，这个密码使用的是明文的，但在第二个例子中，你需要改变它成bcrypt。</p>
<p>如果你现在刷新，你就会登录进来！网页的调试工具栏会告诉你，你是谁你的角色是什么：</p>
<p><img class="alignnone size-full wp-image-292" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/03/symfony_loggedin_wdt.png" alt="symfony_loggedin_wdt" width="366" height="151" /></p>
<p>由于此URL需要ROLE_ADMIN，如果你登录的事ryan，这会拒绝你的访问。后面还有（固定URL模式（ACCESS_CONTROL））。</p>
<p>&nbsp;</p>
<h2>从数据库去加载用户</h2>
<p>如果你想通过doctrine orm加载你的用户这很容易！请参阅 <span style="text-decoration: underline"><span style="color: #ff0000"><em style="color: #ff0000"><a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/security/entity_provider.html">How to Load Security Users from the Database (the Entity Provider)</a>。</em></span></span></p>
<p>&nbsp;</p>
<p>C）加密用户密码</p>
<p>不管里security.yml里的用户存储在一个数据库或者一些其他的形式，你都需要去加密你的密码。最好的算法是使用bcrypt：</p>
<p>&nbsp;</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    encoders:
        Symfony\Component\Security\Core\User\User:
            algorithm: bcrypt
            cost: 12</pre><p>如果你使用php5.4或者更低版本，为了能够使用bcrypt加密，你就需要通过composer去安装ircmaxell/password-compat库。</p>
<p>当然，你的用户密码现在需要准确的算法加密。对于硬编码用户，你能够使用一个<em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="https://www.dailycred.com/blog/12/bcrypt-calculator" target="_blank">在线工具</a></span></span></em>，他会给你生成加密过的密码：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    providers:
        in_memory:
            memory:
                users:
                    ryan:
                        password: $2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli
                        roles: 'ROLE_USER'
                    admin:
                        password: $2a$12$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G
                        roles: 'ROLE_ADMIN'</pre><p>现在一切都将像以前一样工作。但如果你有动态的用户（例如，数据库里的用户），你怎么在数据库中已编程的方式掺入加密的密码？别担心，请看后面的“动态加密密码”。</p>
<blockquote><p>这个加密方式依赖于你的php版本，这个版本要包含hash_algos函数以及一些其他方法（例如bcrypt）算法才会正确返回。可以看看<a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/security.html"><em>Security Reference Section</em></a>中案例的encoders键。</p>
<p>还可以针对不同的用户，分别使用不同的算法。详细内容请查看<em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/named_encoders.html">How to Choose the Password Encoder Algorithm Dynamically</a>。</em></p></blockquote>
<p>&nbsp;</p>
<h2>D）配置完成！</h2>
<p>恭喜！现在您可以在security.yml文件使用http Basic和中加载用户的方式来让认证系统工作了。</p>
<p>你的下一步需要你来设置：</p>
<ul>
<li>配置用户的不同登录方法，例如一个登录表单或者一些完全自定义的方式；</li>
<li>不同的方式来加载用户，例如从数据库或者一些其他的地方；</li>
<li>学习如何拒绝访问，加载用户对象并处理角色的授权；</li>
</ul>
<p>&nbsp;</p>
<h2>2）拒绝访问，角色和其他授权</h2>
<p>现在，用户可以通过http_basic或者其他方式来登录你的应用程序。太好了！现在你需要学习如何拒绝访问并且能够与用户对象一起工作。这就要所谓的授权，他的工作是决定一个用户是否能够访问一些资源（如一个URL、一个model对象、方法调用等）。</p>
<p>授权过程分为两个不同的方面：</p>
<p>用户在登录时收到一组特定的角色（例如 ROLE_ADMIN）。</p>
<p>你添加的资源（例如url、控制器）需要特定“属性”（例如一个ROLE_ADMIN角色）才能够访问它。</p>
<blockquote><p>除了角色（例如ROLE_ADMIN），你还可以使用一些其他的属性／字符串来（例如 EDIT）保护资源或者使用voters和symfony的ACL系统去做同样意义的工作。这可能会派上用场，如果你需要检测用户A能否编辑对象B（例如，id为5的产品）。更多信息请参见<em><a class="reference internal" href="http://symfony.com/doc/current/book/security.html#security-secure-objects">Access Control Lists (ACLs): Securing individual Database Objects</a>。</em></p></blockquote>
<p>&nbsp;</p>
<h2>角色</h2>
<p>当一个用户登录，他会接收到一个设置好的角色（例如 ROLE_ADMIN）。在上面的例子中，这些都被硬编码到security.yml中。如果你正在数据库中加载用户，这些都应该存储在你表的列中。</p>
<p>所有角色你要分给一个用户已定要以ROLE_前缀为开始。否则，他们不会被symfony的安全系统认定为正常的方式（除非你正在做一些先进的事，分配一个角色FOO给一个用户，然后检查FOO在下面的例子中是行不通的）。</p>
<p>角色很简单，而且基本上都是你根据需要自己创造的字符串。例如，如果你需要开始限制访问您的网站博客部分，你可以使用ROLE_BLOG_ADMIN角色来保护。这个角色不需要在其他地方定义它－你就可以开始使用了。</p>
<blockquote><p>确保每个用户最少有一个角色，或者你的用户不需要身份验证。常见的做法就是给普通用户一个ROLE_USER角色。</p></blockquote>
<p>你还可以设置角色的层级结构，symfony会自动知道你的其他角色。</p>
<p>&nbsp;</p>
<h2>添加代码以拒绝访问</h2>
<p>有两种方式可以实现拒绝访问：</p>
<ul>
<li>在security.yml中使用access_control，允许使用URL的模式（例如 /admin/*）.这很容易，但弹性很差；</li>
<li>在你的代码中使用security.authorization_checker服务；</li>
</ul>
<h3>URL保护模式（ACCESS_CONTROL）</h3>
<p>您需要保护应用程序的一部分，最基本的方法是URL模式。早些时候你可能已经看到，任何匹配正则表达式^/admin时都需要ROLE_ADMIN角色：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...
    firewalls:
        # ...

    access_control:
        # require ROLE_ADMIN for /admin*
        - { path: ^/admin, roles: ROLE_ADMIN }</pre><p>它保护了这部分的安全，这很伟大，但是你可能还想保护你的控制器（<a class="reference internal" href="http://symfony.com/doc/current/book/security.html#book-security-securing-controller"><em>secure your individual controllers</em></a> ）。</p>
<p>&nbsp;</p>
<p>如果你需要，你可以定义多个URL模式-每一个都是正则表达式。但是，仅仅有一个会匹配。symfony会从顶部开始扫描，直到找到一个与access_control相匹配的URL，然后立刻结束。</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...
    access_control:
        - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
        - { path: ^/admin, roles: ROLE_ADMIN }</pre><p>路径使用^的意识是指定URL开始的地方去进行模式匹配。例如，一个简单的路径为/admin（不包含^）将匹配/admin/foo也将匹配/foo/admin。</p>
<blockquote>
<h3>了解access_control如何工作</h3>
<p>这个access_control部分非常强大，但是如果你不明白他的工作原理，他也很危险（因为毕竟它涉及到安全性问题）。access_control出了匹配URL还可匹配IP地址、主机名和http方法。他也可以讲用户重定向到https的URL模式上。</p>
<p>想了解这一切，你可以看看<em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/access_control.html">How Does the Security access_control Work?</a>。</em></p></blockquote>
<p>&nbsp;</p>
<h2>保护控制器和其他地方</h2>
<p>您还可以轻松拒绝来自控制器的访问：</p><pre class="crayon-plain-tag">// ...

public function helloAction($name)
{
    // The second parameter is used to specify on what object the role is tested.
    $this-&gt;denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');

    // Old way :
    // if (false === $this-&gt;get('security.authorization_checker')-&gt;isGranted('ROLE_ADMIN')) {
    //     throw $this-&gt;createAccessDeniedException('Unable to access this page!');
    // }

    // ...
}</pre><p></p>
<blockquote><p> 这个denyAccessUnlessGranted()方法在symfony2.6被引入。在此之前和现在，你都可以直接检查访问，并抛出一个AccessDeniedException（就像上面的例子）。</p></blockquote>
<blockquote><p>这个security.authorization_checker服务在symfony2.6被引入。在此之前，你必须使用security.context服务的isGranted()方法。</p></blockquote>
<p>这两种情况下，一个特殊的<a class="reference external" title="Symfony\Component\Security\Core\Exception\AccessDeniedException" href="http://api.symfony.com/2.6/Symfony/Component/Security/Core/Exception/AccessDeniedException.html">AccessDeniedException</a>异常被抛出，这最终触发了symfony内部的一个403-http响应。</p>
<p>就是这样！如果尚未登录的用户，他们会被要求登录（例如重定向到登录页面）。如果它们登录，但不具备ROLE_ADMIN角色，它们会被指定到403拒绝访问页面（可以<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/controller/error_pages.html#cookbook-error-pages-by-status-code">自定义</a></span></em>）。如果它们登录并拥有正确的角色，该代码就会被执行。</p>
<p>多亏了SensioFrameworkExtraBundle，你能够在controller中使用annotations：</p><pre class="crayon-plain-tag">// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Security("has_role('ROLE_ADMIN')")
 */
public function helloAction($name)
{
    // ...
}</pre><p>更多细节，请看<a class="reference external" href="http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html">FrameworkExtraBundle documentation</a>.</p>
<p>&nbsp;</p>
<h2>模版中的访问控制</h2>
<p>如果你想在模版中检测当前用户权限，可以使用内置的辅助函数：</p><pre class="crayon-plain-tag">{% if is_granted('ROLE_ADMIN') %}
    &lt;a href="..."&gt;Delete&lt;/a&gt;
{% endif %}</pre><p>如果你使用的这个函数没有在防火墙下面，它会抛出一个异常。再说，有一个防火墙覆盖所有URL并不是什么坏事（如本章前面讲到的）。</p>
<p>你要留心哦！这可是在你的基本布局或者在你的错误页面。因为一些symfony内部细节原因，prod环境会影响你的页面报错，你需要在这个模版中加入app.user。</p><pre class="crayon-plain-tag">{% if app.user and is_granted('ROLE_ADMIN') %}</pre><p>&nbsp;</p>
<h2>保护其他服务</h2>
<p>symfony可以像保护控制器一样保护一些其他的地方。假设你有一个服务（也就是一个php类），它的任务时发送电子邮件。不管他会在哪里使用，你都可以限制他，指定他的特定用户。</p>
<p>更多信息请参阅<a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/securing_services.html"><em>How to Secure any Service or Method in your Application</em></a>.</p>
<p>&nbsp;</p>
<p>检查用户是否登录（IS_AUTHENTICATED_FULLY）</p>
<p>当目前为止，你已经检查了基于角色的访问-它有ROLE_前缀并非配给用户。但是，如果你只是想检测用户是否登录。（不关心角色）那么你可以使用IS_AUTHENTICATED_FULLY：</p><pre class="crayon-plain-tag">// ...

public function helloAction($name)
{
    if (!$this-&gt;get('security.authorization_checker')-&gt;isGranted('IS_AUTHENTICATED_FULLY')) {
        throw $this-&gt;createAccessDeniedException();
    }

    // ...
}</pre><p></p>
<blockquote><p> 当然，你也可以使用access_control。</p></blockquote>
<p>IS_AUTHENTICATED_FULLY不是一个角色，但是他的这种行为又像是一个角色，并且每个用户成功登录后都要有。事实上，有三个这样的特殊属性：</p>
<ul>
<li>IS_AUTHENTICATED_REMEMBERED：所有登录用户中有这么一种，它们通过&#8221;remember me cookie&#8221;登录。尽管你没有使用<em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/remember_me.html">remember me functionality</a>，你依然能够检测用户是否登录。</em></li>
<li>IS_AUTHENTICATED_FULLY：类似于IS_AUTHENTICATED_REMEMBERED，但更强。用户登录只是因为&#8221;remember me cookie&#8221;，他就只拥有IS_AUTHENTICATED_REMEMBERED，不会拥有IS_AUTHENTICATED_FULLY。</li>
<li>IS_AUTHENTICATED_ANONYMOUSLY：所有用户都有（甚至是匿名用户）－当白名单的URL访问时他很有用。更多细节请查看<a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/access_control.html"><em>How Does the Security access_control Work?</em></a>.</li>
</ul>
<p>您还可以在模版中使用表达式：</p><pre class="crayon-plain-tag">{% if is_granted(expression(
    '"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())'
)) %}
    &lt;a href="..."&gt;Delete&lt;/a&gt;
{% endif %}</pre><p>关于表达式和安全性的更多细节，请参阅 <a class="reference internal" href="http://symfony.com/doc/current/cookbook/expression/expressions.html#book-security-expressions"><em>Security: Complex Access Controls with Expressions</em></a>.</p>
<p>&nbsp;</p>
<h3>Access Control Lists (ACLs):保护单个数据库对象</h3>
<p>想象一下，你正在设计一个博客，用户可以在自己的帖子里发表评论。您还希望用户能够编辑自己的评论，不用别的用户编辑。此外，作为管理员用户，您希望能够编辑所有评论。</p>
<p>要做到这一点，你有两个选择：</p>
<ul>
<li><em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/voters_data_permission.html">Voters</a> 允许用户使用业务逻辑（例如 用户可以编辑这篇文章,因为他们的创造者）去查明访问。可能会需要这个配置－他足够灵活，可以解决以上问题。</em></li>
<li><em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/acl.html">ACLs</a> 允许你在系统中创建一个数据库结构，你可以给不同的用户分配不同的访问（例如 EDIT、VIEW）组成任意对象。你可以在你系统的管理界面使用管理员用户分配自定义访问。</em></li>
</ul>
<p>在这两种情况下，你仍然要配合上面的拒绝访问方法来使用。</p>
<p>&nbsp;</p>
<h2>获取用户对象</h2>
<blockquote><p>这个security.token_storage服务在symfony2.6中被引入。在symfony2.6之前，你必须使用security.context服务的getToken()方法。</p></blockquote>
<p>认证之后，访问当前用户的用户对象就需要调用security.token_storage服务。在控制器内，这样写：</p><pre class="crayon-plain-tag">public function indexAction()
{
    if (!$this-&gt;get('security.authorization_checker')-&gt;isGranted('IS_AUTHENTICATED_FULLY')) {
        throw $this-&gt;createAccessDeniedException();
    }

    $user = $this-&gt;getUser();

    // the above is a shortcut for this
    $user = $this-&gt;get('security.token_storage')-&gt;getToken()-&gt;getUser();
}</pre><p></p>
<blockquote><p> 用户将是一个对象，该对象取决于你的<em><a class="reference internal" href="http://symfony.com/doc/current/book/security.html#security-user-providers">user provider</a>。</em></p></blockquote>
<p>现在你可以在你的对象中调用任何方法。例如，调用一个用户对象的getFirstName()方法：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Response;
// ...

public function indexAction()
{
    // ...

    return new Response('Well hi there '.$user-&gt;getFirstName());
}</pre><p>&nbsp;</p>
<h2>经常检测用户登录</h2>
<p>如果用户首次认证最重要的就是检测。如果他没有，$user就是空或者为匿名。等等，为什么呢？是的，这很怪。如果你没有登录，这个用户严格来讲应该是匿名，尽管控制器快捷方式getUser()方法会转变为null。</p>
<p>问题是这样的：你应该在使用用户前，经常检查用户是否登录并使用isGranted方法或者access_control去做：</p><pre class="crayon-plain-tag">// yay! Use this to see if the user is logged in
if (!$this-&gt;get('security.authorization_checker')-&gt;isGranted('IS_AUTHENTICATED_FULLY')) {
    throw $this-&gt;createAccessDeniedException();
}

// boo :(. Never check for the User object to see if they're logged in
if ($this-&gt;getUser()) {

}</pre><p>&nbsp;</p>
<h2>在模版中获取用户</h2>
<p>这个对象在twig模版中可以使用app.user键：</p><pre class="crayon-plain-tag">{% if is_granted('IS_AUTHENTICATED_FULLY') %}
    &lt;p&gt;Username: {{ app.user.username }}&lt;/p&gt;
{% endif %}</pre><p>&nbsp;</p>
<h2>注销</h2>
<p>通常情况下，你希望用户能够注销。幸运的是，当您激活logout配置参数后，防火墙可以帮助你自动完成：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    firewalls:
        secured_area:
            # ...
            logout:
                path:   /logout
                target: /
    # ...</pre><p>下一步，你需要去创建一个路由URL（但不需要控制器）：</p><pre class="crayon-plain-tag"># app/config/routing.yml
logout:
    path:   /logout</pre><p>就是这样，用户触发/logout（或者你自定义的path路径），symfony将取消当前用户的验证信息。</p>
<p>一旦用户被注销，它们会被重定向到已经定义的target参数路径中（例如 homepage）。</p>
<blockquote><p>如果你需要在注销后做一些有趣的事情，你可以通过success_handler键去添加一个登录成功后的处理程序这个将指向一个有<a class="reference external" title="Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface" href="http://api.symfony.com/2.6/Symfony/Component/Security/Http/Logout/LogoutSuccessHandlerInterface.html">LogoutSuccessHandlerInterface</a>接口的服务类的id。详情请查看 <a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/security.html"><em>Security Configuration Reference</em></a>.</p></blockquote>
<p>&nbsp;</p>
<h2>动态编译密码</h2>
<p>如果，你的用户存储在数据库中，你需要用户插入的密码是加密的。不管你的用户对象使用什么算法，这些算法都要从一个控制器中确定：</p><pre class="crayon-plain-tag">// whatever *your* User object is
$user = new AppBundle\Entity\User();
$plainPassword = 'ryanpass';
$encoder = $this-&gt;container-&gt;get('security.password_encoder');
$encoded = $encoder-&gt;encodePassword($user, $plainPassword);

$user-&gt;setPassword($encoded);</pre><p></p>
<blockquote><p> security.password_encoder服务在symfony2.6版本被引入。</p></blockquote>
<p>为了能够让他工作，你app/config/security.yml文件中的encoders键的配置必须是你的用户类（例如AppBundle\Entity\User）。</p>
<p>这个$encoder对象也有一个isPasswordValid方法，她需要User对象作为第一个参数、要检测的明文密码作为第二个参数。</p>
<blockquote><p>当你的一个用户去提交一个明文密码（例如 注册表单、改变密码表单），你一定有验证保证密码不能多于4096字符。详细请去阅读<a class="reference internal" href="http://symfony.com/doc/current/cookbook/doctrine/registration_form.html#cookbook-registration-password-max"><em>How to implement a simple Registration Form</em></a>.</p></blockquote>
<p>&nbsp;</p>
<h2>角色分级</h2>
<p>很多角色的用户都是关联的，你可以创建一个角色的分级来定义角色的继承关系：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]</pre><p>在上述结构中，用户ROLE_ADMIN也将有ROLE_USER角色。该<tt class="docutils literal"><code>ROLE_SUPER_ADMIN</code></tt>角色有<tt class="docutils literal"><code>ROLE_ADMIN</code></tt>，<tt class="docutils literal"><code>ROLE_ALLOWED_TO_SWITCH</code></tt> 和<tt class="docutils literal"><code>ROLE_USER</code></tt>（继承自<tt class="docutils literal"><code>ROLE_ADMIN</code></tt>）。</p>
<p>&nbsp;</p>
<h2>无状态认证</h2>
<p>默认情况下，Symfony2依赖cookie（会话）去持久化用户的安全上下文。但如果你使用证书或HTTP认证，持久化不需要每个请求都有证书可用。在那种情况下，如果不需要在两个请求之间保存什么的话，你可以激活无状态认证（意思是没有cookie被Symfony2创建）：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    firewalls:
        main:
            http_basic: ~
            stateless:  true</pre><p>如果你使用表单登录的话，即使你将stateless设置true，Symfony2也将创建一个cookie。</p>
<p>&nbsp;</p>
<h2>在依赖中检测已知的安全漏洞</h2>
<p>这个security:check命令在symfony2.5被引入。这个命令包含SensioDistributionBundle，这必须在你的应用程序中注册才能使用次命令。</p>
<p>当你的symfony项目使用大量的依赖时，其中一些可能包含安全漏洞。这就是为什么symfony要包含一个security:check命令，检查你的composer.lock文件，在你安装文件时，发现所有已知的安全漏洞</p><pre class="crayon-plain-tag">$ php app/console security:check</pre><p>定期执行此命令养成良好的习惯，发现问题尽快更新活着更换受损的依赖。在内部，这个命令使用FriendsOfPHP组织的公共<a class="reference external" href="https://github.com/FriendsOfPHP/security-advisories">security advisories database</a> 发布。</p>
<blockquote><p>这个security:check命令发现任何已知的安全漏洞代码就会终止。因此，你可以很容易在你的建设中集成。</p></blockquote>
<p>&nbsp;</p>
<h2>最后的话</h2>
<p>哇，很不错！你现在知道了很多安全的基础知识。最难的部分就是你需要定制的时候：像是定义一个认证策略（例如 api tokens），复杂的授权逻辑或者许多其他的事情（因为安全本来就很复杂！）。</p>
<p>幸运的是：在这里<a class="reference internal" href="http://symfony.com/doc/current/cookbook/security/index.html"><em>Security Cookbook Articles</em></a>有很多文章，它描述了多种情况。此外，还可以参考<em><a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/security.html">Security Reference Section</a>。许多配置没有具体的细节介绍，但是看到完整的配置树我想他可能很有帮助。</em></p>
<p>&nbsp;</p>
<p>祝你好运；</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/242">第十三章：安全security</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/242/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>第十二章：symfony2表单</title>
		<link>http://www.newlifeclan.com/symfony/archives/281</link>
		<comments>http://www.newlifeclan.com/symfony/archives/281#comments</comments>
		<pubDate>Thu, 19 Mar 2015 12:32:17 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=281</guid>
		<description><![CDATA[<p>对于一个Web开发者来说，处理HTML表单是一个最为普通又具挑战的任务。Symfony2集成了一个Form组件 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/281">第十二章：symfony2表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>对于一个Web开发者来说，处理HTML表单是一个最为普通又具挑战的任务。Symfony2集成了一个Form组件，让处理表单变的容易起来。在这一节里，我们将从基础开始创建一个复杂的表单，学习表单类库中最重要的内容。<br />
<span id="more-281"></span></p>
<blockquote><p>Symfony2 的Form组件是一个独立的类库，你可以在Symfony2项目之外使用它。</p></blockquote>
<h2>创建一个简单的表单</h2>
<p>假设你正在构建一个简单的待办事项列表，来显示一些‘任务’。你需要创建一个表单来让你的用户能够编辑和创建任务。在这之前，先来看看Task类，他用来存储任务数据。</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Task.php
namespace AppBundle\Entity;

class Task
{
    protected $task;
    protected $dueDate;

    public function getTask()
    {
        return $this-&gt;task;
    }

    public function setTask($task)
    {
        $this-&gt;task = $task;
    }

    public function getDueDate()
    {
        return $this-&gt;dueDate;
    }

    public function setDueDate(\DateTime $dueDate = null)
    {
        $this-&gt;dueDate = $dueDate;
    }
}</pre><p>该类是一个普通的PHP对象类，因为他们没有任何Symfony或者其它类库引用。它是非常简单的一个PHP对象类，它直接解决了你程序中task的数据问题。当然，在本节的最后，你将能够通过HTML表单提交一个Task实例数据，校验它的数值，并把它持久化到数据库。</p>
<h2>构建表单</h2>
<p>现在我们已经有了一个Task类，下一步就是创建和渲染一个真正的html表单了。再symfony2中，它是通过创建一个表单对象来渲染到模版的。现在，我们要在controller内部处理form表单：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    public function newAction(Request $request)
    {
        // create a task and give it some dummy data for this example
        $task = new Task();
        $task-&gt;setTask('Write a blog post');
        $task-&gt;setDueDate(new \DateTime('tomorrow'));

        $form = $this-&gt;createFormBuilder($task)
            -&gt;add('task', 'text')
            -&gt;add('dueDate', 'date')
            -&gt;add('save', 'submit', array('label' =&gt; 'Create Task'))
            -&gt;getForm();

        return $this-&gt;render('default/new.html.twig', array(
            'form' =&gt; $form-&gt;createView(),
        ));
    }
}</pre><p></p>
<blockquote><p> 这个例子说明了如何在控制器中直接建立自己的form表单。在后面，“创建表单类”我们将使用一个独立的类来构建form表单，这样可以让表单可以重用，我们推荐这样做。</p>
<p>&nbsp;</p></blockquote>
<p>因为Symfony2通过一个表单生成器“form builder&#8221;来创建表单对象，所以你可以使用很少的代码就能完成创建表单任务。表单生成器的目的是让你能编写简单的表单创建方法，让它来负责繁重的创建任务。</p>
<p>在这个示例中，你已经添加了两个字段到你的表单，一个是task一个是dueDate。它们关联到Task类的task和dueDate属性。你已经为它们分别指定了类型（比如，text，date等），由这些类型来决定为这些字段生成什么样的HTML表单标签。</p>
<p>最后，你增加了一个提交按钮并自定义了label名称。</p>
<blockquote><p>2.3时提交按钮被引入到symfony。在此之前，你必须要手动添加按钮到html页面。</p></blockquote>
<p>symfony附带了很多的内建form类型（<span style="color: #ff0000"><em><span style="text-decoration: underline"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/forms/types.html">Form类型手册</a></span></em></span>）</p>
<p>&nbsp;</p>
<h2>渲染一个表单</h2>
<p>表单创建后，下一步就是渲染它。这是通过传递一个特定的表单”view&#8221;对象（就是上例中的 $form-&gt;createView()返回的view对象）到你的模板并通过一系列的表单帮助函数来实现的。</p><pre class="crayon-plain-tag">{# app/Resources/views/default/new.html.twig #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}</pre><p><img class="alignnone size-full wp-image-282" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/03/form-simple.png" alt="form-simple" width="187" height="114" /></p>
<blockquote><p>这个示例假设你提交的POST请求和现在的URL是相同。后面我们将教您如何改变请求的方法和表单提交的URL。</p></blockquote>
<p>就是这样！只需要短短三行就可以渲染完整的form表单：</p>
<p>form_start(form)</p>
<p>呈现出form表单的开始标签，包含当文件上传时配置的正确enctype属性。</p>
<p>form_widget(form)</p>
<p>呈现出所有的字段，其中包括字段本身，一个label和一些验证的错误信息。</p>
<p>form_end(form)</p>
<p>当你自己生成每个字段时可能会有遗漏，这个函数就是生成表单结束标签，以及表单中所有没有被生成的字段的。这个生成隐藏字段时，以及在利用自动CSRF保护机制时，将会非常有用。</p>
<blockquote><p>是不是很简单，但是他并不很灵活。通常情况下，我们渴望单独渲染表单中的每一个字段，这样我们可以更好的控制表单的样式。我们会在在模板中渲染表单一节介绍。</p></blockquote>
<p>在继续下去之前，我们注意到，为什么我们渲染出来的task输入框中有一个来自$task对象的属性值“Write a blog post&#8221;。这是表单的第一个工作：从一个对象中获取数据并把它转换为合适的格式渲染到一个HTML表单中。</p>
<blockquote><p>注意，表单系统已经足够聪明，它们能够通过像getTask()和setTask()方法来访问Task类中受保护的属性task。除非一个是公共属性，否则必须有一个getter和setter方法被定义来用于表单组件从这些属性中获取和保持数据。对于布尔型的属性，你可以使用一个”isser&#8221;和“hasser”方法（比如 isPublished()和hasReminder())替代getter方法(getPublished()和getReminder())。</p></blockquote>
<p>&nbsp;</p>
<h2>处理表单提交</h2>
<p>表单系统的第二个任务就是传递用户提交的数据回到一个对象的属性中。要做到这一点，用户提交的数据必须绑定到表单才行。添加如下代码到你的Controller类：</p><pre class="crayon-plain-tag">// ...
use Symfony\Component\HttpFoundation\Request;

public function newAction(Request $request)
{
    // just setup a fresh $task object (remove the dummy data)
    $task = new Task();

    $form = $this-&gt;createFormBuilder($task)
        -&gt;add('task', 'text')
        -&gt;add('dueDate', 'date')
        -&gt;add('save', 'submit', array('label' =&gt; 'Create Task'))
        -&gt;getForm();

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        // perform some action, such as saving the task to the database

        return $this-&gt;redirectToRoute('task_success');
    }

    // ...
}</pre><p></p>
<blockquote><p>在symfony2.3中handleRequest()被引入进来。在此之前，这个$request会被传入到submit方法-这种方式已经弃用了，会在symfony3.0被彻底移除。有关这个方法的详细信息，请参见<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/form/direct_submit.html#cookbook-form-submit-request" target="_blank">传递一个$request到Form::submit()（以弃用）</a></span></em>。</p></blockquote>
<p>controller一般会遵循一个通用的模式来处理表单，它有三个可能的途径：<br />
1.当在浏览器初始加载一个页面时，请求方法是GET，表单处理仅仅是创建和渲染。handleRequest()承认表单没有提交并且没有变化。如果表单没有提交，isValid()返回false。<br />
2.当用户提交表单，handleRequest()会识别这些并立即将提交数据返回到$task对象的task和duedate属性。然后对这个对象进行验证。如果它是无效的（验证在下一章）isVaild()会返回false，所以该form错误验证会一起渲染。</p>
<blockquote><p>无论提交的数据是否有效，你都能使用isSubmitted()检测表单是否提交。</p></blockquote>
<p>3.当用户提交的表单带有的数据均合法时，提交的数据会被再次写入到表单，但这一次isValid()返回ture。表单绑定并且在页面跳转之前你有机会去使用数据去执行一些业务逻辑活动，比如持久化它$task对象到数据库）。</p>
<blockquote><p>用户提交表单后重定向可以防止用户的浏览器刷新并转发数据。</p>
<p>当表单提交或者数据传递时，你需要更多的控制，你就可以使用submit().更多请查看<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/form/direct_submit.html#cookbook-form-call-submit-directly" target="_blank">cookbook</a></span></em>。</p>
<p>&nbsp;</p></blockquote>
<h2>多个按钮提交表单</h2>
<blockquote><p>再symfony2.3中form中可以支持按钮了。</p></blockquote>
<p>当你的表单支持多个提交按钮，你需要检测哪个按钮被点击了，去挑战控制器程序逻辑。现在我们要再添加一个“Save and add”按钮到表单：</p><pre class="crayon-plain-tag">$form = $this-&gt;createFormBuilder($task)
    -&gt;add('task', 'text')
    -&gt;add('dueDate', 'date')
    -&gt;add('save', 'submit', array('label' =&gt; 'Create Task'))
    -&gt;add('saveAndAdd', 'submit', array('label' =&gt; 'Save and Add'))
    -&gt;getForm();</pre><p>再你的控制器中使用isClicked()方法来判断“Save and add”是否被点击：</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    // ... perform some action, such as saving the task to the database

    $nextAction = $form-&gt;get('saveAndAdd')-&gt;isClicked()
        ? 'task_new'
        : 'task_success';

    return $this-&gt;redirectToRoute($nextAction);
}</pre><p>&nbsp;</p>
<h2>表单验证</h2>
<p>在上一节中，你学会了如何让表单提交有效和无效数据。在symfony中，验证环节是在底层的对象中进行（例如Task）。换句话说，form表单合法与否不重要，主要看在表单提交数据以后，底层对象比如$task对象是否合法。调用$form-&gt;isvalid() 是一个询问底层对象是否获得合法数据的快捷方式。</p>
<p>校验是通过添加一些列规则（约束）到一个类来完成的。我们给Task类添加规则和约束，使它的task属性不能为空，duDate字段不能空并且是一个合法的DateTime对象。</p><pre class="crayon-plain-tag">// AppBundle/Entity/Task.php
use Symfony\Component\Validator\Constraints as Assert;

class Task
{
    /**
     * @Assert\NotBlank()
     */
    public $task;

    /**
     * @Assert\NotBlank()
     * @Assert\Type("\DateTime")
     */
    protected $dueDate;
}</pre><p>就是这样了，如果你现在再提交包含非法数据的表单，你将会看到相应的错误被打印在表单上。</p>
<blockquote>
<h3>html5验证</h3>
<p>作为HTML5，许多浏览器都加强了客户端某些校验约束。最常用的校验活动是在一个必须的字段上渲染一个required属性。对于支持HTML5的浏览器来说，如果用户此时提交一个空字段到表单时，浏览器会显示提示信息。</p>
<p>生成的表单广泛吸收了这些新内容的优点，通过添加一些HTML属性来监控校验。客户端校验可以通过添加novalidate属性到form标签或者formnovalidate 到提交标签而关闭。这对你想检查服务端校验规则，却被浏览器阻止时，非常有用。</p><pre class="crayon-plain-tag">{# app/Resources/views/default/new.html.twig #}
{{ form(form, {'attr': {'novalidate': 'novalidate'}}) }}</pre><p>
</p></blockquote>
<p>验证在symfony中是一个非常强大的功能，并拥有自己的<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/validation.html" target="_blank">专属章节</a></span></em>。</p>
<p>&nbsp;</p>
<h2>验证分组</h2>
<p>如果你的对象想从<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/validation.html#book-validation-validation-groups" target="_blank">验证组</a></span></em>中受益，你需要指定你的表单使用哪个校验组。</p><pre class="crayon-plain-tag">$form = $this-&gt;createFormBuilder($users, array(
    'validation_groups' =&gt; array('registration'),
))-&gt;add(...);</pre><p>如果你创建<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/forms.html#book-form-creating-form-classes" target="_blank">表单类</a></span></em>，你需要添加以下的getDefaultOptions()方法：</p><pre class="crayon-plain-tag">use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'validation_groups' =&gt; array('registration'),
    ));
}</pre><p>在这两种情况下，只有registration验证组将被用于验证底层对象。</p>
<p>&nbsp;</p>
<h2>禁用验证</h2>
<blockquote><p>在symfony2.3中validation_groups才开始可以设置为false。</p></blockquote>
<p>有时你可能想完全禁用表单验证。对于这种情况，您可以设置validation_groups为false：</p><pre class="crayon-plain-tag">use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'validation_groups' =&gt; false,
    ));
}</pre><p>需要注意的是，当你这样做，form仍将运行基本的验证，例如上传文件过大，还有表单提交的字段根本不存在等。如果你想彻底去除验证，你可以使用<em><span style="color: #ff0000"><a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-dynamic-form-modification-suppressing-form-validation">POST_SUBMIT </a></span>事件。</em></p>
<p>&nbsp;</p>
<h2>组根据提交的数据</h2>
<p>如果在验证组你需要一些高级的逻辑(例如根据提交的数据)，你可以设置validation_groups为数组：</p><pre class="crayon-plain-tag">use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'validation_groups' =&gt; array(
            'AppBundle\Entity\Client',
            'determineValidationGroups',
        ),
    ));
}</pre><p>这将在表单提交后，执行验证之前，调用Client类的静态方法determineValidationGroups()。表单对象作为一个参数传入到该方法（下面例子）。</p><pre class="crayon-plain-tag">use Acme\AcmeBundle\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'validation_groups' =&gt; function(FormInterface $form) {
            $data = $form-&gt;getData();
            if (Client::TYPE_PERSON == $data-&gt;getType()) {
                return array('person');
            }

            return array('company');
        },
    ));
}</pre><p>使用validation_groups配置去覆盖正在使用的默认验证组。如果你想要去验证实体的默认约束就必须调整选项如下：</p><pre class="crayon-plain-tag">use Acme\AcmeBundle\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'validation_groups' =&gt; function(FormInterface $form) {
            $data = $form-&gt;getData();
            if (Client::TYPE_PERSON == $data-&gt;getType()) {
                return array('Default', 'person');
            }

            return array('Default', 'company');
        },
    ));
}</pre><p>在book的<em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/validation.html#book-validation-validation-groups" target="_blank">验证组</a></span></em>章节你可以找到更多验证组和默认约束的细节。</p>
<p>&nbsp;</p>
<h2>组根据点击按钮</h2>
<blockquote><p>在symfony2.3中表单可以添加按钮</p></blockquote>
<p>当你的表单包含多个按钮，你可以根据哪个按钮提交来改变表单验证组。例如,一个表单引导,允许您前进到下一步或返回到上一步。当返回到上一步,表单的数据应该被保存,但不能验证。</p>
<p>首先，我们要添加两个按钮到窗体：</p><pre class="crayon-plain-tag">$form = $this-&gt;createFormBuilder($task)
    // ...
    -&gt;add('nextStep', 'submit')
    -&gt;add('previousStep', 'submit')
    -&gt;getForm();</pre><p>这时我们返回上一步就会运行特定的验证组。在这个例子中，如果你想去不让验证，我们就得将validation_groups选项设置为false：</p><pre class="crayon-plain-tag">$form = $this-&gt;createFormBuilder($task)
    // ...
    -&gt;add('previousStep', 'submit', array(
        'validation_groups' =&gt; false,
    ))
    -&gt;getForm();</pre><p>现在form将跳过验证约束。但是他仍将验证完整的基本约束，如检查一个上传文件太大或者你提交无效字段等。</p>
<p>&nbsp;</p>
<p>内建字段类型</p>
<p>Symfony标准版含有大量的字段类型，它们几乎涵盖了所有通用表单的字段和数据类型。</p>
<div id="text-fields" class="section">
<h3>文本字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/text.html"><em>text</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/textarea.html"><em>textarea</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/email.html"><em>email</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/integer.html"><em>integer</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/money.html"><em>money</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/number.html"><em>number</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/password.html"><em>password</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/percent.html"><em>percent</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/search.html"><em>search</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/url.html"><em>url</em></a></li>
</ul>
</div>
<div id="choice-fields" class="section">
<h3>选择字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/choice.html"><em>choice</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/entity.html"><em>entity</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/country.html"><em>country</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/language.html"><em>language</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/locale.html"><em>locale</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/timezone.html"><em>timezone</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/currency.html"><em>currency</em></a></li>
</ul>
</div>
<div id="date-and-time-fields" class="section">
<h3>日期和时间字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/date.html"><em>date</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/datetime.html"><em>datetime</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/time.html"><em>time</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/birthday.html"><em>birthday</em></a></li>
</ul>
</div>
<div id="other-fields" class="section">
<h3>其他字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/checkbox.html"><em>checkbox</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/file.html"><em>file</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/radio.html"><em>radio</em></a></li>
</ul>
</div>
<div id="field-groups" class="section">
<h3>字段组</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/collection.html"><em>collection</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/repeated.html"><em>repeated</em></a></li>
</ul>
</div>
<div id="hidden-fields" class="section">
<h3>隐藏字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/hidden.html"><em>hidden</em></a></li>
</ul>
</div>
<div id="buttons" class="section">
<h3>按钮</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/button.html"><em>button</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/reset.html"><em>reset</em></a></li>
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/submit.html"><em>submit</em></a></li>
</ul>
</div>
<div id="base-fields" class="section">
<h3>基础字段</h3>
<ul class="simple">
<li><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/form.html"><em>form</em></a></li>
</ul>
</div>
<p>当然，你也可以定义自己的字段类型。可以查看 <em><span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html" target="_blank">How to Create a Custom Form Field Type</a>。</span></em></p>
<p>&nbsp;</p>
<h3>字段类型选项</h3>
<p>每一个字段类型都有一定数量的选项用于配置。比如，dueDate字段当前被渲染成3个选择框。而日期字段可以被配置渲染成一个单一的文本框，用户可以输入字符串作为日期。</p><pre class="crayon-plain-tag">-&gt;add('dueDate', 'date', array('widget' =&gt; 'single_text'))</pre><p><img class="alignnone size-full wp-image-471" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/03/About-Downloads.png" alt="About Downloads" width="200" height="63" /></p>
<p>每个字段类型都有很多不同的选项，可以传递进来。关于字段类型的细节都可以在每个类型的文档中找到。</p>
<p>required选项:</p>
<p>最常用到的选项是required选项，它可以应用于任何字段。默认情况下它被设置为true。这就意味着支持HTML5的浏览器会使用客户端校验来判断字段是否为空。如果你不想让它发生，或者把在你的字段上把required选项设置为false，或者关闭HTML5校验。</p>
<p>设置required为true并不意味着服务端校验被应用。换句话说，如果用户提交一个空数值到该字段，它将接受这个控制除非你使用Symfony的NotBlank或者NotNull校验约束。也就是说，required选项是很好，但是服务端校验还是要继续用。<br />
label选项：<br />
表单字段可以使用label选项设置显示字符标签，可以应用于任何字段：</p><pre class="crayon-plain-tag">-&gt;add('dueDate', 'date', array(
    'widget' =&gt; 'single_text',
    'label'  =&gt; 'Due Date',
))</pre><p>这个label是一个字段也能在模版渲染表单时设置，看下文。如果你不需要label关联到你的input，你可以设置他的value为false。</p>
<p>&nbsp;</p>
<h2>字段类型猜测</h2>
<p>现在你已经添加了验证元数据到Task类，Symfony早已经了解一点关于你的字段了。如果你允许，Symfony可以猜到你的字段数据类型并为你设置它。在下面的例子中，Symfony可以根据校验规则猜测到task字段是一个标准的text字段，dueDate是date字段。</p><pre class="crayon-plain-tag">public function newAction()
{
    $task = new Task();

    $form = $this-&gt;createFormBuilder($task)
        -&gt;add('task')
        -&gt;add('dueDate', null, array('widget' =&gt; 'single_text'))
        -&gt;add('save', 'submit')
        -&gt;getForm();
}</pre><p>当你省略了add方法的第二个参数（或者你输入null)时，Symfony的猜测能力就起作用了。如果你输入一个选项数组作为第三个参数（比如上面的dueDate),那么这些选项会成为Symfony猜测的依据。如果你的表单使用了指定的验证数组，字段类型猜测器将还是要考虑所有的验证规则来综合猜测你的字段类型。</p>
<blockquote><p>如果你的表单使用特定的验证组，猜测字段类型时仍将考虑所有验证约束（包含不在这部分的约束）。</p></blockquote>
<p>&nbsp;</p>
<h2><strong>字段类型可选项猜测</strong></h2>
<p>除了猜测字段类型，Symfony还能是这猜测一些可选项字段值。</p>
<blockquote><p>当这些可选项被设置时，字段将会被渲染到特定HTML属性中，让HTML5客户端来提供验证。然而，它们不会在服务端生成相应的验证规则（例如Assert\Length）。尽管你需要手动的在服务端添加这些规则，但是这些字段类型选项还是能根据这些信息猜测到。</p></blockquote>
<p>required</p>
<p>required可以在验证规则（例如<tt class="docutils literal"><code>NotBlank<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif">和</span></code></tt><tt class="docutils literal"><code>NotNull</code></tt>）或者Doctrine元数据（例如字段是nullable）的基础上猜测到。这当你的客户端校验将自动匹配你的校验规则时很有用。</p>
<p>max_length</p>
<p>如果字段是一些列文本字段，那么max_length选项可以从验证规则（如果使用Length和Range）或者Doctrine元数据（例如字段长度）中猜到。</p>
<blockquote><p>symfony要猜测这些字段就必须要在add()方法为空或者不设置时生效。</p></blockquote>
<p>如果你喜欢改变一个猜到的数值，你可以通过在可选项数组中传递该选项来重写它。</p><pre class="crayon-plain-tag">-&gt;add('task', null, array('attr' =&gt; array('maxlength' =&gt; 4)))</pre><p>&nbsp;</p>
<h2>在模版中渲染表单</h2>
<p>到目前为止，我们已经看了一个完整的表单是如何通过一行代码被渲染的。当然，你通常需要更加灵活的渲染方式：</p><pre class="crayon-plain-tag">{# app/Resources/views/default/new.html.twig #}
{{ form_start(form) }}
    {{ form_errors(form) }}

    {{ form_row(form.task) }}
    {{ form_row(form.dueDate) }}
{{ form_end(form) }}</pre><p>你已经知道form_start()和form_end()函数，但是其他功能是做什么的呢？</p>
<p>form_errors(form)</p>
<p>渲染任何整个form的错误信息(特定字段的错误，会显示在每个字段的下面一行）。</p>
<p>form_row(form.dueDate)</p>
<p>默认情况下，为给定的字段在一个div中渲染一个文本标签，错误信息，和HTML表单部件。</p>
<p>大部分工作是由form_row帮助方法类完成的，它默认在一个div中为每个字段渲染显示标签，错误信息和HTML表单部件。在表单主题部分，你将学会如果在form_row定制不同风格。</p>
<blockquote><p>注意，你可以通过form.vars.value 来访问你的当前数据：</p><pre class="crayon-plain-tag">{{ form.vars.value.task }}</pre><p>&nbsp;</p></blockquote>
<h2>手动配置每一个字段</h2>
<p>form_row帮助器是伟大的，能让你很快的渲染你表单中的每一个字段（并且每一行可以被自定义化）。但是生活不总是那么简单的，你也可能要手动的渲染每一个字段。下面的代码呈现的样子和你使用form_row呈现的样子是一样的</p><pre class="crayon-plain-tag">{{ form_start(form) }}
    {{ form_errors(form) }}

    &lt;div&gt;
        {{ form_label(form.task) }}
        {{ form_errors(form.task) }}
        {{ form_widget(form.task) }}
    &lt;/div&gt;

    &lt;div&gt;
        {{ form_label(form.dueDate) }}
        {{ form_errors(form.dueDate) }}
        {{ form_widget(form.dueDate) }}
    &lt;/div&gt;

    &lt;div&gt;
        {{ form_widget(form.save) }}
    &lt;/div&gt;

{{ form_end(form) }}</pre><p>如果自动生成的label不是你想要的，你可以指定它：</p><pre class="crayon-plain-tag">{{ form_label(form.task, 'Task Description') }}</pre><p>一些字段类型有一些额外的渲染选项可以传入widget，一个常用的选项为attr，它允许你修改表单元素的属性。下面的示例将添加task_field class到渲染的文本输入字段：</p><pre class="crayon-plain-tag">{{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}</pre><p>如果你想手工渲染表单字段，你可以单独访问每个字段的值，比如id,name和label，这里我们获取id</p><pre class="crayon-plain-tag">{{ form.task.vars.id }}</pre><p>需要获取表单字段名称属性你需要使用full_name值：</p><pre class="crayon-plain-tag">{{ form.task.vars.full_name }}</pre><p></p>
<h3> Twig模版函数查考</h3>
<p>如果你使用twig，表单渲染功能完整文档在<em><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/twig_reference.html">reference manual</a>。</em></p>
<p>&nbsp;</p>
<h2>更改Action和表单方法</h2>
<p>到目前为止，form_start()助手被用于渲染表单的开始标记，而且我们的表单都会提交同一个URL的post请求。有时你想改变这些参数。你有很多方法。如果你的表单创建在控制器中，你就可以使用setAction()和setMethod()：</p><pre class="crayon-plain-tag">$form = $this-&gt;createFormBuilder($task)
    -&gt;setAction($this-&gt;generateUrl('target_route'))
    -&gt;setMethod('GET')
    -&gt;add('task', 'text')
    -&gt;add('dueDate', 'date')
    -&gt;add('save', 'submit')
    -&gt;getForm();</pre><p></p>
<blockquote><p>这个例子假设您已经创建了一个名为target_route的路由，指向处理表单的控制器。</p></blockquote>
<p>在创建表单类，您将学会如何移除formbuild中的代码到一个单独的类。当在controller中使用一个外部的form时，你能够把action和method作为form配置传入：</p><pre class="crayon-plain-tag">$form = $this-&gt;createForm(new TaskType(), $task, array(
    'action' =&gt; $this-&gt;generateUrl('target_route'),
    'method' =&gt; 'GET',
));</pre><p>最后，你可以通过form()和form_start()去覆盖模版中的action和method：</p><pre class="crayon-plain-tag">{# app/Resources/views/default/new.html.twig #}
{{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}</pre><p>如果这些表单方法不是GET活着POST，但是是PUT、PATCH和DELETE，那么symfony会插入一个name为_method的隐藏字段来存储方法。如果form表单提交一个普通的POST请求，symfony路由会检测_method参数并解析PUT、PATCH和DELETE请求。更多细节请阅读cookbook&#8221;<a class="reference internal" href="http://symfony.com/doc/current/cookbook/routing/method_parameters.html"><em>How to Use HTTP Methods beyond GET and POST in Routes</em></a>&#8220;章节。</p>
<p>&nbsp;</p>
<h2>创建表单类</h2>
<p>正如你看到的，一个表单可以直接在controller类中被创建和使用。然而，一个更好的做法是在一个单独的PHP类中创建表单。它可以被重用到你应用程序的任何地方。创建一个新类来保存生成task表单的逻辑：</p><pre class="crayon-plain-tag">// src/AppBundle/Form/Type/TaskType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&gt;add('task')
            -&gt;add('dueDate', null, array('widget' =&gt; 'single_text'))
            -&gt;add('save', 'submit');
    }

    public function getName()
    {
        return 'task';
    }
}</pre><p></p>
<blockquote><p>注意getName()方法将返回一个该表单类型的唯一标识。这些标识符必须在应用程序中是唯一的。除非你想要去覆盖一个内置类型，他们应该不同于默认的symfony类型并且一些类型是第三方库安装到你应用程序中的。考虑加前缀使用app_以避免冲突。</p></blockquote>
<p>这个新类包含了所有创建一个task表单所需要的内容，用于快速创建该表单。</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/DefaultController.php

// add this new use statement at the top of the class
use AppBundle\Form\Type\TaskType;

public function newAction()
{
    $task = ...;
    $form = $this-&gt;createForm(new TaskType(), $task);

    // ...
}</pre><p>这种方式可以让你的form很容易的在你的项目其他地方重用。这是创建表单最好的方式，但是最终的决定权在于你。</p>
<blockquote>
<h3>设置data_class</h3>
<p>每个表单都需要知道它底层保存数据的类名称，（比如Acme\TaskBundle\Entity\Task)。通常情况下，是根据createForm方法的第二个参数来猜测的（例如$task）。以后，当你开始嵌入表单时，这个可能就不怎么充分了，所以，通常一个好的方法是通过添加下面代码到你的表单类型类来显式的指定data_class 选项。</p><pre class="crayon-plain-tag">use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver-&gt;setDefaults(array(
        'data_class' =&gt; 'AppBundle\Entity\Task',
    ));
}</pre><p>&nbsp;</p>
<p>当你映射表单到一个对象，所有的字段都被映射。 表单的任何字段如果在映射的对象上不存在那么就会造成抛出异常。</p>
<p>在这种情况下，你需要在表单中获取字段（比如，一个“你同意这些说法吗？”复选框）将不能映射到底层对象，那么你需要设置mapped为false以避免抛出异常。</p><pre class="crayon-plain-tag">use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        -&gt;add('task')
        -&gt;add('dueDate', null, array('mapped' =&gt; false))
        -&gt;add('save', 'submit');
}</pre><p>另外，如果有任何的表单字段没有被包含着提交的数据中，那么这些字段需要显式的设置为null。</p>
<p>在controller类中我们可以访问字段数据：</p><pre class="crayon-plain-tag">$form-&gt;get('dueDate')-&gt;getData();</pre><p>此外，未映射字段的数据，也可直接修改：</p><pre class="crayon-plain-tag">$form-&gt;get('dueDate')-&gt;setData(new \DateTime());</pre><p>&nbsp;</p>
<p>&nbsp;</p></blockquote>
<p>定义你的表单作为服务</p>
<p>定义你的formType作为一个服务是很好的做法，使得它很容易在你的应用程序中使用。</p>
<p>服务和服务容器在后面的<a href="http://symfony.com/doc/current/book/service_container.html">book</a>中讲解。你后面读到后会对这一章更加清晰。</p><pre class="crayon-plain-tag"># src/AppBundle/Resources/config/services.yml
services:
    acme_demo.form.type.task:
        class: AppBundle\Form\Type\TaskType
        tags:
            - { name: form.type, alias: task }</pre><p>就是这样！现在你可以在控制器中直接使用你的formType：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/DefaultController.php
// ...

public function newAction()
{
    $task = ...;
    $form = $this-&gt;createForm('task', $task);

    // ...
}</pre><p>甚至在其他的form中使用form type；</p><pre class="crayon-plain-tag">// src/AppBundle/Form/Type/ListType.php
// ...

class ListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ...

        $builder-&gt;add('someTask', 'task');
    }
}</pre><p>阅读<em><a class="reference internal" href="http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#form-cookbook-form-field-service">Creating your Field Type as a Service</a>了解更多信息。</em></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Forms和Doctrine</h2>
<p>表单的目的是把数据从一个底层对象（例如Task）传递给一个HTML表单然后把用户提交的数据传回到原先的底层对象。因此，底层对象把数据持久化到数据库就跟表单没有任何的关系了。但是，如果你已经配置了底层类是通过Doctrine来持久化，（你已经定义了映射元数据在底层类），接下来当表单提交数据后，当表单合法后就可以持久化它了。</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    $em = $this-&gt;getDoctrine()-&gt;getManager();
    $em-&gt;persist($task);
    $em-&gt;flush();

    return $this-&gt;redirectToRoute('task_success');
}</pre><p>如果处于某种原因，你不想访问原有的$task对象，你可以从表单中直接获取数据：</p><pre class="crayon-plain-tag">$task = $form-&gt;getData();</pre><p>更多细节，请查看 <a class="reference internal" href="http://symfony.com/doc/current/book/doctrine.html"><em>Doctrine ORM chapter</em></a>.</p>
<p>在这里，关键要理解当表单跟底层对象绑定后，用户提交的数据会立刻传递给底层对象。如果你想持久化这些数据，你只需要持久化对象本身即可（已经包含了提交的数据）。</p>
<p>&nbsp;</p>
<h2>嵌入式表单</h2>
<p>通常，你可能想生成一个表单，它包含来自不同对象的字段。比如，一个注册表单可能包含属于User对象和Address对象的字段。幸运的是，这些对于form组件来说都是很容易很自然的事。</p>
<h3>嵌入一个单独对象</h3>
<p>假设每个Task属于一个Category对象，首先创建这个Category对象：</p><pre class="crayon-plain-tag">// src/AppBundle/Entity/Category.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Category
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}</pre><p>接下来，添加一个新的category属性到Task类：</p><pre class="crayon-plain-tag">// ...

class Task
{
    // ...

    /**
     * @Assert\Type(type="AppBundle\Entity\Category")
     * @Assert\Valid()
     */
    protected $category;

    // ...

    public function getCategory()
    {
        return $this-&gt;category;
    }

    public function setCategory(Category $category = null)
    {
        $this-&gt;category = $category;
    }
}</pre><p>有个Valid约束被添加到category属性。这个级联会验证相关实体。如果忽略此约束子实体就不能够进行验证。</p>
<p>现在，你的应用程序被更新，并显示一个新需求，需要创建一个表单，并可以让用户修改Category对象。</p><pre class="crayon-plain-tag">// src/AppBundle/Form/Type/CategoryType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CategoryType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder-&gt;add('name');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&gt;setDefaults(array(
            'data_class' =&gt; 'AppBundle\Entity\Category',
        ));
    }

    public function getName()
    {
        return 'category';
    }
}</pre><p>我们的最终目的是能够让用户在Task表单中修改Category对象，所以，我们需要添加一个类型为CategoryType表单类的category字段到TaskType 表单类。</p><pre class="crayon-plain-tag">use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...

    $builder-&gt;add('category', new CategoryType());
}</pre><p>这时，CategoryType就可以和TaskType类一起渲染了。</p>
<p>和呈现原来的Task字段一样呈现Category字段：</p><pre class="crayon-plain-tag">{# ... #}

&lt;h3&gt;Category&lt;/h3&gt;
&lt;div class="category"&gt;
    {{ form_row(form.category.name) }}
&lt;/div&gt;

{# ... #}</pre><p>当用户提交表单时，提交的Category字段数据被用于创建一个Category实例，然后被设置到Task实例的category字段。</p>
<p>该Category实例可以通过Task实例来访问$task-&gt;getCategory(),同时也能被持久化到数据或者用作它用。</p>
<p>&nbsp;</p>
<h2>嵌入一个表单集合</h2>
<p>你也可以将一个表单集合嵌入到一个表单（想象一个Category 表单和许多Product子表单）。它是通过一个字段类型集合类实现的。</p>
<p>想了解更多请参阅cookbook &#8220;<a class="reference internal" href="http://symfony.com/doc/current/cookbook/form/form_collections.html"><em>How to Embed a Collection of Forms</em></a>&#8220;和<em><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types/collection.html">collection</a>字段类型参考。</em></p>
<p>&nbsp;</p>
<h2>表单样式</h2>
<p>表单的每一部分渲染都是可以被个性化的自定义。你可以自由的改变每一个表单行的渲染，改变渲染错误的标志，更或者是textarea标签应该怎样显示等。没有任何限制，不同的个性化设置能用到不同的区域。</p>
<p>Symfony使用模板渲染每一个或者部分表单，比如label标签，input标签，错误信息以及任何其它内容。</p>
<p>在Twig中，每个表单片段会被一个Twig block来渲染。要个性化渲染表单，你只需要重写相应的block即可。</p>
<p>在PHP模板中，它是通过单独的模板文件来渲染表单片段的，所以你需要通过编写新的模板来替代旧的模板即可。</p>
<p>在理解了它们是怎么工作的之后，让我们来个性化form_row片段并添加一个class属性到包裹每一表单行的div元素。首先创建一个新模板文件用于存放新的标志：</p><pre class="crayon-plain-tag">{# app/Resources/views/form/fields.html.twig #}
{% block form_row %}
{% spaceless %}
    &lt;div class="form_row"&gt;
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    &lt;/div&gt;
{% endspaceless %}
{% endblock form_row %}</pre><p>field_row表单片段会在通过form_row函数渲染大部分的表单字段时使用。 要告诉你的表单组件使用你的新的field_row片段，需要添加下面的内容到你渲染的表单的模板顶部：</p><pre class="crayon-plain-tag">{# app/Resources/views/default/new.html.twig #}
{% form_theme form 'form/fields.html.twig' %}

{% form_theme form 'form/fields.html.twig' 'form/fields2.html.twig' %}

{# ... render the form #}</pre><p>其中的form_theme 标签导入前面定义的片段。换句话说，当form_row函数在模板中被调用后，它将从你的自定义主题中使用field_row 块（替代Symfony已有的field_row 块）。</p>
<p>你的个性化主题不必重写所有的块。当渲染一个你没有重写过的块时，主题引擎会找全局的主题（定义在bundle级的主题）使用。</p>
<p>在拥有多个个性化主题的情况下，它会在使用全局主题之前查找定制列表。</p>
<p>要个性化你表单的任何部分，你只需要重写相关的片段即可。下一部分你将准确知道哪些块和文件可用来重写。</p>
<p>想了解更多信息，请参阅<a class="reference internal" href="http://symfony.com/doc/current/cookbook/form/form_customization.html"><em>How to Customize Form Rendering</em></a>.</p>
<p>&nbsp;</p>
<h2>表单片段命名</h2>
<p>在symfony中，表单的每一部分都会被渲染，HTML表单元素，错误消息，显示标签等这些都是被定义在基础主题里的。它组成了一个Twig的块集合和一个PHP模板集合。</p>
<p>在Twig中，每个需要的块都被定义到一个单独的模板文件中（form_div_layout.html.twig),它们被保存在Twig Bridge里。在这个文件中，你可以看到渲染一个表单，需要的每一个block和默认的字段类型。</p>
<p>在PHP模板中，片段是单独的模板文件。 默认情况下它们位于框架bundle的Resources/views/Form 目录下。</p>
<p>每个片段名称都遵循相同的基本模式，用一个下划线（_）分为两部分，比如：</p>
<ul>
<li>field_row 用于form_row渲染大部分的字段</li>
<li>textarea_widget 用于form_widget渲染一个textarea字段类型</li>
<li>field_errors 用于form_errors渲染一个字段的错误信息</li>
</ul>
<p>每个片段都命名都遵循:type_part 模式。type部分对应被渲染的字段类型（比如textarea,checkbox,date等），而part部分对应着是什么被渲染（比如label，widget，errors等）<br />
默认情况下，有4种可能的表单part被用来渲染：</p>
<table class="docutils" border="1">
<tbody valign="top">
<tr class="row-odd">
<td><tt class="docutils literal"><code>label</code></tt></td>
<td>(e.g. <tt class="docutils literal"><code>form_label</code></tt>)</td>
<td>渲染字段标签</td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>widget</code></tt></td>
<td>(e.g. <tt class="docutils literal"><code>form_widget</code></tt>)</td>
<td>渲染字段的html显示</td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>errors</code></tt></td>
<td>(e.g. <tt class="docutils literal"><code>form_errors</code></tt>)</td>
<td>渲染错误信息</td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>row</code></tt></td>
<td>(e.g. <tt class="docutils literal"><code>form_row</code></tt>)</td>
<td>渲染字段一整行（包括label、widget、errors）</td>
</tr>
</tbody>
</table>
<p>还有其它2个part类型，分别是rows，rest,不过这两个一般不会用到。</p>
<p>通过知道字段类型(比如：textarea)和你想渲染那一部分（比如：widget)，你可以创建一个你需要重写的片段名称(比如：textarea_widget).</p>
<p>&nbsp;</p>
<h2>模板片段继承</h2>
<p>在某些情况下，你个性化的片段可能会丢失。比如，在Symfony提供的默认主题中没有提供textarea_errors片段。那么如何来渲染一个textarea字段的错误信息呢？</p>
<p>答案是通过form_errors片段。当Symfony渲染一个textarea类型的错误时，它首先查找一个textarea_errors片段，如果没有找到则会回到form_errors片段。每个field类型有一个parent type（textarea本身是text，它的父类型为form),Symfony如果没有发现本身的片段，就会转而使用父类片段。</p>
<p>所以，要重写textarea字段的errors，拷贝form_errors片段，重命名为textarea_errors并个性化它们。为所有字段重写默认的error渲染，则需要直接拷贝和个性化form_errors片段。</p>
<p>可以在<em><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types.html">form type reference</a>里找到每个字段类型的“parent”类型。</em></p>
<p>&nbsp;</p>
<h2>全局表单样式</h2>
<p>在上面的示例中，我们使用了form_theme 助手来导入自定义个的表单片段到表单。你也可以告诉Symfony在跨整个项目中导入自定义的form。</p>
<p><strong>Twig</strong></p>
<p>为了从所有之前创建的fileds.html.twig模板中自动包含个性化的block，修改你的应用程序配置文件：</p><pre class="crayon-plain-tag"># app/config/config.yml
twig:
    form_themes:
        - 'form/fields.html.twig'
    # ...</pre><p>现在在fields.html.twig模板中的任何块都可以被全局使用来定义表单输出了。</p>
<blockquote>
<h3>自定义表单输出到一个单一的Twig文件中</h3>
<p>在Twig中，你也可以个性化一个表单块在模板中</p><pre class="crayon-plain-tag">{% extends 'base.html.twig' %}

{# import "_self" as the form theme #}
{% form_theme form _self %}

{# make the form fragment customization #}
{% block form_row %}
    {# custom field row output #}
{% endblock form_row %}

{% block content %}
    {# ... #}

    {{ form_row(form.task) }}
{% endblock %}</pre><p>{% form_theme form _self %}标签允许个性化表单区块直接在使用它的模版中被定制。这个方法可以在当前需要（个性化表单）的模版中快速实现表单的定制化输出。</p></blockquote>
<p>&nbsp;</p>
<blockquote><p>注意，{% form_theme form _self %}的功能只有在继承其它模板时才能起作用，如果不是继承其它模板，则需要指出form_theme 到单独模板中。</p></blockquote>
<p><strong>php</strong></p>
<p>从以前创建的Acme/TaskBundle/Resources/views/Form 目录中所有模板，自动导入个性化模板。修改你的配置文件：</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    templating:
        form:
            resources:
                - 'Form'
# ...</pre><p>此时在Acme/TaskBundle/Resources/views/Form目录中的任何片段都可以全局范围内定义表单输出了。</p>
<p>&nbsp;</p>
<h2>CSRF 保护</h2>
<p>CSRF&#8211;Cross-site request forgery，跨站伪造请求 是恶意攻击者试图让你的合法用户在不知不觉中提交他们本不想提交的数据的一种方法。幸运的是，CSRF攻击可以通过在你的表单中使用CSRF 记号来阻止。</p>
<p>默认情况下，Symfony自动为你嵌入一个合法的CSRF令牌。这就意味着你不需要做任何事情就可以得到CSRF保护。</p>
<p>CSRF保护是通过在你的表单中添加一个隐藏字段，默认的名叫_token。它包含一个值，这个值只有你和你的用户知道。这确保了是用户而不是其它实体在提交数据。Symfony自动校验该token是否存在以及其准确性。</p>
<p>_token 字段是一个隐藏字段并且会自动的渲染，只要你在你的模板中包含了form_rest()函数。它确保了没有被渲染过的字段全部渲染出来。</p>
<p>CSRF令牌可以按照表单来个性化，比如：</p><pre class="crayon-plain-tag">use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class TaskType extends AbstractType
{
    // ...

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&gt;setDefaults(array(
            'data_class'      =&gt; 'AppBundle\Entity\Task',
            'csrf_protection' =&gt; true,
            'csrf_field_name' =&gt; '_token',
            // a unique key to help generate the secret token
            'intention'       =&gt; 'task_item',
        ));
    }

    // ...
}</pre><p>要关闭CSRF保护，设置csrf_protection 选项为false。如果想了解更多信息，请参见<em><a class="reference internal" href="http://symfony.com/doc/current/reference/configuration/framework.html#reference-framework-form">form configuration reference</a>。</em></p>
<blockquote><p>intentsion选项是可选的，但为不同的表单生成不同的令牌极大的加强了安全性。</p>
<p>CSRF令牌对于每个用户都是不同的。如果你试图缓存页面，你就需要谨慎了，因为form使用了这种保护。想了解更多信息请查看<a class="reference internal" href="http://symfony.com/doc/current/cookbook/cache/form_csrf_caching.html"><em>Caching Pages that Contain CSRF Protected Forms</em></a>.</p></blockquote>
<p>&nbsp;</p>
<h2>使用一个无底层类的表单</h2>
<p>大多数情况下，一个表单要绑定一个对象的，并且表单中所有的字段获取或者保存它们的数据到该对象属性。</p>
<p>但有时候，你可能只想使用一个没有类的表单，返回一个提交数据的数组，这个非常容易实现：</p><pre class="crayon-plain-tag">// make sure you've imported the Request namespace above the class
use Symfony\Component\HttpFoundation\Request;
// ...

public function contactAction(Request $request)
{
    $defaultData = array('message' =&gt; 'Type your message here');
    $form = $this-&gt;createFormBuilder($defaultData)
        -&gt;add('name', 'text')
        -&gt;add('email', 'email')
        -&gt;add('message', 'textarea')
        -&gt;add('send', 'submit')
        -&gt;getForm();

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        // data is an array with "name", "email", and "message" keys
        $data = $form-&gt;getData();
    }

    // ... render the form
}</pre><p>默认情况下，一个表单真的假设你想要一个数据数组而不是数据对象。</p>
<p>这里有两种方式你可以改变它的行为并绑定一个对象;</p>
<ol>
<li>当创建表单时传入一个对象（作为createFormBuilder的第一个参数或者createForm的第二个参数）。</li>
<li>在你的表单中声明data_class 选项</li>
</ol>
<p>如果以上两种方式都没有，那么表单会返回一个数组数据。在这个示例中因为$defaultData不是一个对象，又没有设置data_class选项，则$form-&gt;getData()最终返回一个数组。</p>
<blockquote><p>你也可以通过Request对象直接访问POST的值</p><pre class="crayon-plain-tag">$request-&gt;request-&gt;get('name');</pre><p>注意,大多数的情况下我们使用getData()方法是更好一点的选择。因为它返回的是经过表单框架转换过的数据。</p></blockquote>
<p>&nbsp;</p>
<h2>添加验证</h2>
<p>唯一遗漏的地方就是校验规则了，通常当你调用$form-&gt;isvalid()时，对象会调用你在类中提供的验证规则进行验证。如果你的表单被映射到一个对象（例如，你正在使用data_class配置或者对象作为参数传入到你的form），这些方法使用非常的勤。请参阅 <em><a class="reference internal" href="http://symfony.com/doc/current/book/validation.html">Validation</a>。</em></p>
<p>但如果表单没有映射到一个对象，你要检索提交过来的一个简单数组，你怎么来添加对你表单数据的约束规则呢？</p>
<p>答案是自己创建约束，然后传入到表单。整体的描述在validation章节，但是这里有一个简单的案例：</p><pre class="crayon-plain-tag">use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;

$builder
   -&gt;add('firstName', 'text', array(
       'constraints' =&gt; new Length(array('min' =&gt; 3)),
   ))
   -&gt;add('lastName', 'text', array(
       'constraints' =&gt; array(
           new NotBlank(),
           new Length(array('min' =&gt; 3)),
       ),
   ))
;</pre><p></p>
<blockquote><p> 如果你使用验证组，你需要在创建表单时引用默认组，或者你要添加在约束中设置正确的组。</p></blockquote>
<p></p><pre class="crayon-plain-tag">new NotBlank(array('groups' =&gt; array('create', 'update'))</pre><p>&nbsp;</p>
<h2>最后的思考</h2>
<p>你现在已经了解了所有建造复杂功能性的表单所需要的所有建造块。当生成表单时，记住一个表单的首要目标是从一个对象（task）把数据传递给一个HTML表单以方便用户修改它们。第二个目标就是把用户提交的数据重写提交回对象。</p>
<p>form很强大还有更多需要学习，比如如何出来文件上传和doctrine还有就是如何创建一个动态的表单（例如待办事项列表，你可以添加很多字段通过javascript提交）。多看看cookbook。此外，一定要依赖<em><a class="reference internal" href="http://symfony.com/doc/current/reference/forms/types.html">field type reference documentation</a>，因为里面包括如何使用和配置每个字段类型的例子。</em></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/281">第十二章：symfony2表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/281/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>第六章：symfony2路由器</title>
		<link>http://www.newlifeclan.com/symfony/archives/228</link>
		<comments>http://www.newlifeclan.com/symfony/archives/228#comments</comments>
		<pubDate>Fri, 26 Dec 2014 01:40:38 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=228</guid>
		<description><![CDATA[<p>对于任何严谨的web应用程序而言漂亮的URL是绝对必须的。这意味着诸如index.php?article_id [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/228">第六章：symfony2路由器</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>对于任何严谨的web应用程序而言漂亮的URL是绝对必须的。这意味着诸如index.php?article_id=57这样丑陋的URL,要被这样的/read/intro-to-symfony的URL所替代。</p>
<p><span id="more-228"></span></p>
<p>拥有灵活性是非常重要的。什么？你需要将页面的URL从/blog改为/news？你需要跟踪大量的链接以便在发生变化时更新它们？如果你使用Symfony2的路由，你根本不用担心因为这很容易。<br />
Symfony路由器允许您定义创造性的url，映射到应用程序的不同区域。在本章结束时，你将可以做到：<br />
1.创建复杂的路由到控制器<br />
2.在模板和控制器中生成URL<br />
3.从Bundles中（也可以从其它什么地方）引导路由资源<br />
4.调试你的路由</p>
<h2>路由实战</h2>
<p>一个路由是一个URL路径到一个控制器的映射。例如，你想匹配一些URL：/blog/my-post 和 /blog/all-about-symfony并且发送到一个能够查询和渲染博文的控制器上。路由只需简单的设置：</p>
<p>Annotations方式</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class BlogController extends Controller
{
    /**
     * @Route("/blog/{slug}")
     */
    public function showAction($slug)
    {
        // ...
    }
}</pre><p>YAML方式：</p><pre class="crayon-plain-tag"># app/config/routing.yml
blog_show:
    path:      /blog/{slug}
    defaults:  { _controller: AppBundle:Blog:show }</pre><p>&nbsp;</p>
<p>定义blog_show路由模式，用于匹配像/blog/*的URL，把相关参数或通配符用slug表示并传入。对于/blog/my-blog-post这样的URL，slug变量得到my-blog-post的值，并供你控制器使用。这个blog_show是一个内部名称，他没什么实际的意义就是一个唯一的标识。以后，你可用他来生成一些URL。</p>
<p>如果你不想去使用annotations，因为你不喜欢他们，或者因为你不希望依赖于<strong>SensioFrameworkExtraBundle，你也可以使用YAML，XML或者PHP。在这些格式中，_controller参数是一个特殊的键，它告诉symfony路由指定的URL应该执行哪个控制器。_controller字符串称为逻辑名。它遵循规则指向一个特定的php类和方法，<tt class="docutils literal"><code>AppBundle\Controller\BlogController::showAction方法。</code></tt></strong></p>
<p>恭喜！您刚刚创建了一个路由并把它连接到控制器。现在，当你访问/blog/my-post，showAction控制器将被执行并且$slug变量就等于my-post。</p>
<p>Symfony2路由的目标：将请求的URL映射到控制器。遵循这一目标，你将学习到各式各样的技巧，甚至使映射大多数复杂的URL变得简单。</p>
<p>&nbsp;</p>
<h2>路由：深入了解</h2>
<p>当一个请求发送到您的应用程序，它包含一个确切的“资源”的客户端请求地址。该地址被称为URL（或URI），它可以是/contact、/blog/read-me或其它任何东西。下面是一个HTTP请求的例子：</p><pre class="crayon-plain-tag">GET /blog/my-blog-post</pre><p>symfony路由系统的目的是解析url，并确定调用哪个控制器。整个过程是这样的：</p>
<p>1. 由Symfony的前端控制器（app.php）来处理请求。</p>
<p>2.symfony的核心（Kernel内核）要求路由器来检查请求。</p>
<p>3.路由将输入的URL匹配到一个特定的路由,并返回路由信息，其中包括要执行的控制器信息。</p>
<p>4.Symfony内核执行控制器并最终返回Response对象。</p>
<p><img class="alignnone size-full wp-image-235" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2014/12/request-flow.png" alt="request-flow" width="690" height="273" /></p>
<p>路由是将一个输入URL转换成特定的工具来执行控制器。<br />
<a name="creating-routes"></a></p>
<h2>创建路由</h2>
<p>Symfony从一个单一的路由配置文件中加载所有的路由到你的应用程序。这个路由配置文件通常是app/config/routing.yml,但你也可以通过应用程序配置文件将该文件放置在任何地方（包括xml或php格式的配置文件）。</p><pre class="crayon-plain-tag"># app/config/config.yml
framework:
    # ...
    router: { resource: "%kernel.root_dir%/config/routing.yml" }</pre><p></p>
<blockquote><p> 尽管所有的路由都可以从一个文件加载，但是通常的做法是包含额外的路由资源。为此，你要把外部的路由文件配置到主要路由文件中。具体信息可查看本章：包含外部路由资源。</p></blockquote>
<h2></h2>
<h2>基本的路由配置</h2>
<p>定义一个路由是容易的，一个典型的应用程序也应该有很多的路由。一个基本的路由包含两个部分：路由匹配和默认数组：</p>
<p>Annotations方式：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/MainController.php

// ...
class MainController extends Controller
{
    /**
     * @Route("/")
     */
    public function homepageAction()
    {
        // ...
    }
}</pre><p>该路由匹配首页（/）并将它映射到AcmeDemoBundle:Main:homepage控制器。_controller字符串被Symfony2转换成PHP函数去执行。这个过程在本章（控制器命名模式）中被简短提及。</p>
<p>&nbsp;</p>
<h2>带参数的路由</h2>
<p>路由系统支持很多有趣的路由写法。许多的路由都可以包含一个或者多个“参数或通配符”占位符：</p>
<p>Annotations方式</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...
class BlogController extends Controller
{
    /**
     * @Route("/blog/{slug}")
     */
    public function showAction($slug)
    {
        // ...
    }
}</pre><p>这个路径将匹配任何/blog/*的URL。更妙的是，这个{slug}占位符将自动匹配到控制器中。换句话说，如果该URL是/blog/hello-world，控制器中$slug变量的值就是hello-world。这可以用来，匹配博客文章标题的字符串。</p>
<p>然而这种方式路由将不会匹配/blog这样的URL，因为默认情况下，所有的占位符都是必填的。当然这也是可以变通的，可以在defaults数组中添加占位符（参数）的值来实现。</p>
<h2>必填和选填的参数（占位符）</h2>
<p>为了让教程更加精彩，我们添加一个新的路由为这个假想的博客应用程序显示所有可用的博文列表：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...
class BlogController extends Controller
{
    // ...

    /**
     * @Route("/blog")
     */
    public function indexAction()
    {
        // ...
    }
}</pre><p>到目前为止，该路由是尽可能的简单，它没有包含通配符（参数），也只是匹配/blog的URL。但如果你需要该路由支持分页，/blog/2显示列表的第2页呢？更新该路由，添加一个新的{page}占位符：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...

/**
 * @Route("/blog/{page}")
 */
public function indexAction($page)
{
    // ...
}</pre><p>跟之前的{slug}占位符一样，这个<tt class="docutils literal"><code>{page}值也会匹配到你的控制器中。这个值主要是用来给博客系统分页的。</code></tt></p>
<p>但是有问题！占位符缺省情况下是必须的，该路由将不再匹配简单的/blog。而要看第1页的博客，你必须要使用/blog/1这样的URL！也没有一个办法让富web应用程序去操作修改路由或者让{page}参数可选。这时symfony的路由提供了一个defaults集来配置默认值，实现这些操作：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...

/**
 * @Route("/blog/{page}", defaults={"page" = 1})
 */
public function indexAction($page)
{
    // ...
}</pre><p>通过添加page的defaults值，{page}占位符在URL中可以选填了。当URL输入/blog时路由匹配/blog/1,page参数这时默认为1。/blog/2的URL传入后，page参数为2。完美了！</p>
<table class="docutils" border="1">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">URL</th>
<th class="head">Route</th>
<th class="head">Parameters</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td><tt class="docutils literal"><code>/blog</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>1</code></tt></td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>/blog/1</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>1</code></tt></td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>/blog/2</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>2</code></tt></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<blockquote><p>当然你还可以有多个可选的占位符（如/blog/{slug}/{page}），但是如果第一个占位符是可选的，后台一切的占位符都必须是可选的。举例，<tt class="docutils literal"><code>/{page}/blog 是一个有效的路径，但是page必须是必填的（如果不是必填的那么路由会匹配之前简单的/blog,而不是匹配/{page}/blog）。</code></tt></p>
<p>这些路由在选填参数时，不会匹配带斜杠的请求（如/blog/不匹配，/blog匹配正确）。</p></blockquote>
<p>&nbsp;</p>
<h2>添加要求</h2>
<p>快速浏览一下已经创建的路由：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...
class BlogController extends Controller
{
    /**
     * @Route("/blog/{page}", defaults={"page" = 1})
     */
    public function indexAction($page)
    {
        // ...
    }

    /**
     * @Route("/blog/{slug}")
     */
    public function showAction($slug)
    {
        // ...
    }
}</pre><p>你能发现问题吗？两个路由都匹配类似/blog/*的URL。Symfony2路由总是选择它第一个匹配的（blog）路由。换句话说，该blog_show路由永远被匹配。相反，像一个/blog/my-blog-post的URL会匹配第一个（blog）路由，并返回一个my-blog-post的值给{page}参数。</p>
<table class="docutils" border="1">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">URL</th>
<th class="head">Route</th>
<th class="head">Parameters</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td><tt class="docutils literal"><code>/blog/2</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>2</code></tt></td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>/blog/my-blog-post</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>"my-blog-post"</code></tt></td>
</tr>
</tbody>
</table>
<p>这个问题的答案是增加路由的要求或者路由的条件（可查看本章<a href="#completely-customized-route-matching-with-conditions">完全自定义路由匹配条件</a>）。在本例中如何让路由/blog/{page}仅匹配{page}部分是整数时才工作。幸运的是symfony可以很方便地为每个参数添加正则表达式要求。例如：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php

// ...

/**
 * @Route("/blog/{page}", defaults={"page": 1}, requirements={"page": "\d+"})
 */
public function indexAction($page)
{
    // ...
}</pre><p>这个\d+是一个正则表达式，意思是｛page｝参数的值必须是数字。blog的路由将匹配像/blog/2这样的URL（因为2是一个数字），但它不再匹配类似/blog/my-blog-post这样的URL（因为my-blog-post不是数字）。</p>
<table class="docutils" border="1">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">URL</th>
<th class="head">Route</th>
<th class="head">Parameters</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td><tt class="docutils literal"><code>/blog/2</code></tt></td>
<td><tt class="docutils literal"><code>blog</code></tt></td>
<td><tt class="docutils literal"><code>{page}</code></tt> = <tt class="docutils literal"><code>2</code></tt></td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>/blog/my-blog-post</code></tt></td>
<td><tt class="docutils literal"><code>blog_show</code></tt></td>
<td><tt class="docutils literal"><code>{slug}</code></tt> = <tt class="docutils literal"><code>my-blog-post</code></tt></td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>/blog/2-my-blog-post</code></tt></td>
<td><tt class="docutils literal"><code>blog_show</code></tt></td>
<td><tt class="docutils literal"><code>{slug}</code></tt> = <tt class="docutils literal"><code>2-my-blog-post</code></tt></td>
</tr>
</tbody>
</table>
<blockquote><p>早些时候的路由总能赢</p>
<p>这一切意味着路由的顺序是非常重要的。如果blog_show路由在Blog博客前面的话，那么/blog/2这样的URL将匹配blog_show，而不是blog，因为blog_show中的{slug}参数没有要求。通过适当的顺序和巧妙的要求，你可以完成任何事情。</p></blockquote>
<p>由于参数要求是正则表达式，每个要求的复杂程度和灵活性都完全由你控制。假定你应用程序的网页URL基于两个不同语言中使用：</p>
<p>Annotations方式</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/MainController.php

// ...
class MainController extends Controller
{
    /**
     * @Route("/{_locale}", defaults={"_locale": "en"}, requirements={"_locale": "en|fr"})
     */
    public function homepageAction($_locale)
    {
    }
}</pre><p>根据传入的请求，{_locale}的URL部分要匹配正则表达式(en|fr)。</p>
<table class="docutils" border="1">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">Path</th>
<th class="head">Parameters</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td><tt class="docutils literal"><code>/</code></tt></td>
<td><tt class="docutils literal"><code>{_locale}</code></tt> = <tt class="docutils literal"><code>"en"</code></tt></td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>/en</code></tt></td>
<td><tt class="docutils literal"><code>{_locale}</code></tt> = <tt class="docutils literal"><code>"en"</code></tt></td>
</tr>
<tr class="row-even">
<td><tt class="docutils literal"><code>/fr</code></tt></td>
<td><tt class="docutils literal"><code>{_locale}</code></tt> = <tt class="docutils literal"><code>"fr"</code></tt></td>
</tr>
<tr class="row-odd">
<td><tt class="docutils literal"><code>/es</code></tt></td>
<td><em>won&#8217;t match this route</em></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<h2>添加HTTP方法的要求</h2>
<p>除了URL，你也可以把匹配的方法传入请求（如GET、HEAD、POST、PUT、DELETE）。假设你一个联系人表单有两个控制器，一个显示表单（GET请求）一个处理提交的表单（POST请求），那么它可以通过以下路由配置来实现：</p>
<p>Annotations方式</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/MainController.php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
// ...

class MainController extends Controller
{
    /**
     * @Route("/contact")
     * @Method("GET")
     */
    public function contactAction()
    {
        // ... display contact form
    }

    /**
     * @Route("/contact")
     * @Method("POST")
     */
    public function processContactAction()
    {
        // ... process contact form
    }
}</pre><p>尽管这两个路由有着相同的路径（/contact），但第1个路由将只匹配GET请求，而第2个路由将只匹配POST请求。这就意味着你可以通过同样的URL去显示和提交表单，而为这两个动作调用不同的控制器。</p>
<blockquote><p>如果没有methods被指定，这个路由将匹配所有方法。</p></blockquote>
<p>&nbsp;</p>
<h2>添加主机要求</h2>
<p>你也可以匹配HTTP上的主机传入的请求。欲了解更多请求，请参阅 <span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/components/routing/hostname_pattern.html" target="_blank">How to Match a Route Based on the Host</a> 。</span></span><br />
<a name="completely-customized-route-matching-with-conditions"></a></p>
<h2>完全自定义路由匹配条件</h2>
<blockquote><p> 2.4 路由条件在symfony2.4被引入</p></blockquote>
<p>正如你所看到的,路由可以只匹配特定的路由通配符（通过正则表达式），HTTP方法和主机名。但是路由系统的conditions：可以扩展到一个几乎无限灵活性的地步。</p><pre class="crayon-plain-tag">contact:
    path:     /contact
    defaults: { _controller: AcmeDemoBundle:Main:contact }
    condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"</pre><p>condition是一个表达式，你可以了解更多关于<span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/components/expression_language/syntax.html" target="_blank">该表达式的语法</a>。</span>有了这个，该路由就不匹配了，除非HTTP方法是GET和HEAD，又或者是User-Agent头是firefox。</p>
<p>你可以利用传递到表达式的两个变量做任何你需要的复杂逻辑：<br />
context：<br />
一个<a href="http://api.symfony.com/2.6/Symfony/Component/Routing/RequestContext.html" target="_blank">RequestContext</a>实例化，包含路由开始匹配的大部分基本信息。<br />
request：<br />
这个是symfony2的Request对象。</p>
<blockquote><p>当生成一个URL时，不用考虑Conditions。</p>
<p><strong>表达式被编译到php</strong><br />
在幕后表达式被编译成原始的php，在我们的例子中会生成php到缓存目录下：</p><pre class="crayon-plain-tag">if (rtrim($pathinfo, '/contact') === '' &amp;&amp; (
    in_array($context-&gt;getMethod(), array(0 =&gt; "GET", 1 =&gt; "HEAD"))
    &amp;&amp; preg_match("/firefox/i", $request-&gt;headers-&gt;get("User-Agent"))
)) {
    // ...
}</pre><p>正因为如此，condition键使用底层php执行，<tt class="docutils literal"><code>没有产生额外的开销。</code></tt></p></blockquote>
<p>&nbsp;</p>
<h2>高级的路由样例</h2>
<p>在Symfony2中你可以通过创建一个强大的路由结构来实现你所需的一切。下面是一个示例来展示路由系统是如何的灵活：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/ArticleController.php

// ...
class ArticleController extends Controller
{
    /**
     * @Route(
     *     "/articles/{_locale}/{year}/{title}.{_format}",
     *     defaults: {"_format": "html"}
     *     requirements: {"_locale": "en|fr", "_format": "html|rss", "year": "\d+"}
     * )
     */
    public function showAction($_locale, $year, $title)
    {
    }
}</pre><p>正如你所看到的，这个路由只匹配一部分URL也就是满足{_locale}为（<tt class="docutils literal"><code>en<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif">或者</span></code></tt><tt class="docutils literal"><code>fr）和 <tt class="docutils literal"><code>{year}是数字的。该路由还向你展示了你可以使用一个句号来分割两个占位符。上面路由匹配的URL如下：</code></tt></code></tt></p>
<ul class="simple">
<li><tt class="docutils literal"><code>/articles/en/2010/my-post</code></tt></li>
<li><tt class="docutils literal"><code>/articles/fr/2010/my-post.rss</code></tt></li>
<li><tt class="docutils literal"><code>/articles/en/2013/my-latest-post.html</code></tt></li>
</ul>
<blockquote><p><strong>这个特殊的_format路由参数</strong></p>
<p>这个示例也突显了特殊的_format路由参数。当使用这个参数时，匹配值将成为Request对象的请求格式。最终，请求格式被用于响应的Content-Type这样的设置（如一个json请求格式将转换成application/json的Content-Type）。它也可以在控制器中使用，根据不同的_format值去渲染不同的模板。_format参数是非常强大的。它可以用不同的格式去渲染同一内容。</p></blockquote>
<p>有些时候你希望路由配置的某些部分在全局使用。symfony可以利用服务容器参数来做到这一点。了解更多，<span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/routing/service_container_parameters.html" target="_blank">How to Use Service Container Parameters in your Routes</a></span>。</p>
<p>&nbsp;</p>
<h2>特殊的路由参数</h2>
<p>正如你所看到的，每个路由参数或默认值都可以作为控制器方法的参数。此外，有三个参数是特殊的：在你的应用程序中每增加一个独特的功能：</p>
<p>_controller</p>
<p>正如你所看到的，这个参数是用来匹配执行控制器的。</p>
<p>_format</p>
<p>用于设置request格式。</p>
<p>_locale</p>
<p>用于设置语言环境的(<span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/translation.html#book-translation-locale-url" target="_blank">阅读更多</a></span>)。</p>
<p>&nbsp;</p>
<h2>控制器命名模式</h2>
<p>每个路由都必须有一个_controller参数，以便当路由被匹配时去确定哪个控制器执行。这个参数使用一个简单的字符串模式叫控制器逻辑名，Symfony2将映射一个指定的PHP方法或类。这个模式有三个部分并用冒号隔开：</p><pre class="crayon-plain-tag">bundle:controller:action</pre><p>假设，一个<tt class="docutils literal"><code>_controller值是一个AppBundle:Blog:show那么意味着：</code></tt></p>
<table class="docutils" border="1">
<thead valign="bottom">
<tr class="row-odd">
<th class="head">Bundle</th>
<th class="head">Controller Class</th>
<th class="head">Method Name</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even">
<td>AppBundle</td>
<td><tt class="docutils literal"><code>BlogController</code></tt></td>
<td><tt class="docutils literal"><code>showAction</code></tt></td>
</tr>
</tbody>
</table>
<p>该控制器可能是这样的：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function showAction($slug)
    {
        // ...
    }
}</pre><p>注意，Symfony2在Blog上添加了字符串Controller作为类名（Blog=&gt;BlogController），添加字符串Action作为方法名（show=&gt;showAction）。</p>
<p>你也可以使用它的全格式类名和方法来指定这个类：Acme\BlogBundle\Controller\BlogController::showAction。但如果你进行一些简单的转换，逻辑名将更加简洁也更加灵活。</p>
<blockquote><p>除了使用逻辑名和全格式类名之外，Symfony2也支持第三种指定控制器的方式。这种方式只使用一个冒号分隔（如service_name:indexAction）并将控制器设为一个服务（参见<span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://www.newlifeclan.com/symfony/archives/66" target="_blank">如何将控制器定义成服务</a></span></span>）。</p></blockquote>
<p>&nbsp;</p>
<p>路由参数和控制器参数</p>
<p>路由参数（如{slug}是非常重要的，因为它（们）都被用作控制器方法的参数：</p><pre class="crayon-plain-tag">public function showAction($slug)
{
  // ...
}</pre><p>现实中，defaults集将参数值一起合并成一个表单数组。该数组中的每个键都被做为控制器的参数。</p>
<p>换句话说，对于控制器方法的每个参数，Symfony2都会根据该名称来查找路由参数，并将其值指向到控制器作为参数。在上面的高级示例当中，下列变量的任何组合（以任意方式）都被用作showAction()方法的参数：</p>
<ul class="simple">
<li><tt class="docutils literal"><code>$_locale</code></tt></li>
<li><tt class="docutils literal"><code>$year</code></tt></li>
<li><tt class="docutils literal"><code>$title</code></tt></li>
<li><tt class="docutils literal"><code>$_format</code></tt></li>
<li><tt class="docutils literal"><code>$_controller</code></tt></li>
<li><tt class="docutils literal"><code>$_route</code></tt></li>
</ul>
<p>占位符和defaults集被合并在一起，就就算是$_controller变量也是可用的。更多细节的讨论，请参见作为 第五章：控制器&#8211;把路由参数传入控制器。</p>
<blockquote><p>你也可以使用指定的$_route变量，它的值是被匹配的路由名。</p></blockquote>
<p>你甚至可以在你的路由中定义额外的信息并在你的控制器中访问它。关于更多信息请阅读 <span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/routing/extra_information.html" target="_blank">How to Pass Extra Information from a Route to a Controller</a></span></span></p>
<p>&nbsp;</p>
<h2>包含外部路由资源</h2>
<p>所有的路由都通过一个单一的配置文件加载-通常是app/config/routing.yml(请看<a href="#creating-routes">创建路由</a>)。但是，如果你使用路由的annotations方式，你需要路由指向到带有annotations的控制器。这种方式你可以通过“importing”来引入路由配置目录：</p><pre class="crayon-plain-tag"># app/config/routing.yml
app:
    resource: "@AppBundle/Controller/"
    type:     annotation # required to enable the Annotation reader for this resource</pre><p></p>
<blockquote><p> 当从YAML导入资源时，关键词（如acme_hello）是没有意义的。仅仅只要确保它是唯一的，没有其它行覆盖它即可。</p></blockquote>
<p>这个<tt class="docutils literal"><code>resource键加载指定的路由资源。在这个例子中资源是一个目录，在这里@AppBundle能够解析AppBundle的完整路径。当路由指定一个目录，该目录中的所有文件都会被解析到该路由中去。</code></tt></p>
<p>你还可以包含其他路由配置文件，这是经常被用来导入第三方路由的方法：</p><pre class="crayon-plain-tag"># app/config/routing.yml
app:
    resource: "@AcmeOtherBundle/Resources/config/routing.yml"</pre><p>&nbsp;</p>
<h2>在引入的路由中加前缀</h2>
<p>你也可以选择为导入的路由提供一个“prefix”前缀。例如，你想在AppBundle的所有路由中使用前缀/site（ 用<tt class="docutils literal"><code>/site/blog/{slug}代替</code></tt> <tt class="docutils literal"><code>/blog/{slug}</code></tt>）：</p><pre class="crayon-plain-tag"># app/config/routing.yml
app:
    resource: "@AppBundle/Controller/"
    type:     annotation
    prefix:   /site</pre><p>这个路径下的每个路由资源都加载了新的前缀字符串/site.</p>
<p>&nbsp;</p>
<h2>添加一个主机要求并引入到路由</h2>
<p>你可以在引入路由配置主机的正则表达式。更多细节请查看 <span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/components/routing/hostname_pattern.html#component-routing-host-imported" target="_blank">Using Host Matching of Imported Routes</a></span></span></p>
<p>&nbsp;</p>
<h2>可视化和调试路由</h2>
<p>当添加和自定义路由时，能够可视化的让你获得路由的详细信息是非常有用的。有一个伟大的方式，就是查看应用程序中每条路由的最好方法是使用命令行debug:router。可以在你项目的根目录中运行以下命令实现：</p><pre class="crayon-plain-tag">$ php app/console debug:router</pre><p></p>
<blockquote><p> 在symfony2.6之前该命令被称为为<tt class="docutils literal"><code>router:debug</code></tt>.</p></blockquote>
<p>该命令将打印应用程序中所有配置路由的列表，这十分有用：</p><pre class="crayon-plain-tag">homepage              ANY       /
contact               GET       /contact
contact_process       POST      /contact
article_show          ANY       /articles/{_locale}/{year}/{title}.{_format}
blog                  ANY       /blog/{page}
blog_show             ANY       /blog/{slug}</pre><p>你只需要在这个命令上加入一个的路由名称，就可以查看你指定的路由具体信息。</p><pre class="crayon-plain-tag">$ php app/console debug:router article_show</pre><p>同样，如果你想测试一个URL是否能够匹配一个指定的路由，你可以使用router:match命令：</p><pre class="crayon-plain-tag">$ php app/console router:match /blog/my-latest-post</pre><p>此命令将打印出URL匹配的路由</p><pre class="crayon-plain-tag">Route "blog_show" matches</pre><p>&nbsp;</p>
<h2>生成URL</h2>
<p>路由系统也用于生成URL。在现实中，路由是一个双向系统：映射URL到控制器+参数以及映射路由+参数返回URL。match()和generate()方法构成了这个双向系统。使用之前的blog_show的例子：</p><pre class="crayon-plain-tag">$params = $this-&gt;get('router')-&gt;match('/blog/my-blog-post');
// array(
//     'slug'        =&gt; 'my-blog-post',
//     '_controller' =&gt; 'AppBundle:Blog:show',
// )

$uri = $this-&gt;get('router')-&gt;generate('blog_show', array('slug' =&gt; 'my-blog-post'));
// /blog/my-blog-post</pre><p>要生成一个URL，你需要指定路由的名称（如blog_show）以及任意的通配符（如slug = my-blog-post）。有个这些信息，任何URL就可以很容易的生成了：</p><pre class="crayon-plain-tag">class MainController extends Controller
{
    public function showAction($slug)
    {
        // ...

        $url = $this-&gt;generateUrl(
            'blog_show',
            array('slug' =&gt; 'my-blog-post')
        );
    }
}</pre><p>在控制器中你没有继承symfony2的父类Controller，你可以使用router的<span style="text-decoration: underline"><span style="color: #ff0000"><a class="reference external" style="color: #ff0000;text-decoration: underline" title="Symfony\Component\Routing\Router::generate()" href="http://api.symfony.com/2.6/Symfony/Component/Routing/Router.html#generate()">generate()</a>服务方法：</span></span></p><pre class="crayon-plain-tag">use Symfony\Component\DependencyInjection\ContainerAware;

class MainController extends ContainerAware
{
    public function showAction($slug)
    {
        // ...

        $url = $this-&gt;container-&gt;get('router')-&gt;generate(
            'blog_show',
            array('slug' =&gt; 'my-blog-post')
        );
    }
}</pre><p>在即将到来的部分中，你将学会如何在模板中生成URL地址。</p>
<blockquote><p>如果您的应用程序前端使用的是ajax请求，你可能希望根据你的路由配置，在JavaScript中生成URL。通过使用<span style="text-decoration: underline"><span style="color: #ff0000"><span class="goog-text-highlight" style="color: #ff0000"><a class="reference external" style="color: #ff0000" href="https://github.com/FriendsOfSymfony/FOSJsRoutingBundle">FOSJsRoutingBundle</a>，</span></span></span>你就可以做到：</p><pre class="crayon-plain-tag">var url = Routing.generate(
    'blog_show',
    {"slug": 'my-blog-post'}
);</pre><p>更多信息请阅读这个bundle文档。</p></blockquote>
<p>&nbsp;</p>
<h2>生成的URL带有Query Strings（？xxx=xxxxxx）</h2>
<p>这个<tt class="docutils literal"><code>generate方法采用通配符数组来生成URL。但是如果在其中添加了额外的键值对，他们将会被添加成Query Strings来生成一个新的URL：</code></tt></p><pre class="crayon-plain-tag">$this-&gt;get('router')-&gt;generate('blog', array('page' =&gt; 2, 'category' =&gt; 'Symfony'));
// /blog/2?category=Symfony</pre><p>&nbsp;</p>
<h2>在模板里生成URL</h2>
<p>在应用程序页面之间进行连接时，最常见的地方就是从模板中生成URL。这样做其实和以前一样，但是使用的是一个模板助手函数：</p><pre class="crayon-plain-tag">&lt;a href="{{ path('blog_show', {'slug': 'my-blog-post'}) }}"&gt;
  Read this blog post.
&lt;/a&gt;</pre><p>&nbsp;</p>
<h2>生成绝对的URL</h2>
<p>默认情况下，路由器会产生相对的URL（如/blog）。在控制器中，很简单的把generateUrl()方法的第三参数设置成true即可。</p><pre class="crayon-plain-tag">$this-&gt;generateUrl('blog_show', array('slug' =&gt; 'my-blog-post'), true);
// http://www.example.com/blog/my-blog-post</pre><p>在模板引擎twig中，要使用url()函数（生成一个绝对的URL），而不是path()函数（生成一个相对的URL）。在php中，需要要在generateUrl()中传入true:</p><pre class="crayon-plain-tag">&lt;a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}"&gt;
  Read this blog post.
&lt;/a&gt;</pre><p></p>
<blockquote><p> 当生成一个绝对URL链接时，所使用的主机自动检测当前使用的Request对象。当生成从web环境外的绝对URL（例如一个控制台命令）这是行不通的。请参见<a href="http://symfony.com/doc/current/cookbook/console/sending_emails.html" target="_blank">How to Generate URLs and Send Emails from the Console</a>来学习如何解决这个问题。</p></blockquote>
<p>&nbsp;</p>
<h2>摘要</h2>
<p>路由是一个将传入请求URL映射到用来处理请求的控制器函数的系统。它允许你指定一个漂亮的URL，并使应用程序的功能与URL“脱钩”。路由是一个双向的机制，意味着它也可以用来生成URL。</p>
<p>&nbsp;</p>
<p>了解更多可以查看 cookbook</p>
<ul class="simple">
<li><span style="text-decoration: underline"><span style="color: #ff0000"><a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/routing/scheme.html"><em>How to Force Routes to always Use HTTPS or HTTP</em></a></span></span></li>
</ul>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/228">第六章：symfony2路由器</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/228/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>第五章：控制器</title>
		<link>http://www.newlifeclan.com/symfony/archives/44</link>
		<comments>http://www.newlifeclan.com/symfony/archives/44#comments</comments>
		<pubDate>Sun, 05 Oct 2014 11:14:21 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=44</guid>
		<description><![CDATA[<p>控制器是一个你创建的php函数，它能够获取http请求信息并构建和返回一个http响应（作为Symfony2的 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/44">第五章：控制器</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>控制器是一个你创建的php函数，它能够获取http请求信息并构建和返回一个http响应（作为Symfony2的Response对象）,这个Response可能是一个html页面、xml文档、一个序列化的json数组、图像、重定向、404错误或者一些其他你能够想像的。控制器包含了你应用程序需要渲染页面的任何逻辑。<br />
<span id="more-44"></span><br />
看看symfony简单的控制器。下面控制器将输出 hello word！：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Response;

public function helloAction()
{
    return new Response('Hello world!');
}</pre><p>控制器的目标都是相同的：创建并返回一个Response对象。在这个过程中，他可能会从请求中读取信息，加载数据库资源，发送邮件，在用户session中设置信息。但是所有情况下，控制器将最终返回 Response 对象给客户端。</p>
<p>没有什么神奇的不用担心还有别的要求！下面是一些常见的例子：</p>
<ul>
<li>控制器A准备了一个首页上的Response对象。</li>
<li>控制器B从请求中读取slug参数，从数据库中载入一条博文，并创建一个显示该博文的Response对象。如果slug不能被数据库中检索到，那么控制器将创建并返回一个带404状态码的Response对象。</li>
<li>控制器C处理关于联系人的表单提交。它从请求中读取表单信息，将联系人信息存入数据库并发送包含联系人信息的电子邮件给网站管理员。最后，它创建一个Response对象将用户的浏览器重定向到联系人表单的“感谢”页面。</li>
</ul>
<p>&nbsp;</p>
<h2>请求、控制器、响应的生命周期</h2>
<p>symfony2处理的每一个请求都会有相同的生命周期。这个框架会负责把很多重复的任务用一个控制器最终执行，这个控制器执行你自定义的应用代码：<br />
1、每个请求都被单个前端控制器（如app.php或index.php）文件处理，前端控制器负责引导框架；<br />
2、路由查看并匹配请求信息，并将其指向一个特定的路由，该路由决定调用哪个控制器；<br />
3、执行控制器，控制器中的代码将创建并返回一个Response对象；<br />
4、HTTP头和Response对象的内容将发回客户端。<br />
创建控制器与创建页面一样方便，同时映射一个URI到该控制器。</p>
<blockquote><p>虽然名称相似，但前端控制器与我们在本章节所说的控制器是不同的，前端控制器是你web目录中的一个PHP小文件，所有的请求都直接经过它。一个典型的应用程序将有一个用于生产的前端控制器（如app.php）和一个用于开发的前端控制器（如app_dev.php）。你可以永远不需要对前端控制器进行编辑、查看和担心。</p></blockquote>
<p>&nbsp;</p>
<h2>一个简单的控制器</h2>
<p>虽然一个控制器可以是任何的可被调用的PHP（函数、对象的方法或Closure），在Symfony2，控制器通常是在控制器对象中的一个方法，控制器也常被称为action。</p><pre class="crayon-plain-tag">// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function indexAction($name)
    {
        return new Response('&lt;html&gt;&lt;body&gt;Hello '.$name.'!&lt;/body&gt;&lt;/html&gt;');
    }
}</pre><p></p>
<blockquote><p>注意：控制器是indexAction方法，它隶属于一个控制器类（HelloController）。不要对名称感到困惑：控制器类只是简单将几个控制器集中在一起的。通常情况下，控制器类将放置多个控制器（如updateAction、deleteAction等），一个控制器有时也会被当作一个action被提及。</p></blockquote>
<p>这个控制器相当的明确：</p>
<ul>
<li>第4行：Symfony2充分利用了PHP5.3的名称空间的功能去为整个控制器类命名空间。use关键字导入Response类，是我们控制器必须返回的；</li>
<li>第6行：类名是一个控制器名称（例如hello）加上Controller关键字。这是一个约定,为控制器提供一致性,并允许他们引用控制器名称(例如hello)作为路由配置。</li>
<li>第8行：在控制器类中的每个action都有着后缀Action，并在路由配置中通过action名（index）被指定。在下一节中，我们将使用路由映射一个URI到该action，并展示如何将路由占位符（{name}）变成action的参数（$name）；</li>
<li>第10行：控制器创建并返回一个Response对象。</li>
</ul>
<p>&nbsp;</p>
<h2>将URI映射到控制器</h2>
<p>我们的新控制器返回一个简单的HTML页。为了能够在指定URI中渲染该控制器，我们需要为它创建一个路由。<br />
我们将在路由章节中讨论路由组件的细节，但现在我们为我们的控制器创建一个简单路由：</p><pre class="crayon-plain-tag"># app/config/routing.yml
hello:
    path:      /hello/{name}
    defaults:  { _controller: AcmeHelloBundle:Hello:index }</pre><p>如果我们在浏览器中输入 /hello/ryan  那么他就会执行HelloController::indexAction()控制器，并且将ryan赋给$name变量。创建这样一个页面就能够让路由跟控制器做简单的关联。<br />
注意，这个是用于指向控制器的语法：AcmeHelloBundle:Hello:index。Symfony2使用一个灵活的字符串注释来指向不同的控制器。这是Symfony2最通用的语法，告诉AcmeHelloBundle在内部去查找一个名为HelloController的控制器类，然后执行方法indexAction()。<br />
关于指向不同控制器的字符串格式用法的更多细节，请参见<span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/routing.html#controller-string-syntax" target="_blank">控制器命名模式</a></span>。</p>
<blockquote><p>这个例子的路由配置目录在app/config/ 下。有更好的方式就是你可以将每个bundle的路由放到各自的资源下，更多信息可查看 <span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/routing.html#routing-include-external-resources" target="_blank">引入外部路由资源</a>。</span></p></blockquote>
<blockquote><p>你也能了解和学习更多的路由系统在<span style="color: #ff0000"> <a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/book/routing.html"><em>Routing chapter</em></a>.</span></p></blockquote>
<h2></h2>
<h2>把路由参数传入控制器</h2>
<p>我们现在已经知道_controller的参数 AcmeHelloBundle:hello:index 指向AcmeHelloBundle 中的HelloController::indexAction()方法，这里更有趣的是发送给该方法的参数：</p><pre class="crayon-plain-tag">// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloController extends Controller
{
    public function indexAction($name)
    {
      // ...
    }
}</pre><p>控制器有个参数$name，对应所匹配路由的{name}参数（在本例中是ryan）。实际上当执行你的控制器时，Symfony2在所匹配路由中匹配带参数控制器中的每个参数。以下所示：</p><pre class="crayon-plain-tag"># app/config/routing.yml
hello:
    path:      /hello/{firstName}/{lastName}
    defaults:  { _controller: AcmeHelloBundle:Hello:index, color: green }</pre><p>因此控制器也需要多个参数：</p><pre class="crayon-plain-tag">public function indexAction($firstName, $lastName, $color)
{
    // ...
}</pre><p>注意两个占位符（{{first_name}}、{{last_name}}）和默认color变量做为控制器的参数。当路由匹配时，占位符变量与 defaults 合并成一个数组，并且用于你的控制器。</p>
<p>将路由参数映射到控制器参数是十分容易和灵活的。在你开发时请遵循以下思路：</p>
<p>1. 控制器参数的顺序无关紧要</p>
<p>Symfony2可以根据路由参数名匹配控制器方法参数的特征。换句话说，它可以实现last_name参数与$last_name参数的匹配。控制器可以在随意排列参数的情况下正常工作。</p><pre class="crayon-plain-tag">public function indexAction($lastName, $color, $firstName)
{
    // ...
}</pre><p>2.控制器所需参数必须匹配路由参数</p>
<p>下面会抛出一个运行时异常（RuntimeException），因为在路由定义中没有foo参数</p><pre class="crayon-plain-tag">public function indexAction($firstName, $lastName, $color, $foo)
{
    // ...
}</pre><p>3.然而如果设置该参数可选是完全可行的。下面的例子不会抛出异常：</p><pre class="crayon-plain-tag">public function indexAction($firstName, $lastName, $color, $foo = 'bar')
{
    // ...
}</pre><p>4.不是所有的路由参数都需要在控制器上有相应参数的</p>
<p>如果，举个例子，last_name对你控制器不是很重要的话，你可以完全忽略掉它：</p><pre class="crayon-plain-tag">public function indexAction($firstName, $color)
{
    // ...
}</pre><p></p>
<blockquote><p>每条路由也都有一个特殊的_route参数，该参数匹配路由的名字（如hello）。虽然不常用，但它一样也可以作为控制器的参数。</p></blockquote>
<h2></h2>
<h2>Request作为一个控制器的参数</h2>
<p>为了方便起见，你可以在symfony中把request对象当作一个参数传到你的Controller里。当你处理表单的时候这是非常方便的，例如：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Request;

public function updateAction(Request $request)
{
    $form = $this-&gt;createForm(...);

    $form-&gt;handleRequest($request);
    // ...
}</pre><p>&nbsp;</p>
<h2>创建静态页面</h2>
<p>有时你也可只创建一个静态页面不用创建控制器（仅仅一个路由和模板即可）。</p>
<p>如何使用，请查看 <span style="color: #ff0000"><a style="color: #ff0000" href="http://www.newlifeclan.com/symfony/archives/54" target="_blank">没有自定义控制器如何渲染一个模板</a></span>。</p>
<p>&nbsp;</p>
<h2>基本控制器类</h2>
<p>出于方便的考虑，Symfony2提供了一个Controller基类，以帮助实现常用的一些控制器任务，并站你的控制器类能够访问所需的资源。通过继承该类，你可以利用其中的一些帮手方法。<br />
在顶部使用use语句添加Controller类，然后修改HelloController去继承它。如下所示：</p><pre class="crayon-plain-tag">// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class HelloController extends Controller
{
    public function indexAction($name)
    {
        return new Response('&lt;html&gt;&lt;body&gt;Hello '.$name.'!&lt;/body&gt;&lt;/html&gt;');
    }
}</pre><p>到目前为止，继承Controller类并没有改变任何东西。在下一节中，我们将使用几个该基类中的助手方法。这些方法只是让你可以方便地使用Symfony2的核心功能，而无论你是否使用Controller基类。其实查看核心功能的最好方式就是看Controller类本身。</p>
<blockquote><p>在Symfony2中是否继承基类是可以选择的；它包含了非常有用的便捷方式，但并不强制使用。你也可以继承ContainerAwaer或使用Symfony\Component\DependencyInjection\ContainerAwareTrait（如果你有php5.4）。这个服务容器对象将通过container属性访问。</p></blockquote>
<blockquote><p><strong>2.4 </strong> ContainerAwareTrait 实在symfony2.4中引入的</p></blockquote>
<blockquote><p>你还可以定义你的<span style="color: #ff0000"> <a style="color: #ff0000" href="http://www.newlifeclan.com/symfony/archives/66" target="_blank">Controller为一个服务</a></span>。这个是可选的，但可以把你更多的控件依赖注入到你的控制器。</p></blockquote>
<p>&nbsp;</p>
<h2>常用控制器任务</h2>
<p>虽然控制器实际上可以做任何事情，但大多数控制器都是一遍又一遍地执行同一个基本任务。这些任务包括重定向、转发、渲染模板和访问核心服务，这一切在Symfony2中非常便于管理。</p>
<p><strong>重定向</strong><br />
如果你想将用户重定向到另一个页面,请使用 redirect() 方法：</p><pre class="crayon-plain-tag">public function indexAction()
{
    return $this-&gt;redirect($this-&gt;generateUrl('homepage'));
}</pre><p>这个generateUrl() 方法仅仅是帮助函数生成指定的URL路径，更多信息请查看路由章节。</p>
<p>默认情况下，redirect方法执行302（临时）重定向。如果要执行301（永久）重定向，请修改第2个参数：</p><pre class="crayon-plain-tag">public function indexAction()
{
    return $this-&gt;redirect($this-&gt;generateUrl('homepage'), 301);
}</pre><p></p>
<blockquote><p>比创建一个专门从事重定向用户的<tt class="docutils literal"><code>Response对象来说</code></tt>这个 <tt class="docutils literal"><code>redirect()方法</code></tt>是个简单的捷径，它相当于：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\RedirectResponse;

return new RedirectResponse($this-&gt;generateUrl('homepage'));</pre><p>
</p></blockquote>
<p>&nbsp;</p>
<h2>转发</h2>
<p>你也可以很容易地通过forward()方法内部转发到另一个action。与重定向用户浏览器不同，它产生一个内部的子请求，并调用一个特殊的控制器。forward()方法返回Response对象，这个Response对象是控制器返回的。</p><pre class="crayon-plain-tag">public function indexAction($name)
{
    $response = $this-&gt;forward('AcmeHelloBundle:Hello:fancy', array(
        'name'  =&gt; $name,
        'color' =&gt; 'green',
    ));

    // ... further modify the response or return it directly

    return $response;
}</pre><p>注意forward()方法使用与路由配置中相同的控制器字符串表示。在本例中目标控制器类将是在AcmeHelloBundle中的Hellocontroller类，发送给控制器类方法的数组做为其参数。当内嵌的控制器进入模板时使用相同的接口（参见内嵌控制器）。目标控制器访问如下所示：</p><pre class="crayon-plain-tag">public function fancyAction($name, $color)
{
    // ... create and return a Response object
}</pre><p>就这样，当为一个路由创建一个控制器时，fancyAction的参数顺序不是问题。Symfony2检索关键词名（如name），并将其匹配到方法参数名（如$name）。如果你改变参数的顺序，Symfony2也会将正确的值赋给每个变量。</p>
<blockquote><p>正如其它Controller基类的方法一样，forward方法也只是Symfony2核心功能是快捷方式。转发可以通过http_kernel服务直接完成。转发返回一个Response对象。</p><pre class="crayon-plain-tag">use Symfony\Component\HttpKernel\HttpKernelInterface;

$path = array(
    '_controller' =&gt; 'AcmeHelloBundle:Hello:fancy',
    'name'        =&gt; $name,
    'color'       =&gt; 'green',
);
$request = $this-&gt;container-&gt;get('request');
$subRequest = $request-&gt;duplicate(array(), null, $path);

$httpKernel = $this-&gt;container-&gt;get('http_kernel');
$response = $httpKernel-&gt;handle(
    $subRequest,
    HttpKernelInterface::SUB_REQUEST
);</pre><p>
</p></blockquote>
<p>&nbsp;</p>
<h2>渲染模板</h2>
<p>虽然不是必需的，多数控制器都会渲染一个模板，这些控制器会负责生产html（或者其他格式）。renderView()方法渲染模板并返回它的内容。来自模板的内容可以用来创建一个Response对象：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Response;

$content = $this-&gt;renderView(
    'AcmeHelloBundle:Hello:index.html.twig',
    array('name' =&gt; $name)
);

return new Response($content);</pre><p>这个方法甚至可以在render()方法中一步完成，返回一个<tt class="docutils literal"><code>Response</code></tt>对象,其中包含模板的内容：</p><pre class="crayon-plain-tag">return $this-&gt;render(
    'AcmeHelloBundle:Hello:index.html.twig',
    array('name' =&gt; $name)
);</pre><p>在上两个示例中，AcmeHelloBundle中的Resources/views/Hello/index.html.twig模板将被渲染。</p>
<p>symfony模板引擎的详细说明请看模板章节。</p>
<p>&nbsp;</p>
<blockquote><p>通过使用@Template注释，你甚至可以避免调用render方法，请查看  <a href="http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html" target="_blank">FrameworkExtraBundle文档</a> 更多细节。</p></blockquote>
<blockquote><p>renderView方法是直接使用模板服务的快捷方式。也可以直接使用模板服务：</p><pre class="crayon-plain-tag">$templating = $this-&gt;get('templating');
$content = $templating-&gt;render(
    'AcmeHelloBundle:Hello:index.html.twig',
    array('name' =&gt; $name)
);</pre><p>
</p></blockquote>
<blockquote><p>可以渲染模板的更深层次的子目录,但是应该注意避免让你的目录结构进入过于精心的陷阱：</p><pre class="crayon-plain-tag">$templating-&gt;render(
    'AcmeHelloBundle:Hello/Greetings:index.html.twig',
    array('name' =&gt; $name)
);
// index.html.twig found in Resources/views/Hello/Greetings
// is rendered.</pre><p>
</p></blockquote>
<h2></h2>
<h2></h2>
<h2>访问其他服务</h2>
<p>当继承controller基类后，你可以通过get()方法访问任何Symfony2的服务。下面列举了一些常见服务：</p><pre class="crayon-plain-tag">$templating = $this-&gt;get('templating');

$router = $this-&gt;get('router');

$mailer = $this-&gt;get('mailer');</pre><p>Symfony2可以有无数个其它的服务，也鼓励你定义自己的服务。使用 container:debug 命令能够列举所有有效的服务。</p><pre class="crayon-plain-tag">$ php app/console container:debug</pre><p>更多详细信息请查看服务容器章节。</p>
<p>&nbsp;</p>
<h2>管理错误和404页</h2>
<p>如果有些动作没找到，将返回一个404响应。为此，你需要抛出一个异常。如果你继承了基础的Controller类，你可以执行以下操作：</p><pre class="crayon-plain-tag">public function indexAction()
{
    // retrieve the object from database
    $product = ...;
    if (!$product) {
        throw $this-&gt;createNotFoundException('The product does not exist');
    }

    return $this-&gt;render(...);
}</pre><p>这个 createNotFoundException() 方法创建了一个特殊的NotFoundHttpException对象，来触发symfony内部的http的404响应。</p>
<p>当然，你也可以自由地抛出你控制器中的任何异常类，Symfony2将自动返回HTTP响应代码500。</p><pre class="crayon-plain-tag">throw new \Exception('Something went wrong!');</pre><p>在每个示例中，一个带格式的错误页被显示给最终用户，而一个全调试错误页会被显示给开发者（当在调试模式查看该页时）。这些错误页都是可以自定义的。要想知道更多请阅读“<span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/controller/error_pages.html" target="_blank">如何自定义错误页</a></span>”。</p>
<p>&nbsp;</p>
<h2>管理会话</h2>
<p>Symfony2提供了一个好的会话对象，它能够存储有关用户的信息（它可以是使用浏览器的人、bot或Web服务）之间的请求。默认情况下，Symfony2通过使用PHP的本地会话来保存cookie中的属性。</p>
<p>这个会话能很容易的从任何控制器存储和取回信息。</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)
{
    $session = $request-&gt;getSession();

    // 存储一个属性
    $session-&gt;set('foo', 'bar');

    // 读取一个属性
    $foobar = $session-&gt;get('foobar');

    // 如果属性不存在可以给他一个默认值
    $filters = $session-&gt;get('filters', array());
}</pre><p>这些属性将会在该用户会话的有效期内保留。</p>
<p>&nbsp;</p>
<h2>Flash消息</h2>
<p>你也可以为了一个额外的请求，在用户会话中保存一些小消息。这在处理表单时很有用：你想重定向并在下个请求中显示一个特定的消息。这些类型的消息被称为“Flash”消息。<br />
让我们看看我们处理表单提交的示例：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Request;

public function updateAction(Request $request)
{
    $form = $this-&gt;createForm(...);

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        // do some sort of processing

        $this-&gt;get('session')-&gt;getFlashBag()-&gt;add(
            'notice',
            'Your changes were saved!'
        );</pre><p>在处理请求之后，控制器设置了一个名为notice的flash消息，然后重定向。这个名称（notice）并不重要 &#8211; 他就是一个确定消息类型的识别符。</p>
<p>在下一个action的模板中，下列代码用来渲染该消息：</p><pre class="crayon-plain-tag">{% for flashMessage in app.session.flashbag.get('notice') %}
    &lt;div class="flash-notice"&gt;
        {{ flashMessage }}
    &lt;/div&gt;
{% endfor %}</pre><p>按照设计，flash消息意味着仅存活一个请求（它们总是“稍纵即逝”）。在本例中它们被设计用来跨跃重定向。</p>
<p>&nbsp;</p>
<h2>Response对象</h2>
<p>对于控制器，唯一的要求就是返回一个Response对象。Response类是一个PHP对于HTTP响应的一个抽象，一个基于文本的消息填充HTTP头，其内容发返客户端：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Response;

// 创建一个简单的202状态码响应 (默认)
$response = new Response('Hello '.$name, Response::HTTP_OK);

// 创建一个202状态码的JOSN响应
$response = new Response(json_encode(array('name' =&gt; $name)));
$response-&gt;headers-&gt;set('Content-Type', 'application/json');</pre><p></p>
<blockquote><p><strong>2.4</strong> 在symfony2.4 引入了http状态码常量。</p></blockquote>
<blockquote><p>headers属性是一个HeaderBag对象，那么他有许多有用的方法来读取和改变Response头。头名称的规范化使得 Content-Type等于content-type甚至等于content_type，他们都是相同的。</p></blockquote>
<p>&nbsp;</p>
<blockquote><p>也有一些特殊的类能够简化某种响应：</p>
<ul>
<li>对于JSON：这是一个<span style="color: #ff0000"><a style="color: #ff0000" href="http://api.symfony.com/2.5/Symfony/Component/HttpFoundation/JsonResponse.html" target="_blank">JosnResponse</a></span>。可查看 <span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/components/http_foundation/introduction.html#component-http-foundation-json-response" target="_blank">创建一个JOSN Response</a></span>。</li>
<li>对于文件：这是 <span style="color: #ff0000"><a style="color: #ff0000" title="Symfony\Component\HttpFoundation\BinaryFileResponse" href="http://api.symfony.com/2.5/Symfony/Component/HttpFoundation/BinaryFileResponse.html">BinaryFileResponse</a></span>。可查看 <span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/components/http_foundation/introduction.html#component-http-foundation-serving-files" target="_blank">Serving Files</a></span>。</li>
</ul>
</blockquote>
<p>&nbsp;</p>
<h2>Request(请求)对象</h2>
<p>除了路由占位符的值,控制器也可先后获得Request对象。在框架中，如果变量是type-hinted的Request，Request 对象会注入到控制器：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)
{
    $request-&gt;isXmlHttpRequest(); // is it an Ajax request?

    $request-&gt;getPreferredLanguage(array('en', 'fr'));

    $request-&gt;query-&gt;get('page'); // get a $_GET parameter

    $request-&gt;request-&gt;get('page'); // get a $_POST parameter
}</pre><p>如同Response对象一样，请求头被保存在HeaderBag对象中，并可以轻易访问到。</p>
<p>&nbsp;</p>
<h2>本知识点结束语</h2>
<p>当你创建了一个页面，你需要在页面中写一些业务逻辑的代码。在symfony中，这些就是Controller，它是一个能够做任何事情的php函数，为了方便，他需要最终返回给用户Response对象。<br />
而且你能够继承Controller类，使工作变得轻松。例如，你不用把html代码写到控制器，您可以使用render()方法来渲染模板,并返回模板的内容。<br />
在其他章节中，你会看到控制器是如何使用数据库持久化和读取数据的，还有表单的提交，使用缓存和更多。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/44">第五章：控制器</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/44/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第四章：在symfony2中创建页面</title>
		<link>http://www.newlifeclan.com/symfony/archives/42</link>
		<comments>http://www.newlifeclan.com/symfony/archives/42#comments</comments>
		<pubDate>Sun, 05 Oct 2014 11:09:16 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=42</guid>
		<description><![CDATA[<p>在symfony2中创建一个页面只需要简单的两个步骤： 创建一个路由：一个路由定义了一个url并指定了一个路由 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/42">第四章：在symfony2中创建页面</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在symfony2中创建一个页面只需要简单的两个步骤：</p>
<ul>
<li>创建一个路由：一个路由定义了一个url并指定了一个路由器，当传入的请求URL匹配该路由时，Symfony2将执行指定的控制器；</li>
<li>创建一个控制器：控制器是一个PHP函数，它接受传入的请求并将其转换成Symfony2的Response对象。</li>
</ul>
<p>&nbsp;</p>
<p>我们喜欢这样简单的实现，因为它符合Web的工作方式。每一个Web交互都是由HTTP请求开始，应用程序的任务就是简单地解释请求并返回相应的HTTP响应。</p>
<p><span id="more-42"></span></p>
<p>Symfony2遵循这一原则，并为你提供工具，以保证在应用程序的用户和复杂性增长时仍保持良好地组织性。</p>
<h3></h3>
<h3>环境和前端控制器</h3>
<p>每一个symfony应用程序都运行着一个环境。一个环境是一组特定的配置和包，可以用一个字符串表示。这些应用程序能运行不同的配置就需要应用程序中运行不同的环境。symfony2中有三个环境定义 &#8211; -dev,test和 prod &#8211;但是你也可以创建自己的。<br />
环境是很有用的，它允许应用程序在开发环境进行调试并在生产环境下优化速度。你也可以基于特定的环境加载特定的包。例如，symfony2的WebProfilerBundle(说明如下)，只支持 dev 和 test 环境。<br />
symfony2有两个前端访问控制器：app_dev.php 在dev 环境下使用，app.php 在prod 环境下使用。<br />
所有的web访问symfony2通常都通过其中一个前端控制器。（测试环境通常只用于运行单元测试,所以没有专门的前端控制器。控制台工具还提供了一个前端控制器,可用于任何环境。）<br />
当前端控制器初始化内核，它提供了两个参数：环境和内核是否运行在debug模式下。symfony2有一个缓存目录 app/cache/ 来让你的应用程序运行的更快。当你启用debug模式（app_dev.php的默认模式），如果你改变任何代码或者配置缓存都会自动刷新。当运行debug模式下，symfony2会运行的慢一些，但是它会自动反应出相应的变化而不用手动清空缓存。<br />
例如，代码</p><pre class="crayon-plain-tag">$kernel = new AppKernel('prod', false);</pre><p></p>
<h3></h3>
<h3>“hello Symfony” 页</h3>
<p>让我们从经典的“hello,world”程序开始，当我们完成后，用户可以通过访问下列URL来得到一声问候：</p><pre class="crayon-plain-tag">http://localhost/app_dev.php/hello/Symfony</pre><p>其实，你可以将Symfony换成其它的名称来问候。 去创建这个页面，需要以下两个步骤：</p>
<blockquote><p>本教程假设您已经下载了Symfony2和配置好了您的web服务器。上述URL假设localhost指向你新的Symfony2项目。这一过程的详细信息，要看文档的web服务配置。这里有相关文档教你如何配置web服务器：</p>
<ul>
<li>apache 服务器, <span style="text-decoration: underline"><em><a href="http://httpd.apache.org/docs/current/mod/mod_dir.html" target="_blank">Apache&#8217;s DirectoryIndex documentation</a></em></span></li>
<li>Nginx 服务器, <a href="http://wiki.nginx.org/HttpCoreModule#location" target="_blank"><span style="text-decoration: underline"><em>Nginx HttpCoreModule location documentation</em></span></a></li>
</ul>
</blockquote>
<h3>从现在开始创建Bundle</h3>
<p>在开始之前，你需要创建一个Bundle。在Symfony2中，Bundle相当于插件，你应用程序中的所有代码都需要放在Bundle中。<br />
Bundle只是一个目录,里面有很多相关的特定功能，包含php类、配置、样式表和javascript文件（参见<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/book/page_creation.html#page-creation-bundles" target="_blank">Bundle系统</a></em></span>）。</p>
<p>去创建一个AcmeHelloBundle （在这一章玩转你需要建设的bundle），按照屏幕上的指令，运行以下命令（使用默认选项）：</p><pre class="crayon-plain-tag">$ php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml</pre><p>在幕后，将创建一个 src/Acme/HelloBundle 目录。</p>
<p>下面这一行代码也被自动添加到 app/AppKernel.php 文件，注册到kernel内核。</p><pre class="crayon-plain-tag">// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        ...,
        new Acme\HelloBundle\AcmeHelloBundle(),
    );
    // ...

    return $bundles;
}</pre><p>现在你已经建设了一个bundle，你可以开始在这个bundle里构建你的应用程序了。</p>
<h3>步骤1：创建路由</h3>
<p>默认，路由配置在symfony2应用程序的 app/config/routing.yml 里. 在Symfony2中所有的配置文件也可以采用PHP或XML格式编写。</p>
<p>如果你看一下主路由文件，你会看到它已经添加了AcmeDemoBundle相关路由文件</p>
<p>yml</p><pre class="crayon-plain-tag"># app/config/routing.yml
acme_website:
    resource: "@AcmeDemoBundle/Resources/config/routing.yml"
    prefix:   /</pre><p>xml</p><pre class="crayon-plain-tag">&lt;!-- app/config/routing.xml --&gt;
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd"&gt;

    &lt;import
        resource="@AcmeDemoBundle/Resources/config/routing.xml"
        prefix="/" /&gt;
&lt;/routes&gt;</pre><p>php</p><pre class="crayon-plain-tag">// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;

$acmeDemo = $loader-&gt;import('@AcmeDemoBundle/Resources/config/routing.php');
$acmeDemo-&gt;addPrefix('/');

$collection = new RouteCollection();
$collection-&gt;addCollection($acmeDemo);

return $collection;</pre><p>这些是非常基础的东西：他告诉symfony由去加载路由配置<tt class="docutils literal"><code>Resources/config/routing.yml（在xml和php的代码分别为routing.xml和routing.php）在AcmeDemoBundle里的文件。这意味着，您将可以直接在路由app/config/routing.yml配置或导入自己应用程序的路由到这里。</code></tt></p>
<blockquote><p>这个路由系统在你的应用程序中创建了灵活而强大的URL结构，如果想了解更多更强大的功能，请阅读有关章节 <a href="http://symfony.com/doc/current/book/routing.html" target="_blank">路由</a>。</p></blockquote>
<h3></h3>
<h3>步骤2：创建控制器</h3>
<p>当一个URL<strong> <span style="color: #339966">/random/10 </span></strong>被应用程序处理时，<span style="color: #339966">random</span> 和 <span style="color: #339966">AcmeDemoBundle:random:index</span> 相匹配并由框架执行。页面创建过程的第二个步骤是创建一个控制器。</p>
<p>该控制器－ AcmeDemoBundle:random:index 是逻辑控制器，并将其映射到Acme/DemoBundle/Controller/RandomController类的 indexAction方法中。首先在AcmeDemoBundle中创建这个文件：</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Controller/RandomController.php
namespace Acme\DemoBundle\Controller;

class RandomController
{
}</pre><p>事实上，这个控制器无非是一个PHP创建的方法并由Symfony执行。这是你的代码来构建和准备所请求的资源的请求使用信息。除了在一些先进的情况下，始终产品的控制器总是相同的：一个Symfony2的<span style="font-family: monospace"> <span style="color: #339966">Response</span> </span>对象。</p>
<p>当这个random 路由匹配时，那symfony需要执行创建的indexAction方法：</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Controller/RandomController.php
namespace Acme\DemoBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class RandomController
{
    public function indexAction($limit)
    {
        return new Response('&lt;html&gt;&lt;body&gt;Number: '.rand(1, $limit).'&lt;/body&gt;&lt;/html&gt;');
    }
}</pre><p>控制器很简单吧：他创建了一个新的 Response 对象，其第一个参数是在response中使用的（在本例中的html里）。</p>
<p>恭喜你！仅在创建一个路由和控制器之后，您就已经有了一个全功能的网页！如果你已经正确安装了一切，你的应用程序应该产生一个随机数给你：</p><pre class="crayon-plain-tag">Http://localhost/app_dev.php/random/10</pre><p></p>
<blockquote><p>你可以查看你的应用在“prod”环境下的访问：</p><pre class="crayon-plain-tag">http://localhost/app.php/random/10</pre><p>如果你得到一个错误，很可能是因为你需要清除缓存再运行：</p><pre class="crayon-plain-tag">$ php app/console cache:clear --env=prod --no-debug</pre><p>
</p></blockquote>
<p>在这个过程中的第三步是创建一个模版。</p>
<p>控制器在你的代码中是一个入口点并且在你创建页面时也是一个关键因素。更多的信息可以在 <a href="http://symfony.com/doc/current/book/controller.html" target="_blank">Controller Chapter </a>中找到。</p>
<p>&nbsp;</p>
<h3>步骤3:创建模版</h3>
<p>模板允许你把所有的（html代码）分别放到不同文件中，能够重用页面布局构成不同的区块。下面就是使用模板来替代控制器中的html代码。</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Controller/RandomController.php
namespace Acme\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class RandomController extends Controller
{
    public function indexAction($limit)
    {
        $number = rand(1, $limit);

        return $this-&gt;render(
            'AcmeDemoBundle:Random:index.html.twig',
            array('number' =&gt; $number)
        );

        // render a PHP template instead
        // return $this-&gt;render(
        //     'AcmeDemoBundle:Random:index.html.php',
        //     array('number' =&gt; $number)
        // );
    }
}</pre><p></p>
<blockquote><p>为了使用 <span style="color: #99ccff">render()</span> 方法，你的路由器必须继承 <span style="color: #99ccff">Controller</span> 类，该类添加了一些常用的快捷方式。在上面的例子中通过添加声明第四行才能使RandomController继承<span style="color: #99ccff">Controller </span>。</p></blockquote>
<p>render()方法创建一个Response对象，该对象使用特定的内容填充并通过模板渲染的。与其它控制器一样，你最终得到的是一个Response对象。</p>
<p>注意，这里有两种不同渲染模板的例子，缺省情况下，Symfony2支持两种渲染模板的方式：传统的PHP模板和简洁强大的Twig模板。你可以随意选择使用其中的一种，也可以在同一项目中混用它们，这都不成问题。</p>
<p>控制器渲染AcmeStudyBundle:Hello:index.html.twig模板，该模板使用以下命名约定：</p>
<p>Bundle名:Controller名:Template名</p>
<p>这是合乎模板逻辑的，他被映射到如下物理位置。</p>
<p><strong>/</strong>path/to<strong>/Bundle名</strong>/Resources/views/<strong>Controller名</strong>/<strong>Template名</strong></p>
<p>在本例中，AcmeStudyBundle是Bundle名，Hello是控制器，index.html.twig是模板名。</p><pre class="crayon-plain-tag">{# src/Acme/DemoBundle/Resources/views/Random/index.html.twig #}
{% extends '::base.html.twig' %}

{% block body %}
    Number: {{ number }}
{% endblock %}</pre><p>让我们一步一步来看看模板：</p>
<ul>
<li>第二行：extends定义了一个父模板，模板明确定义了一个将被替换的布局文件；</li>
<li>第四行：block表示其中的内容将会替换掉名为body的block，如我们所知，它在最终渲染时将负责layout.html.twig中名为body的block的渲染。</li>
</ul>
<p>父模板：<span style="color: #339966">::base.html.twig</span>，省略了 bundle名和Controller名（用两个冒号：：代替），这意味着该模板在app目录中：</p><pre class="crayon-plain-tag">{# app/Resources/views/base.html.twig #}
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
        &lt;title&gt;{% block title %}Welcome!{% endblock %}&lt;/title&gt;
        {% block stylesheets %}{% endblock %}
        &lt;link rel="shortcut icon" href="{{ asset('favicon.ico') }}" /&gt;
    &lt;/head&gt;
    &lt;body&gt;
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    &lt;/body&gt;
&lt;/html&gt;</pre><p>&nbsp;</p>
<p>基本模板文件定义了HTML布局，并用我们在index.html.twig模板中定义的名为body的区块渲染。这里还定义了一个名为title的区块，我们也可以选择在index.html.twig模板中定义。由于我们没有在子模板中定义title区块，所以它还是使用缺省值&#8221;Hello Application&#8221;。</p>
<p>模板在渲染和组织页面内容方面的功能非常强大，它可以是HTML标识语言、CSS代码或者控制器可能需要返回的东东。模板引擎只是达到目标的手段。每个控制器的目标是返回一个Response对象，模板虽然强大，但它却是可选的，它只是为Response对象创建内容的工具而已。</p>
<p>&nbsp;</p>
<h3>目录结构</h3>
<p>经过前面几段的学习，你已经理解了在Symfony2中创建和渲染页面的步骤，也开始明白了Symfony2的组织和结构，在本章的最后，你将学会在哪儿找到和放置不同类型的文件以及为什么这样做。</p>
<p>虽然Symfony2的目录结构相当灵活，但在缺省状态下，Symfony2还是有着相同的、被推荐的基本目录结构：</p>
<p><tt class="docutils literal"><span style="font-family: 仿宋_GB2312"><span class="pre">app/</span> </span></tt>: 该目录包含应用程序配置；<br />
<tt class="docutils literal"><span style="font-family: 仿宋_GB2312"><span class="pre">src/</span> </span></tt>: 所有项目的PHP代码都保存在该目录下；<br />
<tt class="docutils literal"><span style="font-family: 仿宋_GB2312"><span class="pre">vendor/</span> </span></tt>: 根据约定放置所有供应商的库文件；<br />
<tt class="docutils literal"><span style="font-family: 仿宋_GB2312"><span class="pre">web/</span> </span></tt>: 这是web根目录，包括一些公众可以访问的文件。</p>
<p>&nbsp;</p>
<h3>web目录</h3>
<p>web根目录是所有静态的、公共文件的家目录，包括图像、样式表和javascript文件，这里也是前端控制器所在的地方。</p><pre class="crayon-plain-tag">// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel-&gt;loadClassCache();
$kernel-&gt;handle(Request::createFromGlobals())-&gt;send();</pre><p>前端控制器（在这里是app.php）其实是一个PHP文件，在使用Symfony2应用程序时执行。它的功能就是使用内核类AppKernel，来引导应用程序。</p>
<blockquote><p>使用前端控制器意味着要比使用传统的纯PHP程序有着更为灵活多变的URL，当使用前端控制器时，URL格式如下所示：</p><pre class="crayon-plain-tag">http://localhost/app.php/random/10</pre><p>前端控制器app.php,根据 /random/10 执行内部路由。通过使用Apache的重写规则，你可以在不指定app.php的情况下强制执行它：</p><pre class="crayon-plain-tag">http://localhost/random/10</pre><p>
</p></blockquote>
<p>虽然前端控制器在处理请求时必不可少，但你很少会去修改甚至想到它，我们只是在环境一章中简要地提及它。</p>
<p>&nbsp;</p>
<h3>应用程序（app）目录</h3>
<p>正如你在前端控制器所看到的那样，AppKernel类是整个应用程序的主入口，它负责所有的配置，它被保存在app/目录中。</p>
<p>这个类必须实现两个方法，定义了Symfony2的需要了解您的应用程序的一切。你甚至在一开始就无须担心这些方法 -因为Symfony2会智能地为你填充它们。</p>
<ul>
<li>registerBundles(): 返回所有需要在应用程序中运行的bundle数组 (参见<a class="reference internal" href="http://symfony.com/doc/current/book/page_creation.html#the-bundle-system"><span style="color: #008000">Bundle系统</span></a> )；</li>
<li>registerContainerConfiguration(): 引导应用程序的主配置资源文件 (参见<a class="reference internal" href="http://symfony.com/doc/current/book/page_creation.html#application-configuration"><span style="color: #008000">应用程序配置</span></a>章节)；</li>
</ul>
<p>在日常开发中，你会经常用到app/目录，你会在app/config/目录中修改配置和路由文件（参见应用程序配置），也会使用app/cache/目录做为应用程序的缓存目录、使用app/logs/目录做为日志目录、使用app/Resources/目录做为应用程序级别的资源目录。在下面的章节中你将会学到更多关于这些目录的内容。</p>
<p>&nbsp;</p>
<blockquote>
<h3>自动加载</h3>
<p>symfony的加载包含一个特殊的文件：app/autoload.php。该文件负责自动加载src/和第三方库composer.json目录中的所有文件。</p>
<p>因为有自动加载器，你永远无须为使用include或require语句担心。Symfony2利用类的名称空间确定它的位置，并自动加载包含你所需的类文件。</p>
<p>自动加载器看起来已经配置了 src/ 目录下的任何php类。对于自动加载的工作原理，类名和文件路径必须遵循相同的模式：</p><pre class="crayon-plain-tag">Class Name:
    Acme\DemoBundle\Controller\RandomController
Path:
    src/Acme/DemoBundle/Controller/RandomController.php</pre><p>
</p></blockquote>
<h3> 源（src）目录</h3>
<p>简单的说，src/ 目录包含所有的实际代码（php代码，模板，配置文件，样式表等）驱动你的应用程序。在开发，你工作的绝大多数时间会进入你这个目录中，创建一个或多个bundle。</p>
<p>究竟有什么事bundle？</p>
<p><strong>这个bundle系统</strong></p>
<p>一个bundle在symfony中类似一个插件，甚至更加重要。关键的不同点在于在Symfony2中什么都是bundle，包括框架的核心功能和你为应用程序所写的代码。在Symfony2中，Bundle是一类公民，这让使用第三方Bundle的预建功能包或发布你自己的Bundle变得十分灵活。它也可以使你很容易地选择应用程序所需功能，并用你自己的方式去优化它们。</p>
<blockquote><p>您将在这里学习基本的知识，整个cookbook致力于bundle的组织和最佳实践。</p></blockquote>
<p>Bundle简单来说就是在一个目录里用来实现单一功能的结构化文件集。你可以创建BlogBundle、ForumBundle或用户管理的Bundle（许多都已经以开源Bundle的形式存在）。每个目录都包含与功能相关的内容，如PHP文件、模板、样式表、Javascripts、测试等。每个Bundle都包含某种功能的方方面面，而每种功能都必须在Bundle中实现。</p>
<p>应用程序由在AppKernel类中的registerBundles()方法中定义的Bundle组成：</p><pre class="crayon-plain-tag">// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
        new Symfony\Bundle\SecurityBundle\SecurityBundle(),
        new Symfony\Bundle\TwigBundle\TwigBundle(),
        new Symfony\Bundle\MonologBundle\MonologBundle(),
        new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
        new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
        new Symfony\Bundle\AsseticBundle\AsseticBundle(),
        new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
    );

    if (in_array($this-&gt;getEnvironment(), array('dev', 'test'))) {
        $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
        $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
        $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
        $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
    }

    return $bundles;
}</pre><p>通过registerBundles()方法，你就拥有了应用程序所有Bundles的全部控制权（包含Symfony2的核心Bundle）。</p>
<blockquote><p>一个bundle可以在任何地方，只要他可以自动加载（通过配置自动加载机制 app/autoload.php）。</p>
<p>&nbsp;</p></blockquote>
<h3>创建一个Bundle</h3>
<p>symfony标准版自带了一个方便任务为您创建了全功能的bundle。当然，手工创建一个bundle也是很容易的。</p>
<p>为了向你展示Bundle系统是如何之简单，让我们创建一个名为AcmeTestBundle的新Bundle，并激活它。</p>
<blockquote><p>Acme部分只是一个假名字,取而代之的是一些“供应商”的名字,代表您或您的组织(例如ABCTestBundle对应公司名叫ABC)。</p></blockquote>
<p>首先，创建一个src/Acme/TestBundle/ 目录，并添加一个名为AcmeTestBundle.php的新文件：</p><pre class="crayon-plain-tag">// src/Acme/TestBundle/AcmeTestBundle.php
namespace Acme\TestBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeTestBundle extends Bundle
{
}</pre><p>AcmeTestBundle遵循<a href="http://symfony.com/doc/current/cookbook/bundles/best_practices.html#bundles-naming-conventions" target="_blank">Bundle命名约定</a>。</p>
<p>这个空类仅仅只是我们需要创建新Bundle的一部分。虽然是空的，但这个类已经足够强大，并能够用来自定义Bundle的行为。<br />
现在我们已经创建了我们的Bundle，我们需要通过Appkernel类激活它：</p><pre class="crayon-plain-tag">// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        ...,
        // register your bundles
        new Acme\TestBundle\AcmeTestBundle(),
    );
    // ...

    return $bundles;
}</pre><p>虽然目前它还不能做任何事情，但AcmeTestBundle现在已经可以使用了。</p>
<p>同样方便的是，Symfony也提供命令行接口去生成Bundle的基本框架：</p><pre class="crayon-plain-tag">$ php app/console generate:bundle --namespace=Acme/TestBundle</pre><p>命令行会生成一个基本控制器、模板和可自定义的路由资源。接下来我们将会讨论更多的Symfony2命令行工具。</p>
<p>当创建一个新的bundle或者使用第三方bundle，总是你想registerBundles()自动加入并启用这些bundle。那么当你使用 <span style="color: #339966">generate:bundle </span>命令时，他就会为你完成。</p>
<p>&nbsp;</p>
<h3>bundle的目录结构</h3>
<p>Bundle的目录结构是简单而灵活的。默认状态下，Bundle系统遵循Symfony2所有Bundle之间保持代码一致性的约定集。让我们看看AcmeDemoBundle，因为它包含了Bundle的大多数元素：</p>
<ul>
<li>Controller/目录：包含该Bundle的控制器（如：RandomController.php）；</li>
<li>DependencyInjection/ 持有一定的依赖注入扩展类，他可以导入服务配置，注册编译通过，当然（这个目录也可能是不必要的，如果你不需要做这些）。</li>
<li>Resources/config/目录：配置目录，包括路由配置（如：routing.yml）；</li>
<li>Resources/views/目录：通过控制器名组织的模板（如：Hello/index.html.twig）；</li>
<li>Resources/public/目录：包含web资源（图片、样式表等），并被拷贝或软链接到项目的web/目录；</li>
<li>Tests/目录：存放该Bundle的所有测试。</li>
</ul>
<p>根据Bundle实现的功能，它可小可大，它只包含你所需要的文件。</p>
<p>你在本书中还将学习到如何持久化对象到数据库、创建和验证表单、翻译你的应用程序和编写测试等等，它们在Bundle中都有自己的位置和所扮演的角色。</p>
<p>&nbsp;</p>
<h3>应用程序配置</h3>
<p>应用程序由代表应用程序所有功能和特征的Bundle集构成。每个Bundle都可以通过YAML、XML或PHP编写的配置文件来自定义。缺省情况下，主配置文件放置在app/config/目录中，被命名为config.yml、config.xml或config.php，这取决于你所使用的格式：</p><pre class="crayon-plain-tag"># app/config/config.yml
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }

framework:
    secret:          "%secret%"
    router:          { resource: "%kernel.root_dir%/config/routing.yml" }
    # ...

# Twig Configuration
twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"

# ...</pre><p></p>
<blockquote><p>在下一节<a href="http://symfony.com/doc/current/book/page_creation.html#environments" target="_blank">环境</a>中你将学会如何准确地选择要引导的文件/格式。</p></blockquote>
<p>每一个顶级条目，如framework或twig都被配置成一个特定的Bundle。例如，framework被配置成Symfony2的核心FrameworkBundle，并包含路由、模板和其它核心系统的配置。</p>
<p>现在别担心配置文件中各段中的特定配置选项，配置文件缺省值都是合理的。当你浏览Symfony2的各部分时，你将学到每个部分的特定配置选项。</p>
<blockquote><p>配置格式</p>
<p>纵观整个章节，所有的配置示例都用三种格式（YAML、XML和PHP）展示。它们每个都有自己的优缺点，以下是三种格式的说明：</p>
<p>1、YAML：简单、干净和易读<br />
2、XML：有时比YAML更强大且支持IDE的自动完成<br />
3、PHP：非常强大，但与标准配置格式相比易读性差</p>
<p>&nbsp;</p></blockquote>
<h3>默认配置 dump</h3>
<p>您能够通过命令行  config:dump-reference  去dump 一个bundle在YAML的默认配置。这里有一个例子就是dump frameworkBundle配置：</p><pre class="crayon-plain-tag">$ app/console config:dump-reference FrameworkBundle</pre><p>扩展别名(配置键)也可以使用:</p><pre class="crayon-plain-tag">$ app/console config:dump-reference framework</pre><p></p>
<blockquote><p>查看cookbook 文章：<a href="http://symfony.com/doc/current/cookbook/bundles/extension.html" target="_blank">How to Load Service Configuration inside a Bundle</a> 的信息添加配置到自己的bundle</p></blockquote>
<h3>环境</h3>
<p>应用程序可以在不同的环境中运行。不同的环境共享相同的PHP代码（由前端控制 器区分），但却有着完全不同的配置。开发环境记录警告和错误，而生产环境只记录错误。在开发环境中一些文件在每次请求之后被重构，而在生产环境中却被缓存 。所有的环境都在同一机制中生活。</p>
<p>虽然创建新的环境是容易的，但Symfony2项目通常会从三个环境开始（开发、测试和生产）。通过在你浏览器中改变前端控制器，你可以很方便地让应用程序在不同的环境中切换。要将应用程序切换到开发环境，只需要通过开发前端控制器去访问应用程序即可。</p><pre class="crayon-plain-tag">http://localhost/app_dev.php/random/10</pre><p>如果你想看看你的应用程序在生产环境中的表现 ，可以调用生产前端控制器：</p><pre class="crayon-plain-tag">http://localhost/app.php/random/10</pre><p>由于生产环境优化了速度；这个项目中的配置，路由和twig模板都会被编译成原生php类并加入到缓存。当生产环境发生变化后，你需要清楚缓存并让他重建。</p><pre class="crayon-plain-tag">$ php app/console cache:clear --env=prod --no-debug</pre><p></p>
<blockquote><p>如果你打开 web/app.php文件，你将发现它已经很明确地被配置成使用生产环境：</p><pre class="crayon-plain-tag">$kernel = new AppKernel('prod', false);</pre><p>&nbsp;<br />
你可以为一个新的环境创建一个新的前端控制器，只需要拷贝该文件，并将prod修改成其它值。</p></blockquote>
<blockquote><p>当进行自动测试时使用<a href="http://symfony.com/doc/current/book/testing.html" target="_blank">测试环境</a>，它并不能从浏览器直接访问。参见测试章节以得到更多细节。</p></blockquote>
<p>&nbsp;</p>
<h3>环境配置</h3>
<p>AppKernel类负责加载你所选的配置文件：</p><pre class="crayon-plain-tag">// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader-&gt;load(
        __DIR__.'/config/config_'.$this-&gt;getEnvironment().'.yml'
    );
}</pre><p>我们已经知道.yml扩展名可以转换成.xml或.php，只要你喜欢使用XML或PHP来写配置。注意每种环境也可以加载它们自己的配置文件。下面是为生产环境准备的配置文件。</p><pre class="crayon-plain-tag"># app/config/config_dev.yml
imports:
    - { resource: config.yml }

framework:
    router:   { resource: "%kernel.root_dir%/config/routing_dev.yml" }
    profiler: { only_exceptions: false }

# ...</pre><p>&nbsp;</p>
<p>import关键词与PHP格式中include语句一样，都是首先引导主配置文件（config.yml），文件的其它部分是为了增长的日志和其它有利于开发环境的设置而对缺省配置进行的调整。</p>
<p>在生产环境和测试环境都遵循同样一个模型：每个环境导入基本配置文件，然后修改它们的配置值去适应特殊环境的需要。这只是一个惯例，但可以重用你的配置并仅仅定义区块到这个环境中。</p>
<p>&nbsp;</p>
<p><strong>小结</strong></p>
<p>恭喜你，你现在已经明白了Symfony2的基本原理，并惊喜地发现它是那样的方便灵活。尽管有许多的功能，但我们可以牢记以下几个基本点：</p>
<ul>
<li>创建页面需要三个步骤，包括路由、控制器和模板（可选）；</li>
<li>每个应用程序都应该包含四个目录：web/（web资源和前端控制器）、app/（配置）、src/（你的Bundle）和vendor/（第三方代码）（还有一个bin/ 目录有助于帮助应用更新vendor 库）；</li>
<li>Symfony2的每个功能（包括Symfony2框架核心）都被组织进一个Bundle，Bundle是该功能的结构化文件集；</li>
<li>每个Bundle的配置都存放在Resources/config目录中，可以使用YAML、XML和PHP编写；</li>
<li>这个全局应用配置被放到 app/config 目录下；</li>
<li>通过不同的前端控制器（如：app.php或app_dev.php）和配置文件，每种环境都可以被访问。</li>
</ul>
<p>从本章开始，接下来的每个章节都将向你介绍越来越多的强大工具和越来越先进的理念。随着你对Symfony2了解得越多，你就越发欣赏它架构的灵活和强大，它可以让你更加快速地开发应用程序。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/42">第四章：在symfony2中创建页面</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/42/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>第三章：安装和配置symfony2</title>
		<link>http://www.newlifeclan.com/symfony/archives/40</link>
		<comments>http://www.newlifeclan.com/symfony/archives/40#comments</comments>
		<pubDate>Sun, 05 Oct 2014 11:07:34 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=40</guid>
		<description><![CDATA[<p>这一章的目的是教你如何在symfony上运行一个web应用程序。很幸运，symfony提供“发行版”，包含供开 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/40">第三章：安装和配置symfony2</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>这一章的目的是教你如何在symfony上运行一个web应用程序。很幸运，symfony提供“发行版”，包含供开发者借鉴的示例代码。 如果你想了解如何创建新的Symfony2项目，并对代码进行版本管理，请阅读： <em><span style="text-decoration: underline"><a href="http://symfony.com/doc/current/book/installation.html#using-source-control">代码版本管理 </a></span></em>。<br />
<span id="more-40"></span></p>
<h2>安装symfony2发行版</h2>
<blockquote><p>首先,检查你是否已经安装和配置了PHP Web服务器(例如Apache)。关于更多的symfony2配置要求，你可以查看<em><span style="text-decoration: underline"><a href="http://symfony.com/doc/current/reference/requirements.html" target="_blank"> requirements reference</a></span></em></p></blockquote>
<p>Symfony2有不同的“发行版”供你选择，但它们都包含了：基于Symfony2框架的应用程序，一些有用的第三方代码包，推荐的文件目录结构以及默认的配置。下载一个Symfony2的发行版，相当于得到了一个可以立即运行的代码骨架，有了这个基础，你可以快速开发你自己的应用。 你可以访问Symfony2的下载页面来获得Symfony2的发行版：<em><span style="text-decoration: underline"><a href="http://symfony.com/download" target="_blank"> http://symfony.com/download</a></span></em> 。总共有两种方式下载symfony： 第一种 composer <em><span style="text-decoration: underline"><a href="https://getcomposer.org/" target="_blank">composer</a></span></em> 是一个php的依赖管理库，你能够使用它来下载symfony2发行版。 首先 <em><span style="text-decoration: underline"><a href="https://getcomposer.org/download/" target="_blank">下载composer</a></span></em> 到计算机的任意位置，如果安装了curl，也很一样的简单：</p><pre class="crayon-plain-tag">$ curl -s https://getcomposer.org/installer | php</pre><p></p>
<blockquote><p>如果你的电脑没有准备好composer，当你运行以上命令的时候会有一些提示，你遵循这些提示去工作就可以正常运行composer了。</p></blockquote>
<p>composer是一个可执行的phar文件，你能够使用它去下载发行版：</p><pre class="crayon-plain-tag">$ php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/Symfony '~2.6'</pre><p></p>
<blockquote><p>更快的下载vendor所有文件，我们需要再composer命令行中尾部加入 &#8211;prefer-dist 配置</p></blockquote>
<p>这个命令可能需要几分钟才能从composer中下载所需要的symfony发行版。当他完成下载后，你应该看到一个目录，他应该是这个样子：</p><pre class="crayon-plain-tag">path/to/webroot/ &lt;- your web server directory (sometimes named htdocs or public)
    Symfony/ &lt;- the new directory
        app/
            cache/
            config/
            logs/
        src/
            ...
        vendor/
            ...
        web/
            app.php
            ...</pre><p>第二种 直接下载 在官网直接下载，你有两种选择：</p>
<ul>
<li>下载 .tgz 或者 .zip 后缀的文件；</li>
<li>下载没有vendors的版本。如果你日后使用composer管理即可在使用时下载更多的第三方库和很多的bundles，你应该下载没有vendors“without vendors”。</li>
</ul>
<p>把下载下来的压缩包，解压到你本地的站点根目录，并打开它。 如果使用UNIX命令行，你可以输入以下命令（注意将 ### 替换为实际的文件名）：</p><pre class="crayon-plain-tag"># for .tgz file
$ tar zxvf Symfony_Standard_Vendors_2.6.###.tgz

# for a .zip file
$ unzip Symfony_Standard_Vendors_2.6.###.zip</pre><p>如果你下载的是 “ without vendors”，你需要阅读下面一个章节。</p>
<blockquote><p>你可以很容易的覆盖这个默认的目录结构。请看<em><span style="text-decoration: underline"><a href="http://symfony.com/doc/current/cookbook/configuration/override_dir_structure.html" target="_blank">如何覆盖Symfony的默认目录结构</a>。</span></em></p></blockquote>
<p>在symfony2应用程序的symfony/web 目录处理所有的公共文件和前端控制器文件的请求。 所以你的web服务或者虚拟空间应该指定到web目录为更目录，也就是以http://localhost/Symfony/web/，为你应用程序的URLs</p>
<blockquote><p>你不去碰案例中的根目录那么所有的URLs开始都是http://localhost/Symfony/web/</p></blockquote>
<p>升级vendors 这时，你已经下载了一个全功能的symfony项目区开发你自己的应用程序。一个好的symfony项目需要你去依赖大量的外部和第三方库。在这里你需要用Composer去下载vendor目录下的一些有用库。 你在下载symfony的vendor的依赖时可以升级或者不去升级这些为最新版本。但是，升级这些vendors先的库总是安全的，也可以保证你的程序需要。</p>
<p><strong>步骤一 ：请求 composer （伟大的php包管理系统）</strong></p><pre class="crayon-plain-tag">$ curl -s http://getcomposer.org/installer | php</pre><p>一定要确保你下载的composer.phar 是和composer.json在一个目录下（他默认在symfony项目的根目录下）</p>
<p><strong> 步骤二：安装 vendors</strong></p><pre class="crayon-plain-tag">$ php composer.phar install</pre><p>这个命令是去下载vendor必须的库&#8211;包括symfony自身库-也在这个目录下。</p>
<blockquote><p>如果你没有curl安装，你可以手动到这个 <a class="reference external" style="color: #005599" href="http://getcomposer.org/installer">http://getcomposer.org/installer</a> 网址下载。拷贝到你的项目目录下并运行：</p><pre class="crayon-plain-tag">$ php installer
$ php composer.phar install</pre><p>
</p></blockquote>
<blockquote><p>当运行 php composer.phar install 和 php composer.phar update时，Composer需要执行install/update 命令去清楚缓存和安装资源。默认这些资源将被复制到你的web目录。 如果你系统支持symlinks，而你只需要创建symlinks，不用复制你的symfony资源。去创建symlinks，需要在composer.json文件中的extra节点下加入symfony-assets-install并给他赋值为symlink：</p><pre class="crayon-plain-tag">"extra": {
    "symfony-app-dir": "app",
    "symfony-web-dir": "web",
    "symfony-assets-install": "symlink"
}</pre><p>当symfony-assets-install相对应的是symlink时，这个命令将生成相对应的链接。</p></blockquote>
<h2>配置和安装</h2>
<p>这时，所有的最新的第三方库都在vendor/ 目录下。你还需要默认的应用程序设置在 app/ 里和一些简单的代码在 src/ 目录下。 symfony2自带了一个服务器配置检查工具去帮助你检查web服务和php的配置是否符合symfony的要求。你只要输入以下链接：</p><pre class="crayon-plain-tag">http://localhost/config.php</pre><p>如果你有任何的问题，现在纠正它然后再继续。</p>
<blockquote>
<h3>设置权限</h3>
<p>一个常见的问题是，Web服务器和你做开发使用的用户帐户都需要 app/cache 和 app/logs 目录的写权限。如果你使用的是UNIX系统，而你的Web服务器用户和开发帐户不是同一个，你可以运行下面的命令来确保有正确的文件权限。</p>
<p>&nbsp;</p>
<p><strong>1. 如果你的系统支持通过 chmod +a 来配置ACL（访问控制）</strong></p>
<p>多数系统都允许你使用 chmod +a 命令，如果系统报错，你可以尝试方法2。它使用一个命令来确定您的web服务器用户并把它作为HTTPDUSER：</p><pre class="crayon-plain-tag">$ rm -rf app/cache/*
$ rm -rf app/logs/*

$ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1`
$ sudo chmod +a "$HTTPDUSER allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
$ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs</pre><p>&nbsp;</p>
<p><strong>2. 不支持 chmod +a 的系统</strong></p>
<p>有的系统不支持 chmod +a ，但有 setfacl 工具可以用来完成同样的任务。你可能需要在相应的分区 启用文件系统ACL ，并安装setfacl工具（Ubuntu系统即是这个情况），然后运行如下命令：</p><pre class="crayon-plain-tag">$ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1`
$ sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs
$ sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs</pre><p>如果这不起作用,尝试添加- n选项。</p>
<p>&nbsp;</p>
<p><strong>3. 没有ACL</strong></p>
<p>如果你没法启用ACL，你也可以通过修改umask，使cache和logs目录有用户组或全局可写的权限。即，在 app/console ， web/app.php 和 web/app_dev.php 文件里增加以下语句：</p><pre class="crayon-plain-tag">umask(0002); // This will let the permissions be 0775

// or

umask(0000); // This will let the permissions be 0777</pre><p><span style="color: #000000">更推荐使用ACL，因为修改umask不是线程安全的。</span></p>
<p>&nbsp;</p>
<p><strong>4.使用相同的用户</strong></p>
<p>在开发环境中，这是一种常见的设置，unix用户和web服务用户使用同一个账号，以避免进行权限设置时出现问题。可以通过编辑你的web服务配置（例如常见的Apache httpd.conf和apache2.conf）和 设置与他一样的用户（例如Apache更新用户和组）。</p></blockquote>
<p>一切都设置好后，点击“go to the welcome page” 去请求“真正” 的symfony2页面：</p><pre class="crayon-plain-tag">http://localhost/app_dev.php/</pre><p>到目前为止祝贺你成功配置symfony2并看到了欢迎页面！<br />
<a href="http://symfony.newlifeclan.com/wp-content/uploads/2014/05/welcome.png"><img class="alignnone size-full wp-image-87" src="http://symfony.newlifeclan.com/wp-content/uploads/2014/05/welcome.png" alt="welcome" width="1265" height="731" /></a></p>
<blockquote><p>你想有一个和上面一样的链接或者短连接你应该把你的web服务或者虚拟目录指定到Symfony/web目录下。虽然这不是必须要这样设计，但是我们推荐你的应用程序在所有的系统运行，配置文件都不应该让不该看的人看到。如何配置你的web服务器根目录，请参考“<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html" target="_blank">配置一个web服务</a></em></span>”或者查阅你使用的web服务的官方文档：”<span style="text-decoration: underline"><em><a href="http://httpd.apache.org/docs/current/mod/core.html#documentroot" target="_blank">Apache</a></em></span> | <span style="text-decoration: underline"><em><a href="http://wiki.nginx.org/Symfony" target="_blank">Nginx</a></em></span>“。</p></blockquote>
<h3></h3>
<p>开始开发</p>
<p>现在你已经有了一个全功能的Symfony2应用程序，你可以开始开发了！你们的发行版里包含一些示例代码-你可以阅读 README.rst 文件来确认哪些代码被包含在你所使用的发行版里，并了解如何在不需要的时候移除它们。<br />
如果你刚接触Symfony，”<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/book/page_creation.html" target="_blank"> Creating Pages in Symfony2 </a></em></span>“将向你介绍如何在你新建的Web应用里基于Symfony来创建页面，修改配置等等。<br />
一定要看一看 <span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/cookbook/index.html" target="_blank">Cookbook</a></em></span> 章节，它包含了各种各样的symfony问题和解决方法。</p>
<blockquote><p>如果你想删除发行版中的一些实例代码，看一看cookbook 文章”<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/cookbook/bundles/remove.html" target="_blank">怎样移除AcmeDemoBundle</a></em></span>“</p></blockquote>
<h3>代码版本管理</h3>
<p>如果你打算用 Git 或者 Subversion 来管理你的代码，你可以照常操作，并将Symfony2的标准发行版作为你项目的起点。</p>
<p>要了解如何更好地用Git来管理你的Symfony项目，请阅读：<span style="text-decoration: underline"><em> <a href="http://symfony.com/doc/current/cookbook/workflow/new_project_git.html" target="_blank">How to Create and store a Symfony2 Project in git</a> </em></span>。</p>
<h3>忽略 vendor/ 目录</h3>
<p>如果你使用的是不包含第三方代码的发行版，或者你了解如何使用Symfony2自带的脚本通过Git来管理代码依赖，你可以在 .gitignore 文件里忽略 vendor/ 目录：</p><pre class="crayon-plain-tag">/vendor/</pre><p>这样，你的vendor目录不会被提交到Git。其他人如果需要参与项目，他只需要检出相对少很多的文件，并通过 php bin/vendors install 命令来下载必须的第三方代码。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/40">第三章：安装和配置symfony2</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/40/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第二章：Symfony2 VS 原生php</title>
		<link>http://www.newlifeclan.com/symfony/archives/35</link>
		<comments>http://www.newlifeclan.com/symfony/archives/35#comments</comments>
		<pubDate>Sun, 05 Oct 2014 11:00:47 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[book]]></category>
		<category><![CDATA[book 2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=35</guid>
		<description><![CDATA[<p>为什么说Symfony2超过一些框架和自己写原生php？ 如果你从来没有使用过一个php框架，也不了解MVC， [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/35">第二章：Symfony2 VS 原生php</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<h3>为什么说Symfony2超过一些框架和自己写原生php？</h3>
<p>如果你从来没有使用过一个php框架，也不了解MVC，或者对关于Symfony2好处的传言感到好奇，那本章正是为你准备的。我们并不会灌输为什么Symfony2可以帮助你更快更好地开发代码，而是让你自己做这个判断。<br />
本章将让你用纯PHP写一个简单的应用程序，然后将其重构，使之更有条理。你将会穿越时间，了解为什么网站开发在过去几年中会演变成现在这样。<br />
然后你将体会到为什么Symfony2可以让开发工作不再繁琐，让你真正掌控你的代码。<br />
<span id="more-35"></span></p>
<h2>用原生php实现一个简单的博客</h2>
<p>首先，用原生PHP来实现一个博客程序。博客程序至少应有一个页面用来显示数据库里所保存的文章。代码非常简单：</p><pre class="crayon-plain-tag">&lt;?php
// index.php
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

$result = mysql_query('SELECT id, title FROM post', $link);
?&gt;

&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;List of Posts&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;List of Posts&lt;/h1&gt;
        &lt;ul&gt;
            &lt;?php while ($row = mysql_fetch_assoc($result)): ?&gt;
            &lt;li&gt;
                &lt;a href="/show.php?id=&lt;?php echo $row['id'] ?&gt;"&gt;
                    &lt;?php echo $row['title'] ?&gt;
                &lt;/a&gt;
            &lt;/li&gt;
            &lt;?php endwhile; ?&gt;
        &lt;/ul&gt;
    &lt;/body&gt;
&lt;/html&gt;

&lt;?php
mysql_close($link);
?&gt;</pre><p>虽然代码写起来很快，运行速度也不慢，但随着你的程序规模越来越大，维护这种风格的代码将变得越来越麻烦。可能遇到的问题包括：</p>
<ul>
<li><strong>没有错误检查</strong>: 如果数据库连接没有创建成功呢？</li>
<li><strong>代码结构差</strong>: 随着代码的增多，文件将越来越大，变得不便维护。想象一下，要增加对表单的处理，代码应该写在什么位置？又如何验证数据？或者你需要发送邮件？</li>
<li><strong>难以重用代码</strong>: 如果所有的代码都是在一个文件里，如果你需要增加一个别的页面，那该如何重用你已经写好的代码逻辑呢？</li>
</ul>
<blockquote><p>另外一个没有指出的问题是，例子里的代码只能用来连接MySQL数据库。而Symfony2整合了<span style="text-decoration: underline"><em><a class="reference external" style="color: #355f7c;text-decoration: underline" href="http://www.doctrine-project.org/">Doctrine</a></em></span>，从而可以实现数据库操作的抽象，以及表字段的映射。</p></blockquote>
<h4>抽离表现层</h4>
<p>将包含了HTML的“表现层”代码单独保存为一个文件，然后在主“逻辑”文件里引用，可以实现与前面相同的效果：</p><pre class="crayon-plain-tag">&lt;?php
// index.php
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

$result = mysql_query('SELECT id, title FROM post', $link);

$posts = array();
while ($row = mysql_fetch_assoc($result)) {
    $posts[] = $row;
}

mysql_close($link);

// 去包含HTML的代码 
require 'templates/list.php';</pre><p><span style="color: #2c2c2c">现在HTML代码都保存在一个独立的文件（templates/list.php）中，</span><span style="color: #000000">文件里嵌套的是模板风格的PHP代码：</span></p><pre class="crayon-plain-tag">&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;文章列表&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;文章列表&lt;/h1&gt;
        &lt;ul&gt;
            &lt;?php foreach ($posts as $post): ?&gt;
            &lt;li&gt;
                &lt;a href="/read?id=&lt;?php echo $post['id'] ?&gt;"&gt;
                    &lt;?php echo $post['title'] ?&gt;
                &lt;/a&gt;
            &lt;/li&gt;
            &lt;?php endforeach; ?&gt;
        &lt;/ul&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p>根据惯例，例子中的index.php文件包含了应用程序中所有的“逻辑”，被称为“控制器”。控制器这个术语，无论你使用的是框架还是语言，你都将会经常听到，简单来说它就是指你处理用户输入和准备响应的代码。</p>
<p>在上面的例子里，我们的控制器从数据库里读取数据，然后调用一个模板文件来呈现这些数据。通过分离控制器的代码，你将可以轻松地修改模板文件，比如以另外的格式（如创建一个对应JSON格式的list.json.php 模板）来输出博客文章。</p>
<h3>应用程序（域）逻辑分离</h3>
<p>到目前为止，应用程序只有一页。但是,如果第二个页面需要使用相同的数据库连接或者需要相同的博客文章数组呢？重构整个程序，从应用程序中将核心行为和数据访问功能分离出来放入新的model.php文件中。</p><pre class="crayon-plain-tag">&lt;?php
// model.php
function open_database_connection()
{
    $link = mysql_connect('localhost', 'myuser', 'mypassword');
    mysql_select_db('blog_db', $link);

    return $link;
}

function close_database_connection($link)
{
    mysql_close($link);
}

function get_all_posts()
{
    $link = open_database_connection();

    $result = mysql_query('SELECT id, title FROM post', $link);
    $posts = array();
    while ($row = mysql_fetch_assoc($result)) {
        $posts[] = $row;
    }
    close_database_connection($link);

    return $posts;
}</pre><p></p>
<blockquote><p>使用model.php来命名是因为应用程序逻辑和数据访问传统上被称为“Model”层。在一个代码组织良好的应用程序中，大多数代表“业务逻辑”的代码都在“Model”层中（而非控制器中）。而不象本例中模型（Model）只关注数据库访问。</p></blockquote>
<p>现在的控制器(index.php)变得十分简单：</p><pre class="crayon-plain-tag">&lt;?php
require_once 'model.php';

$posts = get_all_posts();

require 'templates/list.php';</pre><p>现在控制器的唯一任务就是从应用程序的“Model”层中得到数据，然后调用一个模板来呈现这些数据。这是一个最简单的MVC模式。</p>
<h3>布局分离</h3>
<p>现在应用程序已经明显被重构成三个有着不同优势的部分，并且在不同的页面中有机会重用几乎所有的东西。<br />
在代码中唯一不能被重用的就只有布局了，因此创建一个新的layout.php文件来修复这个问题。</p><pre class="crayon-plain-tag">&lt;!-- templates/layout.php --&gt; 
    &lt;html&gt; 
        &lt;head&gt; 
            &lt;title&gt;&lt;?php echo $title ?&gt;&lt;/title&gt; 
        &lt;/head&gt; 
        &lt;body&gt; 
            &lt;?php echo $content ?&gt; 
        &lt;/body&gt; 
    &lt;/html&gt;</pre><p>现在模板文件（templates/list.php）可以简单地从layout.php文件中“扩展”出来。</p><pre class="crayon-plain-tag">&lt;?php $title = 'List of Posts' ?&gt;

&lt;?php ob_start() ?&gt;
    &lt;h1&gt;List of Posts&lt;/h1&gt;
    &lt;ul&gt;
        &lt;?php foreach ($posts as $post): ?&gt;
        &lt;li&gt;
            &lt;a href="/read?id=&lt;?php echo $post['id'] ?&gt;"&gt;
                &lt;?php echo $post['title'] ?&gt;
            &lt;/a&gt;
        &lt;/li&gt;
        &lt;?php endforeach; ?&gt;
    &lt;/ul&gt;
&lt;?php $content = ob_get_clean() ?&gt;

&lt;?php include 'layout.php' ?&gt;</pre><p>现在你已经知道了重用布局（layout）的方法。但不幸地是，要实现这个方法，你不得不在模板中使用一些诸如ob_start()、ob_get_clean()这样丑陋的PHP函数。在Symfony2，可以使用Templating组件来让这一切变得干净和方便。</p>
<h2>添加一个博文显示页面</h2>
<p>博客的“列表”页已经重构并具有着更好的代码组织性和可重用性。为了证明这一点，添加一个博文“显示”页，用以显示单个博文，该博文通过ID参数标识来查询。</p>
<p>首先在model.php文件中新增一个函数，以便基于指定ID检索单个博文。</p><pre class="crayon-plain-tag">// model.php
function get_post_by_id($id)
{
    $link = open_database_connection();

    $id = intval($id);
    $query = 'SELECT date, title, body FROM post WHERE id = '.$id;
    $result = mysql_query($query);
    $row = mysql_fetch_assoc($result);

    close_database_connection($link);

    return $row;
}</pre><p>接下来创建一个新的show.php文件，作为新页面的控制器。</p><pre class="crayon-plain-tag">&lt;?php
require_once 'model.php';

$post = get_post_by_id($_GET['id']);

require 'templates/show.php';</pre><p>最后创建新的模板文件（templates/show.php），用以呈现单个博文。</p><pre class="crayon-plain-tag">&lt;?php $title = $post['title'] ?&gt;

&lt;?php ob_start() ?&gt;
    &lt;h1&gt;&lt;?php echo $post['title'] ?&gt;&lt;/h1&gt;

    &lt;div class="date"&gt;&lt;?php echo $post['date'] ?&gt;&lt;/div&gt;
    &lt;div class="body"&gt;
        &lt;?php echo $post['body'] ?&gt;
    &lt;/div&gt;
&lt;?php $content = ob_get_clean() ?&gt;

&lt;?php include 'layout.php' ?&gt;</pre><p>创建第二页非常容易，也没有复制代码。然而这一页还存在着很多挥之不去的问题，选择一个框架他可以为你解决。例如，缺省或无效的ID参数会引起页面的崩溃。如果能够引起404页面被渲染将会更好，但这一点并不容易做到。更糟地是，如果你忘记了用 mysql_real_escape_string()函数对ID参数进行清理的话，你将会把整个数据库陷入到被SQL注入攻击的危险境地之中。</p>
<p>另一个问题就是每一个控制器都必须包含model.php文件。如果每个控制器突然需要包含一个附加文件或者执行其它全局任务（如强制安全）呢？目前的情况是这些代码必须添加到每个控制器中文件中。如果你忘了包含某个文件，希望这不会给我们带来不安全的因素&#8230;</p>
<h2>解救一个前端控制器</h2>
<p>解决方案是使用一个<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/glossary.html#term-front-controller" target="_blank">前端控制器</a></em></span>:单个PHP文件，通过它来处理所有的请求。有了前端控制器，应用程序的URI略有变化，但开始变得灵活多样了。</p><pre class="crayon-plain-tag">没有前端控制器
/index.php          =&gt; Blog post list page (index.php executed)
/show.php           =&gt; Blog post show page (show.php executed)

使用index.php作前端控制器 
/index.php          =&gt; Blog post list page (index.php executed)
/index.php/show     =&gt; Blog post show page (index.php executed)</pre><p></p>
<blockquote><p>如果使用了Apache的rewrite规则（或相同功能）的话，URI中的index.php部分可以省略。这样的话，Blog显示页的URI结果就会简单地用/show来表示。</p></blockquote>
<p>当使用前端控制器时，单个PHP文件（在这里是index.php）将显示所有的请求，对于博文显示页来说，/index.php/show实际执行的是index.php，它现在负责基于全URI来进行内部路由请求。正如你看到的那样，前端控制器是个非常强大的工具。</p>
<h3>创建前端控制器</h3>
<p>你要在应用程序中采取重大举措了。一旦单个文件处理所有的请求，你可以集中进行诸如安全处理、配置加载和路由等事务的处理，在这个例子里，index.php要足够智能，以便根据请求的URL区别并渲染博客列表页和博文显示页。</p><pre class="crayon-plain-tag">&lt;?php
// index.php

// load and initialize any global libraries
require_once 'model.php';
require_once 'controllers.php';

// route the request internally
$uri = $_SERVER['REQUEST_URI'];
if ('/index.php' == $uri) {
    list_action();
} elseif ('/index.php/show' == $uri &amp;&amp; isset($_GET['id'])) {
    show_action($_GET['id']);
} else {
    header('Status: 404 Not Found');
    echo '&lt;html&gt;&lt;body&gt;&lt;h1&gt;Page Not Found&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;';
}</pre><p>为了更好地组织代码，将两个控制器（前身是index.php和show.php）写成两个PHP函数，并将其放入新的controllers.php文件中：</p><pre class="crayon-plain-tag">function list_action()
{
    $posts = get_all_posts();
    require 'templates/list.php';
}

function show_action($id)
{
    $post = get_post_by_id($id);
    require 'templates/show.php';
}</pre><p>作为前端控制器，index.php完全进入了一个新的角色：加载核心库并且路由整个应用程序，以便使两个控制器之一（list_action()或show_action()）被调用。实际上，前端控制器看来去也变得很象Symfony2处理请求和路由请求的机制了。</p>
<blockquote><p>前端控制器另一个优点就是灵活的URL。注意，博客显示页的URL只需在一个位置修改一下，就可以从/show变成/read，而在此之前需要将整个文件重命名。在Symfony2中，URL更加灵活。</p></blockquote>
<p>现在，应用程序已经从单个文件发展为拥有良好组织结构并允许代码重用的程序了。你应该更为高兴，但远未满足。例如，“路由”系统是多变的，不但应该可以通过/index.php来访问，也应该可以通过/来访问（如果添加了Apache重写规则的话）。此外，大量的时间花费在代码的“结构”（如路由、控制器调用和模板等）上，而非花在博客的开发上。你还需要在处理表单提交、输入验证、日志记录和安全上花费更多的时间。为什么你要重复设计这些日常问题的解决方案呢？</p>
<h3>接触一下symfony2</h3>
<p>symfony2前来救援了。在使用Symfony2之前，你需要先下载它。你可以使用composer，它可以下载正确的版本并自动下载相关依赖进行自动装载。自动加载器是一个工具，它可以在没有明确包含所用类文件时开始使用该类。<br />
在你的根目录，创建一个 composer.json 文件并在文件中加入以下内容：</p><pre class="crayon-plain-tag">{
    "require": {
        "symfony/symfony": "2.4.*"
    },
    "autoload": {
        "files": ["model.php","controllers.php"]
    }
}</pre><p>下一步，下载 composer 并运行以下命令，把symfony下载到一个 <span style="color: #444444">vendor/</span> 目录下：</p><pre class="crayon-plain-tag">$ php composer.phar install</pre><p>你在一旁下载依赖的时候，composer会生成一个<span style="color: #444444">vendor/autoload.php 文件，那么这个文件会自动装载所有的文件到symfony框架和composer.json 自动装载的文件一样。</span><br />
Symfony2哲学的核心思想是：应用程序的主要任务就是解释请求并返回响应。因此，Symfony2提供了Request类和Response类，这两个类是原始HTTP中处理请求和返回响应的面向对象的表述。使用它们来提升博客：</p><pre class="crayon-plain-tag">&lt;?php
// index.php
require_once 'vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$uri = $request-&gt;getPathInfo();
if ('/' == $uri) {
    $response = list_action();
} elseif ('/show' == $uri &amp;&amp; $request-&gt;query-&gt;has('id')) {
    $response = show_action($request-&gt;query-&gt;get('id'));
} else {
    $html = '&lt;html&gt;&lt;body&gt;&lt;h1&gt;Page Not Found&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;';
    $response = new Response($html, Response::HTTP_NOT_FOUND);
}

// echo the headers and send the response
$response-&gt;send();</pre><p></p>
<blockquote><p>symfony新的2.4版：已经支持了引入HTTP状态代码</p></blockquote>
<p>现在应用程序通过Response对象来返回响应。为了更加方便，你可以使用render_template()函数，该函数的行为很像Symfony2的模板引擎。</p><pre class="crayon-plain-tag">// controllers.php
use Symfony\Component\HttpFoundation\Response;

function list_action()
{
    $posts = get_all_posts();
    $html = render_template('templates/list.php', array('posts' =&gt; $posts));

    return new Response($html);
}

function show_action($id)
{
    $post = get_post_by_id($id);
    $html = render_template('templates/show.php', array('post' =&gt; $post));

    return new Response($html);
}

// helper function to render templates
function render_template($path, array $args)
{
    extract($args);
    ob_start();
    require $path;
    $html = ob_get_clean();

    return $html;
}</pre><p>通过使用Symfony2很小的一部分，应用程序变得更加灵活可靠。Request类提供了一个可靠的方法去访问HTTP的请求信息。具体来说，getPathInfo()方法返回一个干净的URI（它总是返回/show，而永远不会返回/index.php/show），因此即使用户通过/index.php/show来进入，应用程序也会足够智能地将请求路由到show_action()。<br />
在构造HTTP响应时，Response对象提供了足够的灵活性，它允许响应头和内容通过一个面向对象的接口添加到Response对象中，虽然应用程序中的响应十分简单，但当你应用程序增长时这种灵活性将带来好处。</p>
<h2>一个简单的symfony2应用程序</h2>
<p>博客程序一路编来，对于如此简单的应用程序，它也包含了大量的代码。我们构造了一个路由系统，并且还使用ob_start()和ob_get_clean()方法来呈现模板。如果出于某种原因，你还需要继续“从零开始”搭建“框架”，那么你至少可以使用Symfony2中的独立Routine组件和Templating组件，因为它们已经解决了这些问题。<br />
为了不用重新发明轮子，你可以让Symfony2来帮你实现，下面是相同的示例程序，只不过它们在Symfony2上实现。</p><pre class="crayon-plain-tag">// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function listAction()
    {
        $posts = $this-&gt;get('doctrine')
            -&gt;getManager()
            -&gt;createQuery('SELECT p FROM AcmeBlogBundle:Post p')
            -&gt;execute();

        return $this-&gt;render(
            'AcmeBlogBundle:Blog:list.html.php',
            array('posts' =&gt; $posts)
        );
    }

    public function showAction($id)
    {
        $post = $this-&gt;get('doctrine')
            -&gt;getManager()
            -&gt;getRepository('AcmeBlogBundle:Post')
            -&gt;find($id);

        if (!$post) {
            // cause the 404 page not found to be displayed
            throw $this-&gt;createNotFoundException();
        }

        return $this-&gt;render(
            'AcmeBlogBundle:Blog:show.html.php',
            array('post' =&gt; $post)
        );
    }
}</pre><p>这两个控制器依然是轻量级的，它们都使用Doctrine的ORM库到数据库中检索对象，并且使用Templating组件去渲染模板并返回响应。模板文件现在相当简单：</p><pre class="crayon-plain-tag">&lt;!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php --&gt;
&lt;?php $view-&gt;extend('::layout.html.php') ?&gt;

&lt;?php $view['slots']-&gt;set('title', 'List of Posts') ?&gt;

&lt;h1&gt;List of Posts&lt;/h1&gt;
&lt;ul&gt;
    &lt;?php foreach ($posts as $post): ?&gt;
    &lt;li&gt;
        &lt;a href="&lt;?php echo $view['router']-&gt;generate(
            'blog_show',
            array('id' =&gt; $post-&gt;getId())
        ) ?&gt;"&gt;
            &lt;?php echo $post-&gt;getTitle() ?&gt;
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;?php endforeach; ?&gt;
&lt;/ul&gt;</pre><p>布局文件几乎一样：</p><pre class="crayon-plain-tag">&lt;!-- app/Resources/views/layout.html.php --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;&lt;?php echo $view['slots']-&gt;output(
            'title',
            'Default title'
        ) ?&gt;&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;?php echo $view['slots']-&gt;output('_content') ?&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><p></p>
<blockquote><p>在这里我们将show模板留做练习，实现它相对于实现list模板来说几乎微不足道。</p></blockquote>
<p>在Symfony2引擎（我们称其为Kernel）启动时，它需要根据一个图判断请求信息需要执行哪个控制器。路由配置文件则提供了这样一张图。</p><pre class="crayon-plain-tag"># app/config/routing.yml
blog_list:
    path:     /blog
    defaults: { _controller: AcmeBlogBundle:Blog:list }

blog_show:
    path:     /blog/show/{id}
    defaults: { _controller: AcmeBlogBundle:Blog:show }</pre><p>现在Symfony2处理所有的日常任务时前端控制器却非常简单，而且内容又如此之少，它一旦被创建之后就无须再去接触它。（如果你使用Symfony2的发行版，你都无须去创建它）</p><pre class="crayon-plain-tag">// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel-&gt;handle(Request::createFromGlobals())-&gt;send();</pre><p>前端控制器的唯一工作就是初始化Symfony2的引擎(<span style="color: #444444">Kernel</span>)并将其传递给一个<tt class="docutils literal" style="color: #444444"><code>Request</code></tt><span style="color: #444444"> 对象。symfony2的核心再根据路由分析调用哪个controller 。像从前一样，控制器方法负责返回最终的<tt class="docutils literal"><code>Response</code></tt> 对象。<span style="color: #2c2c2c">对它来说就真的没有别的了。</span></span></p>
<p>至于Symfony2处理请求过程的可视化展示，参见<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/book/http_fundamentals.html#request-flow-figure">请求流程图</a></em></span> 。</p>
<h3>symfony从这里正式开始了</h3>
<p>在接下来的章节中，我们将学到更多关于Symfony2的各部分是如何工作的，以及推荐的项目组织形式。现在，让我们看看从纯PHP迁移到Symfony2上的博客程序优势：<br />
1、你的应用程序现在是干净的，并且代码组织良好（虽然Symfony2并未强制你做到这一点），这提高了程序的可重用性，并且新项目的开发者能够很快进入角色；<br />
2、你所写的代码100％是为了程序，而非开发或维护诸如<em><span style="text-decoration: underline"><a class="reference internal" style="color: #005599;text-decoration: underline" href="http://symfony.com/doc/current/book/page_creation.html#autoloading-introduction-sidebar">autoloading</a></span></em>、<span style="text-decoration: underline"><em><a class="reference internal" style="color: #005599;text-decoration: underline" href="http://symfony.com/doc/current/book/routing.html">routing</a></em></span>或渲染<span style="text-decoration: underline"><em><a class="reference internal" style="color: #005599;text-decoration: underline" href="http://symfony.com/doc/current/book/controller.html">controllers</a></em></span>这样的低级工具；<br />
3、Symfony2让你可以使用开源工具，象Doctrine、Templating、Security、Form、Validation和Translation组件等（仅举几个例子）；<br />
4、感谢路由组件让应用程序拥有十分灵活的URL<br />
5、Symfony2以HTTP为中心的架构可以让你使用强大的工具，例如使用Symfony2的HTTP内部缓存或更为强大的<span style="text-decoration: underline"><em><a href="https://www.varnish-cache.org/">Varnish</a></em></span>工具来实现HTTP<span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/book/http_cache.html">缓存</a></em></span>。这将在稍后的缓冲一章中进行说明<br />
最值得高兴的是，通过使用Symfony2，你现在可以获得一整套Symfony2社区开发的高品质开源工具集，更多详情请查阅<span style="text-decoration: underline"><em><a class="reference external" style="color: #005599;text-decoration: underline" href="http://knpbundles.com/">KnpBundles.com</a></em></span><span style="color: #444444">.</span> 。</p>
<h3>更好的模板</h3>
<p>Symfony2标配的模板引擎叫Twig，如果你选择使用它，它将使你的模板写得更快，也更易理解。这意味着示例程序可以使用更少的代码。例如，列表模板使用Twig书写如下：</p><pre class="crayon-plain-tag">{# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
{% extends "::layout.html.twig" %}

{% block title %}List of Posts{% endblock %}

{% block body %}
    &lt;h1&gt;List of Posts&lt;/h1&gt;
    &lt;ul&gt;
        {% for post in posts %}
        &lt;li&gt;
            &lt;a href="{{ path('blog_show', {'id': post.id}) }}"&gt;
                {{ post.title }}
            &lt;/a&gt;
        &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
{% endblock %}</pre><p>同样的，layout.html.twig更容易写：</p><pre class="crayon-plain-tag">{# app/Resources/views/layout.html.twig #}
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;{% block title %}Default title{% endblock %}&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        {% block body %}{% endblock %}
    &lt;/body&gt;
&lt;/html&gt;</pre><p><span style="color: #2c2c2c">Twig在Symfony2中被很好地支持。虽然PHP永远被Symfony2支持，但我们将继续讨论Twig的更多优势，详情请参见<em><span style="text-decoration: underline"><a href="http://symfony.com/doc/current/book/templating.html">模板章节</a></span></em>。</span></p>
<p>&nbsp;</p>
<p>学习更多的内容请来到cookbook</p>
<ul>
<li><span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/cookbook/templating/PHP.html">如何使用PHP模板而不是twig</a></em></span></li>
<li><span style="text-decoration: underline"><em><a href="http://symfony.com/doc/current/cookbook/controller/service.html">怎样定义一个控制器为一个服务</a></em></span></li>
</ul>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/35">第二章：Symfony2 VS 原生php</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/35/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
