<?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; Cook 2.6</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/category/cookbook/cookbook-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>(模板)如何在所有模板中注入变量(如：全局变量)</title>
		<link>http://www.newlifeclan.com/symfony/archives/618</link>
		<comments>http://www.newlifeclan.com/symfony/archives/618#comments</comments>
		<pubDate>Fri, 23 Oct 2015 04:03:41 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[模板]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=618</guid>
		<description><![CDATA[<p>有时你想你自己的一个变量能在symfony的所有模板中使用。比较合适的方式是在app/config/confi [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/618">(模板)如何在所有模板中注入变量(如：全局变量)</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>有时你想你自己的一个变量能在symfony的所有模板中使用。比较合适的方式是在app/config/config.yml文件中设置。<span id="more-618"></span></p><pre class="crayon-plain-tag"># app/config/config.yml
twig:
    # ...
    globals:
        ga_tracking: UA-xxxxx-x</pre><p>现在，这个<tt class="docutils literal"><code>ga_tracking</code></tt>变量就可以在所有twig模板中使用了：</p><pre class="crayon-plain-tag">&lt;p&gt;The google tracking code is: {{ ga_tracking }}&lt;/p&gt;</pre><p>是不是很简单！</p>
<p>&nbsp;</p>
<h2>使用服务容器参数</h2>
<p>你可以利用内置的服务参数系统，它可以让您分离或重用该值：</p><pre class="crayon-plain-tag"># app/config/parameters.yml
parameters:
    ga_tracking: UA-xxxxx-x</pre><p>&nbsp;</p><pre class="crayon-plain-tag"># app/config/config.yml
twig:
    globals:
        ga_tracking: "%ga_tracking%"</pre><p>同一个变量还是和以前一样使用。</p>
<p>&nbsp;</p>
<h2><strong>引用服务</strong></h2>
<p>除了使用静态变量，你还可以设置一个服务。每当全局变量在模板中访问时，该服务也就随之请求服务容器，你就能够获取这个对象。</p>
<blockquote><p>服务不会延迟加载。换句话说，当 Twig 被加载时，即使您从来没有使用全局变量，您的服务也会被实例化。</p></blockquote>
<p>要将服务定义为全局 Twig 变量，要在字符串前加“ <strong>@</strong> ”为前缀。这应该很熟悉了，因为他和你服务配置时一样都使用同样的语法。</p><pre class="crayon-plain-tag"># app/config/config.yml
twig:
    # ...
    globals:
        user_management: "@app.user_management"</pre><p>&nbsp;</p>
<h2>使用一个Twig扩展</h2>
<p>如果你想把全局变量变得更为复杂 &#8211; 比如说一个对象 &#8211; 那么你就不能使用上面的方法了。你需要去创建一个Twig Extension（Twig扩展）并在 <strong>getglobals</strong> 方法中返回一个全局变量。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/618">(模板)如何在所有模板中注入变量(如：全局变量)</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/618/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(Security)在登录表单使用CSRF保护</title>
		<link>http://www.newlifeclan.com/symfony/archives/315</link>
		<comments>http://www.newlifeclan.com/symfony/archives/315#comments</comments>
		<pubDate>Thu, 26 Mar 2015 06:33:55 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=315</guid>
		<description><![CDATA[<p>当使用登录表单时，你应该确保你的应用免受CSRF(跨站请求伪造)攻击。Security组件已经内置了对CSRF [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/315">(Security)在登录表单使用CSRF保护</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>当使用登录表单时，你应该确保你的应用免受CSRF(跨站请求伪造)攻击。Security组件已经内置了对CSRF的防御支持。在本文中，您将学习如何在登录表单使用它。<span id="more-315"></span></p>
<h2><b>配置CSRF保护</b></h2>
<p>首先，配置Security组件，它能够提供CSRF保护。这个Security组件需要一个 CSRF token provider。你可以把它设置为使用默认的表单组件。</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    firewalls:
        secured_area:
            # ...
            form_login:
                # ...
                csrf_provider: form.csrf_provider</pre><p>Security组件还可以进一步配置，但是login表单的CSRF的所有信息都在这里了。</p>
<p>&nbsp;</p>
<h2>渲染CSRF字段</h2>
<p>Security组件还需要检查CSRF token，你需要在login表单中添加一个CSRF token的hidden字段。默认情况下，这个字段的名称是 _csrf_token. 隐藏字段一定要包含CSRF token，这个值需要使用这个csrf_token 函数生成。那么当使用login表单时，一定要在这个函数中传人‘authenticate’，才能够最终生成登录的token id。</p><pre class="crayon-plain-tag">{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}

{# ... #}
&lt;form action="{{ path('login_check') }}" method="post"&gt;
    {# ... the login fields #}

    &lt;input type="hidden" name="_csrf_token"
        value="{{ csrf_token('authenticate') }}"
    &gt;

    &lt;button type="submit"&gt;login&lt;/button&gt;
&lt;/form&gt;</pre><p>在这之后，你就有能力保护你的表单免受CSRF攻击。</p>
<blockquote><p>你还能够在你的配置文件里改变你的csrf字段名称，你需要设置csrf_parameter。那么改变token id 需要设置intention：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    firewalls:
        secured_area:
            # ...
            form_login:
                # ...
                csrf_parameter: _csrf_security_token
                intention: a_private_string</pre><p>&nbsp;</p></blockquote>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/315">(Security)在登录表单使用CSRF保护</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/315/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(Security)如何去搭建一个传统的登录表单</title>
		<link>http://www.newlifeclan.com/symfony/archives/300</link>
		<comments>http://www.newlifeclan.com/symfony/archives/300#comments</comments>
		<pubDate>Tue, 24 Mar 2015 15:29:01 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[登录表单]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=300</guid>
		<description><![CDATA[<p>注意：如果你需要为存储在某种数据库中的用户做一个登录表单，那么你应该考虑使用FOSUserBundle，这有助 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/300">(Security)如何去搭建一个传统的登录表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>注意：如果你需要为存储在某种数据库中的用户做一个登录表单，那么你应该考虑使用FOSUserBundle，这有助于你建立你的User对象，还为您提供了常见的登录、注册、忘记密码的路由和控制器。</p>
<p><span id="more-300"></span></p>
<p>在此文章中，将构建一个传统的登录表单。当然，当用户登录时，你可以从数据库或者任何地方加载用户。</p>
<p>本章假设您已经遵循了security章节并且http_basic 认证正常工作。</p>
<p>首先，启用防火墙下表单登录</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    firewalls:
        default:
            anonymous: ~
            http_basic: ~
            form_login:
                login_path: /login
                check_path: /login_check</pre><p></p>
<blockquote><p>这个login_path和check_path也可以是路由名称（但不能有强制通配符例如 /login/{foo}）这里foo没有默认值</p></blockquote>
<p>现在，当安全系统启动认证过程，它会让用户跳转到登录表单 /login。你的工作是实现这个登录表单视觉。首先，创建一个新的SecurityController在bundle中：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SecurityController extends Controller
{
}</pre><p>下一步创建两个路由：分别是刚才form_login下的设置的两个路径（/login和/login_check）:</p>
<p>annotations</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php

// ...
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class SecurityController extends Controller
{
    /**
     * @Route("/login", name="login_route")
     */
    public function loginAction(Request $request)
    {
    }

    /**
     * @Route("/login_check", name="login_check")
     */
    public function loginCheckAction()
    {
        // this controller will not be executed,
        // as the route is handled by the Security system
    }
}</pre><p>&nbsp;</p>
<p>非常好！下一步，你要去添加loginAction的逻辑，并把需要的渲染到login表单：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php

public function loginAction(Request $request)
{
    $authenticationUtils = $this-&gt;get('security.authentication_utils');

    // get the login error if there is one
    $error = $authenticationUtils-&gt;getLastAuthenticationError();

    // last username entered by the user
    $lastUsername = $authenticationUtils-&gt;getLastUsername();

    return $this-&gt;render(
        'security/login.html.twig',
        array(
            // last username entered by the user
            'last_username' =&gt; $lastUsername,
            'error'         =&gt; $error,
        )
    );
}</pre><p></p>
<blockquote><p> 这个security.authentication_utils服务和<tt class="docutils literal"><code><em><span style="text-decoration: underline"><span style="color: #ff0000"><a class="reference external" style="color: #ff0000;text-decoration: underline" title="Symfony\Component\Security\Http\Authentication\AuthenticationUtils" href="http://api.symfony.com/2.6/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.html">AuthenticationUtils</a></span></span></em>类在symfony2.6都有介绍。</code></tt></p></blockquote>
<p>不要让这个控制器迷惑你。你看到当用户提交表单的这一刻，security系统会自动处理表单提交到这个控制器。如果用户提交了一个无效的用户名和密码，该控制器可以从security系统中读出表单提交的错误，以便把他显示给用户。</p>
<p>换句话说,你的工作是显示登录表单和任何可能发生的登录错误,但是security系统本身负责检查提交的用户名和密码并认证用户。</p>
<p>最后，创建模版：</p><pre class="crayon-plain-tag">{# app/Resources/views/security/login.html.twig #}
{# ... you will probably extends your base template, like base.html.twig #}

{% if error %}
    &lt;div&gt;{{ error.messageKey|trans(error.messageData) }}&lt;/div&gt;
{% endif %}

&lt;form action="{{ path('login_check') }}" method="post"&gt;
    &lt;label for="username"&gt;Username:&lt;/label&gt;
    &lt;input type="text" id="username" name="_username" value="{{ last_username }}" /&gt;

    &lt;label for="password"&gt;Password:&lt;/label&gt;
    &lt;input type="password" id="password" name="_password" /&gt;

    {#
        If you want to control the URL the user
        is redirected to on success (more details below)
        &lt;input type="hidden" name="_target_path" value="/account" /&gt;
    #}

    &lt;button type="submit"&gt;login&lt;/button&gt;
&lt;/form&gt;</pre><p></p>
<blockquote><p>这个传入到模版的错误变量是一个<span style="text-decoration: underline"><em><span style="color: #ff0000"><span class="goog-text-highlight" style="color: #ff0000"><a class="reference external" style="color: #ff0000" title="Symfony的\分量\安全\核心\异常\的AuthenticationException" href="http://api.symfony.com/2.6/Symfony/Component/Security/Core/Exception/AuthenticationException.html">AuthenticationException</a>。</span></span></em></span>它包含很多信息－一些敏感信息－关于认证失败信息，所以你要明智的使用它。</p></blockquote>
<p>要实现这些东西，要注意这几个要求：</p>
<ul>
<li>该表单必须提交到/login_check,因为你在security.yml的form_login健中配置的</li>
<li>这个用户名一定要name为_username并且password一定要name为_password。</li>
</ul>
<blockquote><p>其实所有的这些都可以配置到form_login健下，请查看 <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#reference-security-firewall-form-login" target="_blank">form登录配置</a></span></span></em>。</p></blockquote>
<p>此登录表单目前没有使用CSRF攻击。如果需要请阅读<span style="color: #ff0000"> <a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/security/csrf_in_login_form.html"><em>Using CSRF Protection in the Login Form</em></a> </span>。</p>
<p>就是这样！当您提交表单，security系统会自动检查用户的凭证，验证用户或向用户发送错误信息在登陆表单。</p>
<p>回顾整个过程：</p>
<p>1.用户试图访问被保护的资源。</p>
<p>2.防火期启用认证过程将用户重定向到登录表单（/login）</p>
<p>3.这个例子中，通过路由（route）和控制器（controller）来显示登录表单。</p>
<p>4.用户提交登录表单到 /login_check;</p>
<p>5.security系统截取请求，检查用户提交的凭据，验证他们是否正确，如果他不是系统允许的，页面会重新跳转到表单。</p>
<p>&nbsp;</p>
<h2>成功后，重定向</h2>
<p>如果提交的凭证是正确的，该用户会被重新定向到请求的原始页面（如/admin/foo）。如果用户最初直接进入登录页面，它需要跳转到首页。这些都是可以设定的，并且允许你指定到一个指定的url上。</p>
<p>更多细节，请参阅 <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.html">How to Customize your Form Login</a></span></span></em>。</p>
<p>&nbsp;</p>
<h2>避免常见问错误</h2>
<p>在设置表单时应该注意一些常见的陷阱。</p>
<p><strong>1.创建正确的路由</strong></p>
<p>首先，确保你已经定义了正确的/login和/login_check，他们应该和你配置的login_path和check_path是一样的。如果你配置错误他会重定向到一个404页面，而不是登录页面，或者会出现提交表单不执行任何操作（你会一遍又一遍的看到登录表单）。</p>
<p><strong>2.确保登录页面可以访问</strong></p>
<p>此外，要确保登录页面匿名用户可以访问。例如，下面的配置-ROLE_ADMIN角色用于所有的URL（包括 /login），将会出现重定向循环：</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
access_control:
    - { path: ^/, roles: ROLE_ADMIN }</pre><p>添加access_control中添加一个 /login/*，并且指定一个任何身份都可以进入的角色。</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: ROLE_ADMIN }</pre><p>另外，如果您的防火墙没有允许匿名用户（没有<tt class="docutils literal"><code>anonymous</code></tt> 键），你需要去创建一个特殊的防火墙，允许匿名用户登录：</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
firewalls:
    # order matters! This must be before the ^/ firewall
    login_firewall:
        pattern:   ^/login$
        anonymous: ~
    secured_area:
        pattern:    ^/
        form_login: ~</pre><p><strong> 3.确保 /login_check 位于防火墙的后面</strong></p>
<p>确保你的check_path的url(如 /login_check)在表单登录防火墙里（例如本例，单一的防火墙匹配所有URL，包含/login_check）。如果/login_check不匹配路由，你会收到一个<span style="color: #339966">Unable to find the controller for path &#8220;/login_check&#8221;</span>的异常。</p>
<p>4.多个防火墙不共享相同的Security内容</p>
<p>如果你使用多个防火墙并且你进行认证了一个防火墙，其他的防火墙就不会对此做自动认证了。不同的防火墙，就像不同的安全系统。要做到这一点，你就必须要明确指定不同防火墙下相同的<span style="text-decoration: underline"><span style="color: #ff0000;text-decoration: underline"> <a class="reference internal" style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/configuration/security.html#reference-security-firewall-context"><em>Firewall Context</em></a>  </span></span>。但大多数应用，有一个主要的防火墙就足够了。</p>
<p>5.路由错误不受防火墙限制</p>
<p>Security已经把路由里的404页面设置成了不受防火墙限制。这意味着在这些页面上，你不能检测Security甚至访问用户对象。请查看 <span style="text-decoration: underline"><em><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/controller/error_pages.html" target="_blank">How to Customize Error Pages</a></span></em></span></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/300">(Security)如何去搭建一个传统的登录表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/300/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>(Doctrine)如何处理Doctrine文件上传</title>
		<link>http://www.newlifeclan.com/symfony/archives/199</link>
		<comments>http://www.newlifeclan.com/symfony/archives/199#comments</comments>
		<pubDate>Thu, 04 Dec 2014 06:36:11 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[symfony2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=199</guid>
		<description><![CDATA[<p>以Doctrine形式上传文件和一些其他方式上传图片是没有什么不同的。换句话说，你可以在表单提交后，自由的处理 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/199">(Doctrine)如何处理Doctrine文件上传</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>以Doctrine形式上传文件和一些其他方式上传图片是没有什么不同的。换句话说，你可以在表单提交后，自由的处理文件。对于如何做到这一点，请参阅 <a href="http://symfony.com/doc/current/reference/forms/types/file.html" target="_blank">http://symfony.com/doc/current/reference/forms/types/file.html</a></p>
<p><span id="more-199"></span></p>
<p>你可以将上传文件的处理集成到Entity的生命周期中（如创建，修改和删除时调用）。</p>
<p>在这种情况下的好处，在Doctrine中创建、修改和删除数据时，上传文件和删除文件也会自动完成（必须在控制器中做任何操作）</p>
<h2>基本设置</h2>
<p>创建一个简单的Doctrine实体类：</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 */
class Document
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     */
    public $name;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    public $path;

    public function getAbsolutePath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadRootDir().'/'.$this-&gt;path;
    }

    public function getWebPath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadDir().'/'.$this-&gt;path;
    }

    protected function getUploadRootDir()
    {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__.'/../../../../web/'.$this-&gt;getUploadDir();
    }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'uploads/documents';
    }
}</pre><p>该document实体有一个名称与文件相关联。这个path属性存储一个文件的相对路径并且在数据库中存储。这个getAbsolutePath()会返回一个绝对路径，getWebPath()会返回一个web路径，用于模板加入上传文件链接。</p>
<blockquote><p>如果你还没有这样做的话，你应该阅读http://symfony.com/doc/current/reference/forms/types/file.html首先了解基本的上传过程。</p>
<p>如果您使用注释来验证规则（如本例所示），请确保你启用了注释验证（见http://symfony.com/doc/current/book/validation.html#book-validation-configuration）。</p>
<p>&nbsp;</p></blockquote>
<p>在处理一个实际的文件上传时，使用一个“虚拟”的file字段。例如，如果你在controller中直接构建一个form，他可能是这样的：</p><pre class="crayon-plain-tag">public function uploadAction()
{
    // ...

    $form = $this-&gt;createFormBuilder($document)
        -&gt;add('name')
        -&gt;add('file')
        -&gt;getForm();

    // ...
}</pre><p>下一步，创建file这个属性到你的Document类中并且添加一些验证规则：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

// ...
class Document
{
    /**
     * @Assert\File(maxSize="6000000")
     */
    private $file;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile()
    {
        return $this-&gt;file;
    }
}</pre><p>annotations</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Document
{
    /**
     * @Assert\File(maxSize="6000000")
     */
    private $file;

    // ...
}</pre><p></p>
<blockquote><p> 当你使用File约束，symfony会自动猜测表单字段输入的是一个文件上传。这就是当你创建表单（-&gt;add(&#8216;file&#8217;)）时，为什么没有在表单明确设置为文件上传的原因。</p></blockquote>
<p>下面的控制器，告诉您如何处理全部过程：</p><pre class="crayon-plain-tag">// ...
use Acme\DemoBundle\Entity\Document;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
// ...

/**
 * @Template()
 */
public function uploadAction(Request $request)
{
    $document = new Document();
    $form = $this-&gt;createFormBuilder($document)
        -&gt;add('name')
        -&gt;add('file')
        -&gt;getForm();

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        $em = $this-&gt;getDoctrine()-&gt;getManager();

        $em-&gt;persist($document);
        $em-&gt;flush();

        return $this-&gt;redirect($this-&gt;generateUrl(...));
    }

    return array('form' =&gt; $form-&gt;createView());
}</pre><p>以前的controller当提交name自动的存储Document实体，但是他不会做任何关于文件的事情并且path属性也将是空白。</p>
<p>处理文件上传一个简单的方法就是在entity持久化之前设置相应的path属性。在某一时刻处理文件上传时，要调用Document实体类一个upload()方法给path赋值。</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $document-&gt;upload();

    $em-&gt;persist($document);
    $em-&gt;flush();

    return $this-&gt;redirect(...);
}</pre><p>这个upload()方法利用UploadedFile对象，是它提交后返回file字段：</p><pre class="crayon-plain-tag">public function upload()
{
    // the file property can be empty if the field is not required
    // 该file属性为空这个属性就不需要了
    if (null === $this-&gt;getFile()) {
        return;
    }

    // use the original file name here but you should
    // sanitize it at least to avoid any security issues
    //  这里你应该使用原文件名但是应该至少审核它避免一些安全问题
    // move takes the target directory and then the
    // target filename to move to
    // 将目标文件移动到目标目录
    $this-&gt;getFile()-&gt;move(
        $this-&gt;getUploadRootDir(),
        $this-&gt;getFile()-&gt;getClientOriginalName()
    );

    // set the path property to the filename where you've saved the file
    // 设置path属性为你保存文件的文件名
    $this-&gt;path = $this-&gt;getFile()-&gt;getClientOriginalName();

    // clean up the file property as you won't need it anymore
    // 清理你不需要的file属性
    $this-&gt;file = null;
}</pre><p></p>
<h2> 使用生命周期回调</h2>
<blockquote><p>生命周期回调是一种有限的技术，他有一些缺点。如果你想移除Document::getUploadRootDir()方法里的写死的编码__DIR__，最好的方法是开始使用Doctrine listeners。在哪里你将能够注入内核参数，如kernel.root_dir来建立绝对路径。</p></blockquote>
<p>这种原理工作，他有一个缺陷：也就是说当entity持久化时会有什么问题呢？答：该文件已经转移到了它的最终位置，实体类下的path属性不能够正确的实体化。</p>
<p>（如果entity有持久化问题或者文件不能够移动，什么事情也没有发生）为了避免这些问题，你应该改变这种实现方式以便数据库操作和自动删除文件：</p><pre class="crayon-plain-tag">/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
}</pre><p>接下来,利用这些回调函数重构<tt class="docutils literal"><code>Document</code></tt>类:</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
    private $temp;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
        // check if we have an old image path
        // 检查如果我们有一个旧的图片路径
        if (isset($this-&gt;path)) {
            // store the old name to delete after the update
            $this-&gt;temp = $this-&gt;path;
            $this-&gt;path = null;
        } else {
            $this-&gt;path = 'initial';
        }
    }

    /**
     * @ORM\PrePersist()
     * @ORM\PreUpdate()
     */
    public function preUpload()
    {
        if (null !== $this-&gt;getFile()) {
            // do whatever you want to generate a unique name
            // 去生成一个唯一的名称
            $filename = sha1(uniqid(mt_rand(), true));
            $this-&gt;path = $filename.'.'.$this-&gt;getFile()-&gt;guessExtension();
        }
    }

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this-&gt;getFile()) {
            return;
        }

        // if there is an error when moving the file, an exception will
        // be automatically thrown by move(). This will properly prevent
        // the entity from being persisted to the database on error
        //当移动文件发生错误，一个异常move（）会自动抛出异常。
        //这将阻止实体持久化数据库发生错误。
        $this-&gt;getFile()-&gt;move($this-&gt;getUploadRootDir(), $this-&gt;path);

        // check if we have an old image
        if (isset($this-&gt;temp)) {
            // delete the old image
            unlink($this-&gt;getUploadRootDir().'/'.$this-&gt;temp);
            // clear the temp image path
            $this-&gt;temp = null;
        }
        $this-&gt;file = null;
    }

    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        $file = $this-&gt;getAbsolutePath();
        if ($file) {
            unlink($file);
        }
    }
}</pre><p></p>
<blockquote><p> 如果更改你的entity是由Doctrine event listener 或event subscriber处理，这个 <tt class="docutils literal"><code>preUpdate()回调函数必须通知Doctrine关于正在做的改变。有关preUpdate事件限制的完整参考请查看 http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate</code></tt></p></blockquote>
<p>现在这个类做了你需要的一切：他会在entity持久化之前生成一个唯一的文件名，持久化之后，移动文件，删除文件。</p>
<p>现在移动文件是entity自动完成，这个$document-&gt;upload()就应该从controller中移除了：</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $em-&gt;persist($document);
    $em-&gt;flush();

    return $this-&gt;redirect(...);
}</pre><p></p>
<blockquote><p> 这个@ORM\PrePersist()和@ORM\PostPersist()事件回调:一个是在entity持久化到数据库之前触发,一个是在entity持久化到数据库之后触发。另一方面， <tt class="docutils literal"><code>@ORM\PreUpdate()</code></tt> 和 <tt class="docutils literal"><code>@ORM\PostUpdate()事件回调时当实体更新时触发。</code></tt></p>
<p>当改变entity字段后进行持久化操作时，<tt class="docutils literal"><code>PreUpdate</code></tt>和<tt class="docutils literal"><code>PostUpdate回调才会被触发。这意味着，默认情况下，你只改变了$file属性，这些事件不会被触发，因为这个属性它自己不会持久化到Doctrine。有一个解决方法，就是创建一个updated字段把它持久化到Doctrine，并当文件改变时手动调整它。</code></tt></p></blockquote>
<h2></h2>
<h2>使用ID作为文件名</h2>
<p>如果要使用ID作为文件名，实现略有不同，您需要保存path属性为文件扩展名,而不是实际的文件名:</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
    private $temp;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
        // check if we have an old image path
        if (is_file($this-&gt;getAbsolutePath())) {
            // store the old name to delete after the update
            $this-&gt;temp = $this-&gt;getAbsolutePath();
        } else {
            $this-&gt;path = 'initial';
        }
    }

    /**
     * @ORM\PrePersist()
     * @ORM\PreUpdate()
     */
    public function preUpload()
    {
        if (null !== $this-&gt;getFile()) {
            $this-&gt;path = $this-&gt;getFile()-&gt;guessExtension();
        }
    }

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this-&gt;getFile()) {
            return;
        }

        // check if we have an old image
        if (isset($this-&gt;temp)) {
            // delete the old image
            unlink($this-&gt;temp);
            // clear the temp image path
            $this-&gt;temp = null;
        }

        // you must throw an exception here if the file cannot be moved
        // so that the entity is not persisted to the database
        // which the UploadedFile move() method does
        $this-&gt;getFile()-&gt;move(
            $this-&gt;getUploadRootDir(),
            $this-&gt;id.'.'.$this-&gt;getFile()-&gt;guessExtension()
        );

        $this-&gt;setFile(null);
    }

    /**
     * @ORM\PreRemove()
     */
    public function storeFilenameForRemove()
    {
        $this-&gt;temp = $this-&gt;getAbsolutePath();
    }

    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        if (isset($this-&gt;temp)) {
            unlink($this-&gt;temp);
        }
    }

    public function getAbsolutePath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadRootDir().'/'.$this-&gt;id.'.'.$this-&gt;path;
    }
}</pre><p>你会注意到，在这种情况下,你需要做一点工作,以删除该文件。在数据删除之前，你必须保存文件路径（因为它依赖于ID）。然后，一旦对象已经完全从数据库中删除，你就可以安全的删除文件（在数据删除之后）。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/199">(Doctrine)如何处理Doctrine文件上传</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/199/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(控制器)如何自定义错误页面</title>
		<link>http://www.newlifeclan.com/symfony/archives/87</link>
		<comments>http://www.newlifeclan.com/symfony/archives/87#comments</comments>
		<pubDate>Sat, 11 Oct 2014 06:15:06 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[控制器]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=87</guid>
		<description><![CDATA[<p>当symfony出现异常时，都会被Kernel类所捕获并最终转发到一个特殊的控制器TwigBundle:Exc [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/87">(控制器)如何自定义错误页面</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>当symfony出现异常时，都会被Kernel类所捕获并最终转发到一个特殊的控制器TwigBundle:Exception:show来处理。这个控制器在核心的TwigBundle里，他来确定显示哪些错误模板，并且给相关异常设定状态码。<br />
<span id="more-87"></span><br />
错误页面可以定义为两种不同的形式，这取决与你要更改的灵活度：<br />
1.自定义不同错误页面的模板<br />
2.替换默认的异常控制器 twig.controller.exception:showAction</p>
<h2>默认ExceptionController</h2>
<p>默认ExceptionController是否显示一个异常或错误页面，取决于是否这是kernel.debug。虽然异常页面会给我们排查问题提供很多有用信息，但是最终给用户显示的应该是错误页面，而不是异常页面。</p>
<blockquote><p>在开发过程中测试错误页面<br />
在开发过程中你要看到你的错误页面，你就不能设置kernel.debug为false。这是因为它会阻止symfony重新编译twig模板等，看不到异常页面。<br />
这个第三方WebfactoryExceptionsBundle提供了一个特殊的测试控制器，它能够在kernel.debug设置为true的情况下，允许你显示任意的http状态的自定义错误页面。</p></blockquote>
<h2>重写错误模板</h2>
<p>所有的错误模板都写在TwigBundle下。去重写这个模板，只需要遵守这个bundle的基本方法就可以了。更多方法请看 <span style="color: #ff0000"><a style="color: #ff0000" href="http://symfony.com/doc/current/book/templating.html#overriding-bundle-templates">重写bundle模板</a></span>。<br />
举例,我们现在就去重写一个默认的错误模板，那么需要在此路径下新建一个模板<br />
app/Resources/TwigBundle/views/Exception/error.html.twig</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;An Error Occurred: {{ status_text }}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Oops! An Error Occurred&lt;/h1&gt;
    &lt;h2&gt;The server returned a "{{ status_code }} {{ status_text }}".&lt;/h2&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><p></p>
<blockquote><p>你一定不能在你的错误页面使用is_granted（或者layout使用你的错误页面），因为路由运行在防火墙之前。如果路由器抛出异常（例如，当路由不匹配时）你使用is_granted将引发有一个异常。或者你能够通过这种方式{% if app.user and is_granted(&#8216;&#8230;&#8217;) %}来使用is_granted。</p></blockquote>
<blockquote><p>如果你不熟悉Twig，不用担心。Twig是symfony中非常简单，强大和可选择的模板引擎。更多信息请查看  book文档（<a href="http://symfony.com/doc/current/book/templating.html" target="_blank">创建和使用模板</a>）。</p></blockquote>
<p>除了标准的html页面，symfony还提供很多其他格式的错误页面，例如JSON（error.json.twig）,XML（error.xml.twig）甚至还有JavaScript(error.js.twig)。只需要在这个app/Resources/TwigBundle/views/Exception目录下创建同名文件就可以重写这些模板了。上面是用标准的方法重写这些在bundle里的模板。</p>
<p>&nbsp;</p>
<h2>自定义404页面和其他错误页面</h2>
<p>你可以根据http的状态定制特定的模板。例如创建一个app/Resources/TwigBundle/views/Exception/error404.html.twig模板去显示一个不存在的404错误页。</p>
<p>symfony使用下面的算法去决定使用哪个模板：</p>
<ul>
<li>首次，判断给定的格式和状态码，并寻找一个模板（像：error404.json.twig）;</li>
<li>如果error404.json.twig不存在，他会去寻找给定的格式模板（例如：error.json.twig）</li>
<li>如果error.json.twig也不存在，他会返回html模板（例如：error.html.twig）</li>
</ul>
<blockquote><p>要是去看所有的额默认错误模板，在TwigBundle的Resources/views/Exception目录下。在symfony标准版里就可以找到TwigBundle在vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle。有一个简单的方法，我们可以从TwigBundle里拷贝页面到app/Resources/TwigBundle/views/Exception目录下并修改它，让他成为一个自定义的错误页。</p></blockquote>
<blockquote><p>同样开发人员可以自定义这些友好的异常页面，例如可以创建模板exception.html.twig是为了标准的HTML页面异常显示和exception.json.twig是JOSN异常页面显示。</p></blockquote>
<p>&nbsp;</p>
<h2>替换默认的异常控制器</h2>
<p>如果你需要更多的灵活性，那只修改模板文件是不够的（例如你可能需要传入一些自己的变量到你的模板），那么你可以重写你的控制器去渲染自己的模板。</p>
<p>默认的异常控制器是注册为一个服务的</p>
<p>—— 实际的类是Symfony\Bundle\TwigBundle\Controller\ExceptionController</p>
<p>为此，创建一个新的控制器并且让他继承默认的symfony类</p>
<p>Symfony\Bundle\TwigBundle\Controller\ExceptionController</p>
<p>你可以有几种方法，把自定义的不同部分在错误页面里呈现，例如你可以覆盖整个showAction或者只是findTemplate方法让自己的模板渲染。</p>
<p>为了让symfony能够使用自己的异常控制器而不是默认的，设置app/config/config.yml的twig.exception_controller选项。</p>
<blockquote><p>自定义的异常处理实际上比上面写的更强大。一个内部事件Kernel.exception是能够完全控制所有抛出的异常。详细内容可查看，<span style="color: #ff0000"><a style="color: #ff0000" title=" kernel.exception Event" href="http://symfony.com/doc/current/book/internals.html#kernel-kernel-exception" target="_blank">kernel.exception Event</a></span></p></blockquote>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/87">(控制器)如何自定义错误页面</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/87/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(Assetic)在twig里怎么使用Assetic去优化图片</title>
		<link>http://www.newlifeclan.com/symfony/archives/79</link>
		<comments>http://www.newlifeclan.com/symfony/archives/79#comments</comments>
		<pubDate>Sat, 11 Oct 2014 02:56:33 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Assetic]]></category>
		<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=79</guid>
		<description><![CDATA[<p>他有很多的过滤器，其中Assetic有四个过滤器能够进行图片的优化。可以让你不使用图片编辑器处理每个图片，就能 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/79">(Assetic)在twig里怎么使用Assetic去优化图片</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>他有很多的过滤器，其中Assetic有四个过滤器能够进行图片的优化。可以让你不使用图片编辑器处理每个图片，就能获得更小尺寸的图片。结果会被缓存，而且没有批量生产所以没有性能的损耗，很好的呈现给用户。<br />
<span id="more-79"></span><br />
&nbsp;</p>
<h2>使用Jpegoptim</h2>
<p><span style="color: #ff0000"><a style="color: #ff0000" href="http://www.kokkonen.net/tjko/projects.html" target="_blank">Jpegoptim</a></span>是一个很实用的JPEG文件优化程序。要与Assetic一起使用，并在Assetic添加配置：</p><pre class="crayon-plain-tag"># app/config/config.yml
assetic:
    filters:
        jpegoptim:
            bin: path/to/jpegoptim</pre><p></p>
<blockquote><p>注意你要使用jpegoptim,你一定要把它安装到系统上。这个bin选项要指定到jpegoptim编译的二进制文件位置。</p></blockquote>
<p>现在你就能从一个模板使用他了：</p><pre class="crayon-plain-tag">{% image '@AcmeFooBundle/Resources/public/images/example.jpg'
    filter='jpegoptim' output='/images/example.jpg' %}
    &lt;img src="{{ asset_url }}" alt="Example"/&gt;
{% endimage %}</pre><p>&nbsp;</p>
<h2>移除所有EXIF数据</h2>
<p>默认情况下，运行它只删除了文件的一些meta信息存储。一些EXIF信息和注释都没有移除，如果你想移除这些，可以添加 <tt class="docutils literal"><code>strip_all 选项：</code></tt></p><pre class="crayon-plain-tag"># app/config/config.yml
assetic:
    filters:
        jpegoptim:
            bin: path/to/jpegoptim
            strip_all: true</pre><p>&nbsp;</p>
<h2>降低图片质量</h2>
<p>默认情况下，JPEG的质量水平是不受约束的。你要是想降低文件的大小你可以添加max选项。当然这会以牺牲图片质量为代价：</p><pre class="crayon-plain-tag"># app/config/config.yml
assetic:
    filters:
        jpegoptim:
            bin: path/to/jpegoptim
            max: 70</pre><p>&nbsp;</p>
<h2>短语法：Twig Function</h2>
<p>如果你使用Twig。你可以启用和使用Twig function的特殊的短语法。开始把它添加到配置文件吧：</p><pre class="crayon-plain-tag"># app/config/config.yml
assetic:
    filters:
        jpegoptim:
            bin: path/to/jpegoptim
    twig:
        functions:
            jpegoptim: ~</pre><p>Twig模板可以更改为一下内容：</p><pre class="crayon-plain-tag">&lt;img src="{{ jpegoptim('@AcmeFooBundle/Resources/public/images/example.jpg') }}" alt="Example"/&gt;</pre><p>你还能指定输出的目录，添加 output 选项：</p><pre class="crayon-plain-tag"># app/config/config.yml
assetic:
    filters:
        jpegoptim:
            bin: path/to/jpegoptim
    twig:
        functions:
            jpegoptim: { output: images/*.jpg }</pre><p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/79">(Assetic)在twig里怎么使用Assetic去优化图片</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/79/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(控制器)怎样去定义一个控制器为服务</title>
		<link>http://www.newlifeclan.com/symfony/archives/66</link>
		<comments>http://www.newlifeclan.com/symfony/archives/66#comments</comments>
		<pubDate>Thu, 09 Oct 2014 07:47:35 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[控制器]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=66</guid>
		<description><![CDATA[<p>在book文档里，你已经学习了如何让你的一个Controller去继承基类Controller。这个工作方式很 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/66">(控制器)怎样去定义一个控制器为服务</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在book文档里，你已经学习了如何让你的一个Controller去继承基类Controller。这个工作方式很好，但是还有别的方式例如把Controller指定为一个服务。<br />
<span id="more-66"></span></p>
<blockquote><p>指定一个控制器作为服务需要做一点工作。这样做的主要优势是全部的Controller和任何服务都能通过Controller使用，只需要修改服务容器配置。开发一个开源的软件包或给不同的项目使用时，他非常方便。<br />
第二个优势，通过观察构造函数参数,很容易看出他使用什么类型的东西，也知道这个控制器能做或不能做的事情。而且很明显（也就是说，如果你有很多的构造参数时）由于每个依赖都需要手动注入，控制器已经变得过于庞大，你就可以分割成多个控制器。<br />
所以，即使你没有指定控制器为一个服务，你也会从一些开源的symfony的程序中看到。很重要的是你要了解这两种方式的优劣性。</p></blockquote>
<h2>定义这个控制器为一个服务</h2>
<p>一个控制器可以和其他类一样被定义成一个服务。例如，你有以下简单的控制器：</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><pre class="crayon-plain-tag"># src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    # ...
    acme.controller.hello.class: Acme\HelloBundle\Controller\HelloController

services:
    acme.hello.controller:
        class: "%acme.controller.hello.class%"</pre><p>&nbsp;</p>
<h2>涉及到该服务</h2>
<p>去使用一个定义为服务的控制器时，要采用冒号（:）。例如，如何转发到上面定义的 acme.hello.controller的indexAction()方法:</p><pre class="crayon-plain-tag">$this-&gt;forward('acme.hello.controller:indexAction', array('name' =&gt; $name));</pre><p></p>
<blockquote><p>注：当你使用这个语法时，不能够舍弃indexAction方法名的Action部分。</p></blockquote>
<p>当定义路由_controller的值时，你可以用路由指定服务同样也使用冒号（:）等相同符号：</p><pre class="crayon-plain-tag"># app/config/routing.yml
hello:
    path:     /hello
    defaults: { _controller: acme.hello.controller:indexAction }</pre><p></p>
<blockquote><p>你还可以采用注释的方式配置路由，来使用一个定义为服务的控制器。请查看<a href="http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html" target="_blank"> FrameworkExtraBundle documentation</a>。</p></blockquote>
<p>&nbsp;</p>
<h2>替代继承的基础Controller方法</h2>
<p>当使用的控制器作为服务时，他可能不会用Controller类。也不依赖于它的快捷方式方法，你会直接与你所需要的服务进行交互。幸运的是，通常我们会很容易的和基础的Controller（一个伟大的代码）来执行许多常见任务。<br />
举个例子，如果你想呈现一个模板，而不是直接创建Response对象，如果你继承了基础的Controller，那么你原来的代码应该这样写：</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)
    {
        return $this-&gt;render(
            'AcmeHelloBundle:Hello:index.html.twig',
            array('name' =&gt; $name)
        );
    }
}</pre><p>如果你看一下基础Controller在symfony中render函数的源代码，你会发现这个方法实际使用的是templating服务。</p><pre class="crayon-plain-tag">public function render($view, array $parameters = array(), Response $response = null)
{
    return $this-&gt;container-&gt;get('templating')-&gt;renderResponse($view, $parameters, $response);
}</pre><p>在一个作为服务的控制器中，您可以直接注入模板服务并使用它：</p><pre class="crayon-plain-tag">// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    private $templating;

    public function __construct(EngineInterface $templating)
    {
        $this-&gt;templating = $templating;
    }

    public function indexAction($name)
    {
        return $this-&gt;templating-&gt;renderResponse(
            'AcmeHelloBundle:Hello:index.html.twig',
            array('name' =&gt; $name)
        );
    }
}</pre><p>服务定义也需要修改指定构造函数参数：</p><pre class="crayon-plain-tag"># src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    # ...
    acme.controller.hello.class: Acme\HelloBundle\Controller\HelloController

services:
    acme.hello.controller:
        class:     "%acme.controller.hello.class%"
        arguments: ["@templating"]</pre><p>不是从容器获取<tt class="docutils literal"><code>templating服务，而是你能够精确的注入需要的服务到你的Controller里。</code></tt></p>
<p>&nbsp;</p>
<blockquote><p>这并不意味着你不能从自己的基础控制器扩展这些控制器。之所以从标准的基本控制器中剥离，是因为控制器的一些方法，使用前提是“有控制器可以使用”但如果控制器“被定义成服务”，此时控制器就不可用。把它从基本的控制器中剥离可能是一个好主意，提取通用的代码到一个服务里,代码再注入到一个基本控制器扩展。这两种方法都是有效的,主要看你想要如何整理你的可重用代码。</p></blockquote>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/66">(控制器)怎样去定义一个控制器为服务</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/66/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>(模板)没有自定义控制器如何渲染一个模板</title>
		<link>http://www.newlifeclan.com/symfony/archives/54</link>
		<comments>http://www.newlifeclan.com/symfony/archives/54#comments</comments>
		<pubDate>Wed, 08 Oct 2014 06:51:15 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[模板]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=54</guid>
		<description><![CDATA[<p>通常，当你需要去创建一个页面，你需要去创建一个控制器和控制器需要渲染的一个模板文件。但是如果你只需要一个简单的 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/54">(模板)没有自定义控制器如何渲染一个模板</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>通常，当你需要去创建一个页面，你需要去创建一个控制器和控制器需要渲染的一个模板文件。但是如果你只需要一个简单的模板不需要数据传递给他，你可以不用创建一个控制器，而是使用内部的FrameworkBundle:Template:template控制器就可以了。<br />
<span id="more-54"></span><br />
例如，假如你想把AcmeBundle:Static:privacy.html.twig模板输出，不用传递任何变量。你就不用创建控制器了，你需要这么写：</p><pre class="crayon-plain-tag">acme_privacy:
    path: /privacy
    defaults:
        _controller: FrameworkBundle:Template:template
        template:    'AcmeBundle:Static:privacy.html.twig'</pre><p>上面代码中FrameworkBundle:Template:template 控制器，可以将任意template的模板渲染。</p>
<p>当然也可以使用这个技巧就是从模板内部嵌入控制器。但由于从模板中呈现一个控制器的目的通常是在控制器准备一些典型数据，我们还可以缓存这些嵌入页面的部分内容（查看 缓存静态模板）</p><pre class="crayon-plain-tag">{{ render(url('acme_privacy')) }}</pre><p>&nbsp;</p>
<h2>缓存静态模板</h2>
<p>由于模板用这种方式呈现都是静态的，只有静态的才能够缓存。幸运的是，他非常的简单！在你的路由中通过配置一些其他的变量，你就能控制怎样正确的缓存你的页面：</p><pre class="crayon-plain-tag">acme_privacy:
    path: /privacy
    defaults:
        _controller:  FrameworkBundle:Template:template
        template:     'AcmeBundle:Static:privacy.html.twig'
        maxAge:       86400
        sharedAge:    86400</pre><p>这个maxAge和sharedAge用于修改在控制器中创建的Response对象。关于更多缓存细节请查看 <span style="color: #ff0000"> http Cache</span>。</p>
<p>还有一个private变量（他没有显示在这里）。默认情况下，只要有maxAge和sharedAge传递，这个Response将是共有的。如果设置为true，这个Response就是私有的。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/54">(模板)没有自定义控制器如何渲染一个模板</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/54/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
