第七章:创建并使用模板

就像你知道的,controller负责处理每一个进入symfony2应用程序的请求。实际上,controller把大部分的繁重工作都委托给了其它地方,以使代码能够被测试和重用。当一个controller需要生成HTML,CSS或者其他内容时,它把这些工作给了一个模板化引擎。在本章中,您将学习如何编写功能强大的模板,用于把内容返回给用户,填充email,等等。您还将学会快捷方式,用聪明的方法扩展模板,以及如何重用模板代码。

如何渲染模板请查看book的controller。

 

模板

模板是很简单的,可以生成基于文本格式(html,xml,csv,LaTex…)的任何文本文件。我们最熟悉的模板类型就是php模板-包含文字和php代码的混合模式的一个文本文件:

但是,symfony packages 有一个更加强大的模板语言Twig。Twig模板学法简明、易读,对页面设计师更友好,在许多方面比PHP模板更加强大。

Twig定义了三种特殊的语法:

{{ … }}

“说事”:打印一个变量或者一个表达式的值到模板。

{% … %}

“做某些事”,控制模板逻辑的标签,它用于执行比如for循环语句等。

{# … #}

“注释一些东西”:他相当于php的/* comment */语法。它用于注释单行和多行。注释的内容不会呈现在页面中。

twig也包含filters,他可以在模板渲染之前改变内容。下面让title变量渲染前全部大写:

Twig内置就有一大群的标签(tags)和过滤器(filters)可以使用。当然你也可以根据需求扩展自己的Twig。

注册一个Twig扩展非常容易,创建一个新服务并把它标记为Twig.extension 标签。

正如你会在本文档中看到的一样,Twig还支持功能和新功能的添加。比如,下面使用一个标准的for标签和cycle功能函数来打印10个div 标签,用odd,even 属性代替。

在本章中,模板案例将展示twig和php方式。

如果你不使用Twig或者禁用它,你需要通过kernel.execption事件实现自己的一个异常处理程序。

为什么使用twig?

twig模板是为了尽量简单,不去处理php标签。这样设计:twig模板只负责呈现,而不去考虑逻辑。你用twig越多,你就越会欣赏它,并从这种方式中受益。当然,你也会被无数的网页设计者喜欢。

还有很多twig可以做但是php不能的事情,如空格控制、沙盒、自动html转义、手动上下文输出转义还有可以包含定制的(函数和fiters),这些只影响模板。twig包含一些小功能,使得写模板更加方便和简洁。看看下面的例子,他结合了逻辑循环,if语句:

 

 

Twig模板缓存

twig是很快的。每个Twig模板被编译到原生的PHP类,它将在运行时被渲染。编译过的类被保存在app/cache/{environment}/twig 目录下(其中{environment} 是环境,如devprod),并在某些情况下可以同时调试。

当debug模式可用时,一个twig模板如果发生改变将会被自动重新编译。这就意味着你可以在开发过程中随意的修改模板,而不必担心需要去清除内存了。

当debug模式被关闭时,你必须手动的清除Twig缓存目录,以便能够重新生成Twig模板。记住在部署程序时一定要做到这一点。

 

模板继承和布局

大多数的时候,模板在项目中都有通用的元素,比如header,footer,sidebar等等。在Symfony2中,我们将采用不同的思考角度来对待这个问题。一个模板可以被另外的模板装饰。这个的工作原理跟PHP类非常像,模板继承让你可以创建一个基础”layout”模板,它包含你的站点的所有通用元素并被定义成blocks。这里的block可以比作是为PHP基类的方法。 一个子模板可以继承基础的layout模板并重写它任何一个block。

现在首先创建一个base layout文件:

 虽然在讨论关于Twig的模板继承,从原理上其实twig和php模板之间是相同的。

该模板定义了一个简单的两列式的html页面。在本例中,有三处{% block %}分别定义了titlesidebar和body。每个block都可以被继承它的子模板重写或者保留它现在的默认实现。该模板也能被直接渲染,只不过只是显示基础模板的定义内容。

一个子模板看起来是这样的:

父模板由一个特殊的字符串语法表示 base.html.twig ,它表示该模板在项目的 app/Resources/views 目录下。你也可以使用逻辑名称相同的::base.html.twig。具体可查看模板名称和位置。

模板继承的关键字 {% extends %}标签。 该标签告诉模板引擎首先评估父模板,它会设置布局和定义多个blocks。然后是子模板,上例中父模板中定义的title和body 两个blocks将会被子模板中的定义所取代。根据blog_entries的值,输出的内容如下:

请注意,由于子模板中没有定义sidebar这个block,所以来自父模板的内容被显示出来,而没有被子模板替代。位于父模板中的{% block %}标签是默认值,如果没有被子模板重写覆盖,它将作为默认值使用。

只要你想,你可以根据你的需要进行多层继承。 Symfony2项目中一般使用一种三层继承模式来组织模板和页面。

当我们使用模板继承时,需要注意:

  •   如果在模板中使用{% extends %},那么它必须是模板的第一个标签。
  •   你父模板中{% block %}越多越好,记住,子模板不必定义父模板中所有的block。你父模板中block定义的越多,你的布局就越灵活。
  •   如果你发现在多个模板中有重复的内容,这可能就意味着你需要为该内容在父模板中定义一个{% block %}。有些时候更好的解决方案可能是把这些内容放到一个新模板中,然后在该模板中include它。
  •   如果你需要从父模板中获取一个block的内容,你可以使用{{ parent() }}函数。如果你只是想在父模板上添加新的内容,而不是覆盖它,这就非常有用了:

 

模板的命名和存储位置

默认情况下,模板可以被保存到两个不同的位置上:

app/Resources/views

这个应用程序的views 目录可以存放整个应用程序级的基础模板(你应用程序的布局和bundle的模板)以及那些重写bundle模板的模板(查看 Overriding Bundle Templates)。

path/to/bundle/Resources/views

每个第三方bundle的模板都会放在他自己的Resources/views/目录(或者子目录)。当你打算分享你的bundle,你应该把它放在bundle模板中,而不是app/目录。

更多的时候,你会把模板放到app/Resources/views/目录下。这个目录还可以创建相对目录。例如,去渲染和继承app/Resources/views/base.html.twig,你需要使用base.html.twig的路径,并且去渲染app/Resources/views/blog/index.html.twig时,你需要使用blog/index.html.twig路径。

 

在Bundle中引入模板

Symfony2使用bundle:directory:filename字符串语法表示模板。这可以表示许多不同类型的模板,每种都存放在特定的路径下:
AcmeBlogBundle:Blog:index.html.twig 用于指定一个特定页面的模板。字符串分为三个部分,每个部分由冒号(:)隔开,含义如下:

AcmeBlogBundle :(bundle)说明模板位于AcmeBlogBundle,比如src/Acme/BlogBundle。

Blog:(目录)指定模板位于Resourcs/views的Blog子目录中。

index.html.twig:(文件名)文件的实际名称为index.html.twig。

假设AcmeBlogBundle位于src/Acme/BlogBundle, 最终的路径将是:src/Acme/BlogBundle/Resources/views/Blog/index.html.twig

AcmeBlogBundle::layout.html.twig 该表示法指向AcmeBlogBundle的父模板。没有中间的“目录”部分(如blog),所以模板应该位于AcmeBlogBundle的Resources/views/layout.html.twig。是的,该语句两个冒号,意味着没有“控制器”目录部分。
在重写Bundle模板一节,你将发现位于AcmeBlogBundle的模板,是如何被app/Resources/AcmeBlogBundle/views/目录下的所有同名模板所重写的,这种方式给了我们一个有力的途径来重写供应商提供的bundle的模板。

模版的命名语法看起来很熟悉-它类似于控制器的命名模式规则。

 

模版后缀

每个模版都有两个扩展名,来指定格式和模版引擎。

文件名 格式 引擎
blog/index.html.twig HTML Twig
blog/index.html.php HTML PHP
blog/index.css.twig CSS Twig

默认情况下,Symfony2的任何模板都可以被写成Twig或者PHP引擎的,它由后缀决定。其中后缀的前一部分(.html,.css)表示最终生成的格式。不像引擎,它是决定symfony如何解析模板,这是一个很简单的使用策略,你可以使用HTML(index.html.twig),XML(index.xml.twig)或任何其他格式作为渲染的资源。更多的细节,请阅读模版格式部分。

这个“engines”变量是可以配置的,甚至可以添加一个新的引擎。请查看 模版配置 更多细节。

 

标签和助手

您已经了解了模板,它们如何命名以及如何使用模板继承的基础知识。最难的部分已经过去。接下来,我们将了解大量的可用工具来帮助我们执行最通用的模板任务,比如包含其他模板,链接一个页面或者包含图片。

Symfony2 中包含了几个专业的Twig标签和功能函数来帮助设计者更容易的完成工作。在PHP中,模板系统提供了一个可扩展的helper系统,它可以在模板上下文中提供许多有用的内容。

你已经看到了一些内建的Twig标签,比如{% block %}{% extends %}等,还有PHP 助手$view[‘slots’]。在这里,你还会学到更多。

引入其他模版

你可能经常想在多个不同的页面中包含同一个模板或者代码片段。比如“新闻文章”会显示在一个应用程序的模板中,那么这一篇文章,可能会被用到文章详细页面,一个现实最流行文章的页面,或者一个最新文章的列表页面等。

当你需要重用一些PHP代码,你通常都是把这些代码放到一个PHP类或者函数中。同样在模板中你也可以这么做。通过把可重用的代码放到一个它自己的模板中,然后把这个模板包含到其他模板中。比如我们创建一个可重用模板如下:

在其他模板引入这个模板很简单:

这个模板包含使用{% include %}标签。请注意,模板名称要遵循通用方式。在article_details.html.twig模板中使用article变量,来传入值。这种情况下,你也可以避免这样做,因为在list.html.twig模板中变量也可以在article_details.html.twig中使用(除非你设置with_context为false)。

{‘article':article}语法是标准Twig哈希映射的写法。如果我们需要传递多个元素,可以写成{‘foo': foo, ‘bar': bar}。

 

嵌入Controllers

在某些情况下,你需要做的不止包含一个简单的模板。假设你有一个菜单栏sidebar在你的布局文件中来显示最新的文章。获取三篇最新文章可能需要查询数据库或者执行其它包含很多逻辑的操作,这样就不能在一个模板中完成了。

这种情况的解决方案是简单的嵌入一个完整的controller结果到你的模板。首先,创建一个controller来渲染特定数量的最近文章:

这个recentList模板很简单:

 请注意,这篇文章的URL被硬编码在案例中(例如 /article/*slug*)。这是不好的做法。在下一节中,您将学习如何正确做。

为了能包含controller,你需要使用一个标准的字符语法来表示controller,格式类似bundle:controller:action

你需要一个变量或者一些列信息时,你不必在模板中访问,而是考虑渲染一个controller。因为Controller能够更快的执行并且很好的提高了代码的组织和重用。当然,像所有的控制器,它们都应该“瘦身”,这意味着尽可能多的代码应该被做成可重用的服务。

 

异步内容hinclude.js

使用hinclude.js-JavaScript库能够异步嵌入Controllers。这个嵌入的内容来自其他页面(或者控制器),symfony使用一个标准版本的render函数去配置hinclude标签:

 hinclude.js需要包含在你的网页中才能工作。

当使用一个控制器作为一个URL,你启用symfony的fragments配置:

可以应用程序配置中设置全局默认内容(正在加载或者禁用javascript):

你能够定义每一个默认模板的render函数(覆盖任何已经定义的全局默认模板):

或者你也可以指定一个字符串,以现实默认内容:

 

页面链接

在你的应用程序中创建一个链接到其它页面对于一个模板来说是再普通不过的事情了。我们采用path Twig函数基于路由配置来生成URL而非在模板中硬编码URL。以后如果你想修改一个特定页面的URL,你只需要改变路由配置即可,模板将自动生成新的URL。

比如我们打算链接到”_welcome”页面,首先定义其路由配置:

我们可以在模板中使用Twig的path函数来配置这个路由生成该页面的链接。

正如预期的那样,它生成了URL /。现在,弄一个更复杂的路由:

这种情况下,你需要指定路由名称(article_show)还要一个参数值{slug}。现在让我们再来看上面的recentList模板 ,我们修改以前的硬编码而使用path链接正确的文章。

你能够使用Twig的URL生成绝对路径:

PHP代码模板中,你需要给generate()方法传入第三个参数true 来实现生成绝对路径:

 

 

Assets链接

模板通常也需要一些图片,Javascript,样式文件和其它资产。当然你可以硬编码它们的路径。比如/images/logo.png。 但是Symfony2 提供了一个更加动态的Twig函数 asset()。

asset函数的主要目的是让你的应用程序更加便捷。如果你的应用程序在你的主机根目录下(比如:http://example.com),那么它会渲染出的路径为 /images/logo.png 。但是如果你的应用程序位于一个子目录中(比如:http://example.com/my_app),这时它会为你渲染出的路径变为 /my_app/images/logo.png 。asset函数能够根据你的应用程序来生成正确的路径。

另外,如果你使用asset函数,symfony可以自动追加一个查询字符串到你的资产,以保证被更新的静态资源不会在部署时被缓存。比如:/images/logo.png 可能看起来是 /images/logo.png?v2 。想了解更多内容,请参阅assets_version配置选项。

在一个asset-by-asset基础上设置URL版本是在symfony2.5被引入。

如果你需要设置特定的资源版本,你可以设置第四个参数(或者是这个版本参数)为所需的版本:

如果你没有设置一个版本或者设置为null,默认的软件版本(从assets_version)将被使用。如果传入false,url版本将被停用。

assets绝对URL在symfony2.5被引入。

如果你需要assets绝对URL,你可以设置第三个参数(或者是这个absolute参数)为true:

 私人注释:想要查看asset参数信息请阅读http://symfony.com/doc/current/reference/twig_reference.html#asset

 

 

包含样式表和Javascript文件到Twig模板

每个网站中都不可能缺少了样式表和javascript文件。在Symfony中,这些内容可以通过模板继承优势优雅的处理。

这章会教你如何包含stylesheet和javaScript资源。Symfony打包了另外一个类库叫做Assetic, 它允许你对这些资源做更多的有趣操作。更多细节请查看How to Use Assetic for Asset Management.

首先在你的基模板中添加两个blocks来保存你的资源,一个叫stylesheets,放在head标签里,另一个叫javascript,放在body结束标签上面一行。这些blocks将包含你整个站点所需要的所有stylesheets和javascripts。

这太简单了!但如果你想从子模板中包含一个额外的stylesheet或者javascript怎么办呢?比如假设你有一个联系页面需要包含一个contact.css样式表只用于该页面。在你的contact页面模板内部,你可以这样实现

在子模板中你只需要重写stylesheets block并把你新的样式表标签放到该块里。当然,因为你想添加到父模板该块内容中(而不是替代它们),所以你需要在此之前先使用parent()函数来获取父模板中的所有stylesheets。

你也可以包含资源位置到你的bundle的Resources/public 文件夹。你需要执行php app/console assets:install target [–symlink]命令,它会把文件移动到正确的位置,默认情况下的目标位置是web文件夹。

上面代码最终结果是页面会包含main.css和contact.css样式表。

 

全局模板变量

在每个请求中,Symfony2 将会在Twig引擎和PHP引擎默认设置一个全局模板变量app。该app变量是一个GlobalVariables实例,它将让你自动访问到应用程序一些特定的变量。比如:

app.security 安全上下文

app.user 当前用户对象

app.request 当前Request对象

app.session Session对象

app.environment 当前应用程序的环境(dev,prod等)

app.debug 如果是true说明是调试模式,false则不是。

这个全局app.security变量(或者在php模板中的$app-getSecurity()方法)在symfony2.6被弃用。使用app-user($app-getUser())或者is_granted()($view[‘security’]->isGranted())来代替。

你也可以向其添加你自己的全局模板变量 。查看cookbook例子Global Variables.

 

配置和使用模板服务

Symfony2模板系统的核心是模板化引擎。这个特殊的对象负责渲染模板并返回他们正确的内容。当你在controller中渲染一个模板时,其实你是使用了模板化引擎服务。举例

相当于

该模板化引擎(服务)在Symfony2内部是预先配置好,自动工作的。当然,它也可以在应用程序的配置文件中进行配置。

还有几个配置选项是可用的,包含在附件的配置

twig强制使用webprofiler(以及许多第三方包)。

 

重写bundle模板

Symfony2社区现在正在为他们创建和维护了很多不同内容的高质量的Bundle而感到骄傲(见KnpBundle.com)。一旦你使用第三方的bundle你可能想重写或者个性化它们的一个或者多个模板。

假设你已经包含了开源AcmeBlogBundle到你的项目中,比如目录是src/Acme/BlogBundle,你想重写blog列表(list)页面来按照你自己的应用程序风格个性化它。我们打开AcmeBlogBundle的Blog controller,可以发现如下内容:

当AcmeBlogBundle:Blog:index.html.twig被渲染时,Symfony2 会查找两个位置来定位模板:

1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig

要重写该bundle的模板,仅仅从bundle中拷贝index.html.twig 模板到app/Resources/AcmeBlogBundle/views/Blog/index.html.twig。
注意,如果app/Resources/AcmeBlogBundle目录不存在,可以手工建立。之后你就可以随心所欲的个性化处理该模板了。

如果你在一个新的位置添加模板,你可能需要清楚一下缓存(php app/console cache:clear),即使是在调试模式下。

该逻辑同样适用于基bundle模板,假设AcmeBlogBundle中每个模板都是继承于基模板AcmeBlogBundle::layout.html.twig。Symfony2 将从下面两个地方寻找模板:
1.app/Resources/AcmeBlogBundle/vews/layout.html.twig
2.src/Acme/BlogBundle/Resources/views/layout.html.twig
同样的,如果要重写该模板,你仅仅需要从bundle中拷贝到app/Resources/AcmeBlogBundle/views/layout.html.twig 就可以轻松个性化它了。你也可以通过bundle继承来从bundle内部重写模版。

如果你退一步,你会看到symfony总是从app/Resources/{BUNDLE_NAME}/views/目录下的模板开始。如果这个模板不存在,它就会到自己bundle的Resources/views目录寻找。这意味着,所有的bundle模板都能够通过正确的app/Resources子目录覆盖。

你还可以使用bundle继承从一个bundle内部继承模板。想了解更多,请参阅How to Use Bundle Inheritance to Override Parts of a Bundle.

 

重写核心模板

因为Symfony2 框架本身就是一个bundle,所以其核心模板也可以按照同样的方式进行重写。比如,核心模板TwigBundle包含很多不同”execption”和“error” 的模板,你可以通过复制Resources/views/Exception 目录下每一项到app/Resources/TwigBundle/Views/Exception 目录下 。

 

三级继承

Symfony2中一般采用三级继承来完成模板创建。她可以完美的覆盖三种不同类型的模板:
首先,创建一个app/Resources/views/base.html.twig 文件,它包含你应用程序主要的布局,该模板被调用时写成 ::base.html.twig。
然后,为你站点的每个部分“section”创建一个模板。比如,一个博客功能, 它有自己的一个模板blog/layout.html.twig。该模板只包含特定的元素,比如body。

最后,是为每个页面创建一个单独的模板并继承合适的section模板。比如,为index页创建的模板 Blog/index.html.twig 用来列出所有的blog。

注意该模板是继承了section模板(blog/layout.html.twig),而section模板又继承了应用程序基模板(base.html.twig)。这就是通常说的三级继承模式。

在你创建你的应用程序时,你可以选择这种模式,也可以为每个页面创建模板时直接继承应用程序的基模板(比如: {% extends ‘base.html.twig’ %} 。三级继承模板模式对于一个bundle开发商来说是最好的方法,因为这样bundle使用者可以很容易的重写bundle的基模板来适合自己应用程序的基本布局。

 

输出转义

当我们从模板中生成HTML时,总会有风险存在,比如一些模板变量可能输出一些意外的HTML或者危险的客户端代码。结果这些动态内容会打破结果页面的HTML或者允许一些恶意的访问者执行一些页面攻击。举个例子:

你想象一下这个用户输入下面的代码:

如果没有任何的转义,此模板会导致弹出一个js警告框

这种情况看上去,没多大害处,但是如果一个用户知道这一步,它完全有能力写一个javascript在我们未知的安全区域来执行一些恶意行为。

这个问题的解决办法是输出安全转义。 如果添加了输出安全转义,同样的模板会渲染出无害的内容,script标签会作为普通文本输出到屏幕上。

PHP和Twig模板化系统采用了不同的方式来解决这个问题。如果你使用Twig,默认情况下是输出安全转义的,你的输出是受到保护的。如果是PHP,则输出安全转义不是自动的,需要你手工的进行处理。

Twig中的输出转义

如果你使用Twig模板,那么输出转义是默认的。你不需要对用户提交的输出内容进行手动保护。

在某些情况下,你需要关闭输出转义保护,当你渲染某个可信变量或者包含某个标签时。假设管理员用户可以编写一些代码有HTML标签的文章。默认情况下,Twig将转义文章体。

为了渲染正常,需要添加一个raw 过滤器:

你也可以在{% block %}区域或者整个模板中关闭输出转义。想了解更多,请参见Twig文档Output Escaping

PHP中输出转义

在PHP中安全输出保护不是自动完成的,这就意味着除非你显示的选择来对某个输出变量进行保护,否则输出都是不安全的。我们使用view的方法escape()来对输出变量进行安全输出保护:

默认情况下,escape()方法默认情况下假设变量是被渲染在一个HTML上下文中的。也就是说只针对HTML安全。 它的第二个参数让你能够改变它针对的上下文。比如需要在javascript上下文中输出某些东西,就是用 js 上下文。

 

调试

当使用php代码时,我们可以使用dump()来快速的查看一个值的变量传递。他非常有用,例如,在你的控制器里:

工具条会渲染dump函数

在twig模板中使用dump函数达到相同的机制

如果Twig的debug设置(config.yml)为true,这个变量才会抛出。默认情况下,这意味着变量只能

在dev环境抛出,而不能在prod环境。

 

语法检查

你可以使用twig:lint命令检查twig模板语法错误:

 

模板格式

模板是一个渲染任何格式内容的通用的方式。大多数情况下,我们使用模板来渲染HTML内容。模板同样也能渲染想javascript,CSS,XML以及你能想象到的其它格式内容。

比如,同一个资源resource经常被渲染为不同的格式。把文章目录页渲染为XML,你需要在模板的名称中包含相应的格式即可。

XML 模板名: Article/index.xml.twig
XML 模板文件名:index.xml.twig

事实上,这里只是命名上有了变化,而模板并没有真正的基于不同的格式渲染不同。

在大多数情况下,你可能想让单一的controller根据请求的格式不同渲染多个不同的格式。下面是一个通常的写法:

Request对象的getRequestFormat()方法默认返回值为html, request的格式通常是在路由时决定的。比如/contact 设置的请求格式是html,而 /contact.xml 设置的请求格式则是 XML。想了解更多请查看Advanced Example in the Routing chapter

创建一个包含请求格式的链接,只需要在参数哈希表中包含 _format键值即可。

 

总结

Symfony2中的模板引擎是一个强大的工具,你可以用它来根据需要生成包括HTML,XML以及其它任何格式的内容。虽然模板以controller生产是一种常见的方式,但是不是必须的。controller返回的Response对象可以使用模板也可以没有模板。

Symfony2的模板引起非常灵活,默认情况下支持传统的PHP模板和圆滑强大的Twig模板,他们都拥有非常丰富的帮助函数来执行一些常见任务。Symfony2推荐使用Twig模板,因为它更加简洁,高效,能更好的处理继承等。

总体而言,在你处理模板问题的时候,它是一个强大的工具。在某些情况下,你可能不需要渲染模板,在symfony中,这绝对是没有问题的。

发表评论