正如你所知,控制器处理响应传入Symfony2应用程序的请求。实际上,控制器将大部分重负载的工作带到其他地方以便代码可以被测试和重试。当控制器需要生在HTML、CSS或其它内容时,它将工作交给模板引擎。这本章,你将学习如何编写一个强大的模板,使之可以用于将内容返回给用户、电子邮件内容等。你将学习继承模板的快捷、智能的方式,以及如何重用模板代码。
模板是简单的文本文件,可以生成任何基于文本格式(HTML、XML、CSV、LaTex...)。最密切的模板类型是PHP模板,这种模板是被PHP解析并包含文本和PHP代码混和体的文本文件:
<!DOCTYPE html> <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1><?php echo $page_title ?></h1> <ul id="navigation"> <?php foreach ($navigation as $item): ?> <li> <a href="<?php echo $item->getHref() ?>"> <?php echo $item->getCaption() ?> </a> </li> <?php endforeach; ?> </ul> </body> </html>
但Symfony2包中更为强大的模板语言是Twig,Twig允许你去编写简洁、易读的模板,它对web设计者更为友好,在某些方面,甚至比PHP模板更为强大。
<!DOCTYPE html> <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html>
Twig包含两种指定的语法定义:
{{ ... }}:“说什么”:打印一个变量或表达式的结果到模板;
{% ... %}:“做什么”:一个控制模板逻辑的标签。常用于执行储如for-loops之类的语句。
这是第三种语法,用于创建注释:{# 这是一个注解 #}。这个语法可用于跨行,就象PHP中的/* 注解 */语法一样。
Twig也包含过滤器,可以在渲染之前修改内容。下面的示例可以在渲染title变量之前把它变成大写:
{{ title | upper }}
缺省状况下,Twig有着大量的标签和过滤器。必要时,你甚至可以添加你自己的扩展到Twig。
正如你将看到的那样,Twig也可以轻易地添加支持功能和新功能,下面的例子使用一个标准的if标签和cycle函数去打印十个交替使用odd和even类的div标签:
{% for i in 0..10 %} <div class="{{ cycle(['odd', 'even'], i) }}"> <!-- 这里是一些 HTML --> </div> {% endfor %}
为什么使用Twig?
Twig模板注定是简单的,也不会处理PHP标签。Twig模板系统被设计用来表达表现而不是程序逻辑。你用Twig越多,你就越发会欣赏这样设计,并从中获益。当然你也就更加喜爱web设计。
Twig也可以做PHP不能做的事,如真正的模板继承(Twig模板编译成PHP类,以便从其它模板继承)、空白符控制、沙箱和包括只影响模板的自定义函数和过滤器。Twig包含的小功能使得编写模板更为简洁和方便。下面这个例子是结合了逻辑if语句的循环:
<ul> {% for user in users %} <li>{{ user.username }}</li> {% else %} <li>No users found</li> {% endfor %} </ul>
Twig是快的。每个Twig模板被编译成PHP本地类,在运行时被渲染。被编译的类放置在app/cache/{environment}/twig目录中({environment}是环境,如dev或prod),有时在调试时有用。更多关于环境的信息参见环境章节。
当debug模式打开时(通常是在dev环境),Twig模板在改变后会被自动编译。这意味着在开发期间你可以愉快地对Twig模板进行修改,你可以立即看到改变而无须担心要去清除缓冲。
然而当debug模式关闭时(通常是prod环境),你必须要清除Twig缓冲目录以便重新生成Twig模板。当你部署你的应用程序时要记住进行这项操作。
通常,项目中的模板总是共享一些通用元素,如页头、页脚、边栏等等。在Symfony2中,我们对这个问题有着不同的观点:模板可以被另一个装饰。这种方式与PHP类相似:模板继承允许你构建一个基本“布局”模板,包括你站点中所有的通用元素被定义成区块(想像一下“带有基本方法的PHP类”)。一个子模板可以继承基本布局,并覆写任意的区块(想像一下“PHP子类覆写它父类的方法)。
首先,构建一个基本布局文件:
{# app/Resources/views/base.html.twig #} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Test Application{% endblock %}</title> </head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block body %}{% endblock %} </div> </body> </html>
虽然讨论模板继承是Twig的术语,但Twig和PHP模板之间的理念是相同的。
这个模板定义了HTML两列页的基本框架。在本例中有三个{% block %}被定义(title、sidebar和body)。每个区块也可以被直接渲染。在本例中title、sidebar和bod区块都简单地保留了用于这个模板中的缺省值。
子模板如下所示:
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends '::base.html.twig' %} {% block title %}My cool blog posts{% endblock %} {% block body %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}
父模板通过一个特殊的字符串语法(::base.html.twig)标识,这表示该模板放置在项目的app/Resources/view目录中。这个命名约定在模板命令和定位中被充分地解释了。
模板继承的关键词是{% extends %}标签。它告诉模板引擎首先评估基本模板,该模板设置了布局并定议了几个区块。将子模板中的title和body区块替换掉父模板中的相应区块,然后子模板被渲染。依赖blog_entries中的值,输出如下所示:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>My cool blog posts</title> </head> <body> <div id="sidebar"> <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> </div> <div id="content"> <h2>My first post</h2> <p>The body of the first post.</p> <h2>Another post</h2> <p>The body of the second post.</p> </div> </body> </html>
注意,因为子模板没有定义sidebar区块,它的值将被父模块取代。在父模板中{% block %}标签中的内容经常被缺省使用。
只要你想,你可以使用多重继承。在下一节中,一个常见的3层继承模型将说明在Symfony2项目中模板是如何被组织的。
当模板继承工作时,请牢记以下技巧:
1、如果你在模板中使用{% extends %},它必须是该模板中的第一个标签。
2、当你在基本模板中有着多个{% block %}标签时,最好记住,子模板并不需要定义所有父区块。因此在你的基本模板中创建尽可能多的区块,并给它们一个合理的缺省值。在你基本模板中的区块越多,你的布局就越发的灵活。
3、如果你发现你在许多模板中有重复的内容,这也许意味着你应该将该内容移到父模板的{% block %}中。在某些情况下,更好地解决方案也许是将该内容移到一个新的模板,并包含它(参见包含其它模板)。
4、如果你需要从父模板得到一个区块的内容,你可以使用{{ parent() }}函数。这非常有用,特别当你想添加一个父区块内容而不想完全覆写它时:
{% block sidebar %} <h3>Table of Contents</h3> ... {{ parent() }} {% endblock %}
缺省状态下,模板可以在两个不同的地方:
1、app/Resources/views/ 应用程序的views目录可以包含应用程序范围的基本模板(如你应用程序的布局),并且该模板会被Bundle模板覆写(参见覆写Bundle模板)
2、path/to/bundle/Resources/views/ 每个Bundle都将它的模板放置在它自身的Resources/views目录中(或子目录)。绝大多数模板都位于Bundle中。
Symfony2通过 bundle:controller:template 这样的字符串来使用模板。下面是允许使用的不同类型的模板,它们每一个都有着不同的定位方式:
1、AcmeBlogBundle:Blog:index.html.twig:这个语法用于特定页面的特定模板。该字符串被冒号(:)分隔成三个部分,含义如下:
* AcmeBlogBundle(bundle):模板位于AcmeBlogBundle目录之中(如src/Acme/BlogBundle)
* Blog(controller):指定模板位于Resources/views目录中的Blog子目录下
* index.html.twig(template):文件名叫index.html.twig
假定AcmeBlogBundle位于src/Acme/BlogBundle目录,那么布局的最终路径应该是src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
2、AcmeBlogBundle::layout.html.twig:这个语法指定了一个AcmeBlogBundle的基本模板。因为中间部分“controller”被忽略了(如Blog),模板路径位于AcmeBlogBundle中的Resources/views/layout.html.twig。
3、::base.html.twig:这个语法指定了一个应用程序范围的模板或布局。注意该字符串由两个冒号(::)开始,意思是bundle和controller部分被忽略。这意味着该模板没有位于任何Bundle之中,因此它应该位于app/Resources/views/目录中。
在覆写Bundle模板章节中,你将学到在AcmeBlogBundle中的每个模板都可以被app/Resources/AcmeBlog/view/目录的相同模板名的模板覆盖。这就提供了从任何提供商的Bundle中覆写模板的强大功能。
希望模板命名语法看起来亲切?它使用的是与控制器命名模式相同的命名约定。
bundle:controller:template 格式指出每个模板的模板文件位于什么地方。每个模板名也有两个扩展名来说明模板的格式和引擎:
1、AcmeBlogBundle:Blog:index.html.twig - HTML格式、Twig引擎
2、AcmeBlogBundle:Blog:index.html.php - HTML格式、PHP引擎
3、AcmeBlogBundle:Blog:index.css.twig - CSS格式、Twig引擎
缺省状态下,任何Symfony2模板可以用Twig或PHP来写,最后一个扩展名(如.twig或.php)指定将使用这两种引擎中的哪一个。第一个扩展名(如.html、.css等)是模板最终生成的格式。不象引擎决定Symfony2如何解析模板,这个只是一个组织策略,因为相同的资源需要被渲染成HTML(index.html.twig)、XML(index.xml.twig)或其它任意格式。更多信息请参阅模板格式一节。
“引擎”是否能用是可以配置的,甚至也可以添加新的引擎,更多细节请参见模板配置。
你已经理解了模板基础知识,它们是怎么命名以及如何使用模板继承。最困难的部分已经过去了。在本节,你将学到许多工具去帮助执行大多数通用模板任务,如包含其它模板、链接页面和包含图片。
Symfony2绑定了几个专用的Twig标签和函数,以减轻模板设计者的工作。在PHP中,模板系统提供了一个扩展的帮手系统来为模板上下文提供有用的功能。
我们已经看到了一些内建Twig的标签({% block %} & {% extends %}),正如PHP的帮手函数($view['slots'])一样,让我们多学一些。
你将经常会在几个不同的页面中包含相同的模板和代码段。举个例子,在有着“新文章”的应用程序中,模板代码显示一篇文章也许被用于文章细节页、显示热点文章页或最新文章列表中。
当你需要重用PHP代码块时,典型做法是将代码移到一个新PHP类或函数中。这同样适用于模板。将重用的模板代码移入自身模板,并被其它模板包含。首先,创建你要重用的模板。
{# src/Acme/ArticleBundle/Resources/Article/articleDetails.html.twig #} <h1>{{ article.title }}</h1> <h3 class="byline">by {{ article.authorName }}</h3> <p> {{ article.body }} </p>
从其它模板中包含该模板是简单的:
{# src/Acme/ArticleBundle/Resources/Article/list.html.twig #} {% extends 'AcmeArticleBundle::layout.html.twig' %} {% block body %} <h1>Recent Articles<h1> {% for article in articles %} {% include 'AcmeArticleBundle:Article:articleDetails.html.twig' with {'article': article} %} {% endfor %} {% endblock %}
使用{% include %} 来包含模板。注意模板名也遵循同样的约定。articleDetails.html.twig模板使用了一个acticle变量,它通过list.html.twig模板使用with命令发送。
{'article':article}语法是对应哈希MAP(如有着命名关键词的数组)的标准Twig语法。如果我们需要发送多个元素,看上去类似 :{'foo':foo, 'bar':bar}。
有些情况,你需要做得更多,而不仅仅只是包含一个简单的模板。假设你的布局中有一个边栏,包含了最新的三篇文章。检索三篇文章也许要查询数据库或执行其它重量级的逻辑,这在一个模板中无法做到。
解决方法是从你的模板中简单地内嵌一个控制器处理的结果。首先创建一个控制器去渲染几篇最新文章:
// src/Acme/ArticleBundle/Controller/ArticleController.php class ArticleController extends Controller { public function recentArticlesAction($max = 3) { // 编写一个数据库调用或其它逻辑,得到"$max"个最新文章 $articles = ...; return $this->render('AcmeArticleBundle:Article:recentList.html.twig', array('articles' => $articles)); } }
recentList模板十分简单:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %}
注意在这个例子中我们已经在article的URL中使用了硬编码(如 /article/*slug*),这是个坏习惯。在下一节,你将学会如何去修正它。
为了包含控制器,你将要使用标准的字符串语法去引用它(如:bundle:controller:action):
{# app/Resources/views/base.html.twig #} ... <div id="sidebar"> {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %} </div>
无论什么时候你觉得你需要一个或一段你无法在模板中访问的变量和信息,那么考虑渲染一个控制器吧。控制器执行快,提高代码的组织性,并且可以重用。
在你应用程序中创建到其它页面的链接是模板众多常见任务之一。为了避免在模板中使用硬编码,使用Twig的path函数(或PHP中的router帮手函数)来生成基于路由配置的URL。然后,如果你想要修改特定页的URL,你所要做的就是改变路由配置;模板将自动生成新的URL。
首先链接到“首页“,通过下列路由配置访问:
homepage: pattern: / defaults: { _controller: FrameworkBundle:Default:index }
为了要链接该页,可以使用Twig的path函数指向路由:
<a href="{{ path('homepage') }}">Home</a>
可以预见,该链接将生成URL /。让我们看看链接如何工作在一个更为复杂的路由:
article_show: pattern: /article/{slug} defaults: { _controller: AcmeArticleBundle:Article:show }
在这个例子中,你需要指定路由名(article_show)和{slug}参数的值。使用这个路由,让我们重温上一节中的recentList模板,并链接到正确的文章:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {% for article in articles %} <a href="{{ path('article_show', { 'slug': article.slug }) }}"> {{ article.title }} </a> {% endfor %}
你也可以使用Twig的url函数生成绝对URL:
<a href="{{ url('homepage') }}">Home</a>
在PHP模板中通过发送generate()方法中的第三个参数,也可以做到:
<a href="<?php echo $view['router']->generate('homepage', array(), true) ?>">Home</a>
模板通常也指向图片、Javascript、样式表和其它的asset。当然可以硬编码这些asset的路径(如:/images/logo.png),但Symfony2通过Twig的asset函数来提供更多动态的选项:
<img src=\'#\'" asset('images/logo.png') }}" alt="Symfony!" /> <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
asset函数的主要目的是将你的应用程序更容易移植。如果你的应用程序在你主机的根目录下(如:http://example.com),然后被渲染的路径将是/images/logo.png。但如果你的应用程序是在子目录下(如:http://example.com/my_app),每个asset路径都将渲染成带子目录的(如:/my_app/images/logo.png)。asset函数要小心这一点,决定如何使用你的应用程序并并据此生成正确的路径。
配置和使用模板服务
在Symfony2中模板系统的核心是模板引擎。这个特定的对象负责渲染模板并返回它的内容。当你在控制器中渲染一个模板时,举个例子,你其实是在使用模板引擎服务。如:
return $this->render('AcmeArticleBundle:Article:index.html.twig');
等同于
$engine = $this->container->get('templating'); $content = $engine->render('AcmeArticleBundle:Article:index.html.twig'); return $response = new Response($content);
模板引擎(或“服务”)被预配置成在Symfony2中自动工作。当然它可以在应用程序配置文件中配置。
# app/config/config.yml framework: # ... templating: { engines: ['twig'] }
几个配置选项可用,可配置附录中可以找到。
twig引擎被强制使用webprofiler(就象许多第三方Bundle一样)
Symfony2最好的功能之一就是Bundle系统,它鼓励组件的组织性,在一定程序上使它们更容易被其它工程重用或者作为开源库发布。实际上,Symfony2社区对于它能够创建和维护实现大量不同功能的高质量Bundle,而颇感自豪。查找更多可用的开源Bundle,请访问Symfony2Bundles.org 网站。
在Symfony2中,Bundle的大多数部分都可以被覆写,以致于你可以为你特定的应用程序而使用和定制它。模板也不例外。
假设你已经在你的项目中包含了假想的开源博客AcmeBlogBundle(如在src/Acme/BlogBundle目录下)。你喜欢这个Bundle中的所有东西,但你想要覆写博客的“list”页以便为你的应用程序设置一个特殊的标志。通过分析AcmeBlogBundle的Blog控制器,你发现了以下语句:
public function indexAction() { $blogs = // some logic to retrieve the blogs $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs)); }
我们在模板命名和定位一节中学到模板位于AcmeBlogBundle的Resources/views/Blog/index.html.twig。为了覆写Bundle模板,将index.html.twig模板拷至app/Resources/AcmeBlogBundle/views/Blog/index.html.twig中(AcmeBlogBundle目录可能不存在)。现在当渲染AcmeBlogBundle:Blog:index.html.twig模板时,Symfony2将首先在app/Resources/AcmeBlogBundle/views/Blog/index.html.twig查找,而不是src/Acme/BlogBundle/Resources/views/Blog/index.html.twig。你现在可以为你的应用程序自由定制模板了。
假设在AcmeBlogBundle中的每个模板是自一个基本模板继承的,该模板在AcmeBlogBundle中被称为AcmeBlogBundle::layout.html.twig。缺省情况下,这个模板在AcmeBlogBundle中的Resources/views/layout.html.twig。为了覆写它,只需要把它拷到app/Resources/AcmeBlogBundle/views/layout.html.twig即可。
如果你回顾一下,你会明白Symfony2总是先查找位于app/Resources/BUNDLE_NAME/views/目录的模板。如果该模板不存在,它将继续在Bundle自己内部的Resources/views目录中查找。这意味着所有的Bundle模板都可以在app/Resources子目录中被覆写。
因为Symfony2框架自身也是一个Bundle,核心模板也可以用同样的方式覆写。举个例子,核心的FrameworkBundle包含着大量的“异常”和“错误”模板。要覆写它们,可以将它们从FrameworkBundle的Resources/views/Exception目录中拷贝到,正如你所想,app/Resources/FrameworkBundle/views/Exception目录中。
常见的继承使用方式之一就是三级继承。这种方式通过使用我们要覆盖的三个不同类型的模板实现得很完美:
*创建一个app/Resources/views/base.html.twig文件,其中包含你应用程序的主布局(如前例如示)。该模板可以用::base.html.twig来调用;
*创建一个为你站点的每部分设计的模板。举个例子,AcmeBlogBundle将有个名为AcmeBlogBundle::layout.html.twig的模板,仅包含blog部分的模板
{# src/Acme/BlogBundle/Resources/views/layout.html.twig #} {% extends '::base.html.twig' %} {% block body %} <h1>Blog Application</h1> {% block content %}{% endblock %} {% endblock %}
*创建为每一页创建一个独立的模板,并将其继承应用程序适当部分的模板。举个例子,“index”页面将调用AcmeBlogBundle:Blog:index.thml.twig模板并列出实际的博文。
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends 'AcmeBlogBundle::layout.html.twig' %} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}
注意这个模板继承了Blog模板(AcmeBlogBundle::layout.html.twig),该模板又继承自应用程序的基本布局(::base.html.twig)。这就是常见的三级继承模型。
当构建你的应用程序时,你可以选择使用这一方式或者简单地将每个页面模板都直接继承应用程序基本模板(如:{% extends '::base.html.twig' %})。三模板模型是被供应商Bundles使用最佳实践方式,它可以使Bundle中的基本模板可以容易地覆写去完美地继承你应用程序的基本布局。
当从模板生成HTML时总是存在危险,比如模板变量可能会输出一个意外的HTML或危险的客户端代码。结果就是动态内容可以中断结果页的HTML或允许恶意用户去执行跨站脚本(XSS)攻击。看看下面这个典型的示例:
Hello {{ name }}
设想一下当用户输入以下代码作为他/她的名称:
script>alert('hello!')</script>
在没有做任何输出清理的情况下,结果模板将弹出一个警告框:
Hello <script>alert('hello!')</script>
这看上去无害,但如果用户可以做得更多,同一用户也可以写一个Javascript脚本在一个未知或合法用户的安全范围去执行恶意的动作。
这个问题的答案就是输出清理。打开输出清理,同一模板将进行无害渲染,并按字面上的意思将脚本标签打印在屏幕上:
Hello <script>alert('helloe')</script>
Twig和PHP模板系统是通过不同的途径来解决这一问题的。如果你使用Twig,输出清理被缺省打开,你是受到保护的。在PHP中,输出清理不是自动的,这意味着在必要时你需要手工清理。
如果你正在使用Twig模板,那么输出清理缺省是打开的。这意味着你可以免受用户提交代码的意外后果。缺省情况下,输出清理假定HTML输出的内容是被清理的。
在某些案例中,当正在渲染一个被信任变量和不能被清理的标识时,你需要取消输出清理。假设管理用户可以写包含HTML代码的文章。缺省状态下,Twig将清理文章内容。要正常地渲染它,添加raw过滤器:{{ article.body | raw }}。
你也可以在{% block %}区域或整个模板中取消输出清,更多信息,请参见Twig文档中的输出清理 。
Output escaping is not automatic when using PHP templates. This means that unless you explicitly choose to escape a variable, you're not protected. To use output escaping, use the special escape() view method:
Hello <?php echo $view->escape($name) ?>
By default, the escape() method assumes that the variable is being rendered within an HTML context (and thus the variable is escaped to be safe for HTML). The second argument lets you change the context. For example, to output something in a JavaScript string, use the js context:
var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';
模板是用任何格式渲染内容的通用方式。在大多数情况下,你将使用模板去渲染HTML内容,模板也可以生成JavaScript、CSS、XML或任何你想要的格式。
举个例子,相同的“资源”经常被渲染成不同的几种格式。要渲染XML格式的文章索引页,只需在模板名中包含该格式即可:
XML模板名:AcmeArticleBundle:Article:index.xml.twig;XML模板文件名:index.xml.twig
事实上,这不过是个命名约定,模板其实并不能根据它的格式来进行不同的渲染。
在许多情况下,你也许想站单个控制器根据“请求格式”去渲染多个不同格式,为了这个原因,一种常见的模式如下所示:
public function indexAction() { $format = $this->get('request')->getRequestFormat(); return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); }
Request对象的getRequestFormat默认是html,但也可以根据用户的格式请求返回其它的格式。请求格式大多数都被路由管理,路由可以配置成将/contact设置成请求格式是HTML、/contact.xml设置成格式是XML。更多信息请参见路由的高级示例章节。
为了创建包含格式参数的链接,在参数的哈希数组中包括_format关键词:
<a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}"> PDF Version </a>
在Symfony2的模板引擎是一个强大的工具,每当你需要生成用HTML、XML或其它任何格式显示内容时都可以用得上。虽然模板是在控制器中生成内容的常见方式,但并不强制使用它。被控制器返回的Response对象可以不使用模板:
// 创建内容被模板渲染的 Response 对象 $response = $this->render('AcmeArticleBundle:Article:index.html.twig'); // 创建内容是简单文本的 Response 对象 $response = new Response('response content');
Symfony2的模板引擎非常灵活,缺省状态下有两种不同的模板渲染器可用:传统的PHP和平滑而强大的Twig模板。两者支持模板继承,并且自带的大量帮手函数也能够胜任大多数常见任务。
总之,模板应该被认为是你强大的工具。某些情况下,你也许不需要去渲染模板。在Symfony2中,这绝对是可行的。