Symfony2 中国語マニュアル /创建和使用模板

创建和使用模板

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

如何渲染模板请查看“框架起步”之控制器。

模板

模板就是生成基于文本格式(html,xml,csv,LaTex…)的任何文本文件。我们最熟悉的模板类型就是php模板——包含文字和php代码的被PHP引擎解析的文本文件:

   Welcome to Symfony! 

但是,symfony 框架中有一个更强大的模板语言叫作Twig。Twig可令你写出简明易读且对设计师友好的模板,在几个方面比PHP模板强大许多。

   Welcome to Symfony! 

{{ page_title }}

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

  • {{ ... }}
  • “说些什么”:输出一个变量值或者一个表达式的结果到模板。
  • {% ... %}
  • “做些什么”:控制模板逻辑的*tag(标签)*,用于执行声明,如for循环语句等。
  • {# ... #}
  • “进行注释”:它相当于php的/* comment */语法。它用于注释单行和多行。注释的内容不作为页面输出。

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

{{ title|upper }}

Twig内置了大量的标签(tags)和变量调节器(filters),默认就可以使用。你甚至可以利用Twig扩展来添加你自己的自定义调节器和函数(乃至更多)。

注册一个Twig扩展非常容易,创建一个新服务并打上Twig.extension标签。

Twig代码很像PHP代码,两者有微妙的区别。下例使用了一个标准的for标签和cycle函数来输出10个div 标签,用oddevencss类交替显示。

{% for i in 0..10 %} 
{% endfor %}

本章的模板例程,将同时使用twig和php来展示。

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

为什么使用twig?

twig模板就是为了简单而不去处理php标签。设计上即是如此:twig模板只负责呈现,而不去考虑逻辑。你用twig越多,你就越会欣赏它,并从它的特性中受益。当然,你也会被普天下的网页设计者喜欢。

还有很多twig可以做但是php不可以的事,如空格控制、沙盒、自动html转义、手动上下文输出转义,以及包容“只会影响模板”的自定义函数和调节器。twig包含一些小功能,使得写模板时更加方便快捷。请看下例,它结合了逻辑循环和if语句:

    {% for user in users if user.active %}
  • {{ user.username }}
  • {% else %}
  • No users found
  • {% endfor %}

Twig模板缓存

twig是很快的,每个Twig模板被编译成原生PHP类并且缓存起来。编译过的类被保存在var/cache/{environment}/twig目录下(其中{environment}是环境,如devprod),并在某些情况下可以同时调试,非常有用。关于环境的更多细节请参考:环境

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

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

模板继承和布局

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

首先创建一个layout基础文件:

Twig:{# app/Resources/views/base.html.twig #}    {% block title %}Test Application{% endblock %} 
{% block body %}{% endblock %}
PHP:     <?php $view['slots']->output('title', 'Test Application') ?> 
output('body') ?>

虽然讨论的是关于Twig的模板继承,但在思维方式上twig和php模板之间是相同的。

该模板定义了一个简单的两列式html页面。在本例中,三处{% block %}区域被定义了(即titlesidebarbody)。每个block都可以被继承它的子模板覆写,或者保留现在这种默认实现。该模板也能被直接渲染(输出)。只不过此时只是显示基础模板所定义的内容,title,sidebarbody都将保持默认值。

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

Twig:{# 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 %} 

{{ entry.title }}

{{ entry.body }}

{% endfor %}{% endblock %}
php:extend('base.html.php') ?> set('title', 'My cool blog posts') ?> start('body') ?>  

getTitle() ?>

getBody() ?>

stop() ?>

父模板以一个特殊的字符串语法来表示base.html.twig,这个路径是相对于整个项目的app/Resources/views目录而言的。你也可以使用逻辑名称相同的::base.html.twig。参考下文的模板名称和位置

模板继承的关键字是{% extends %}标签。 该标签告诉模板引擎首先评估父模板,它设置了布局并定义了若干blocks。然后子模板被渲染,上例中父模板中定义的titlebody两个blocks将会被子模板中的同名区块内容所取代。根据blog_entries的取值,输出的内容可能像下面这样:

   My cool blog posts 

My first post

The body of the first post.

Another post

The body of the second post.

注意,由于子模板中没有定义sidebar这个block,来自父模板的内容将被显示出来。父模板中的{% block %}标签内的内容,始终作为默认值来用。

你可以进行任意多个层级的模板继承。Symfony项目中一般使用“三级继承”模式,来组织模板和页面,参考如何使用继承来组织你的Twig模板。

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

  • 如果在模板中使用{% extends %},它必须是模板中的第一个标签。

  • 你的基础(布局)模板中的{% block %}标签越多越好,记得,子模板不必定义父模板中的所有block。基础模板中的block定义得愈多,你的布局就愈灵活。

  • 如果你发现在多个模板中有重复的内容,这可能意味着你需要为该内容在父模板中定义一个{% block %}了。某些情况下,更好的解决方案可能是把这些内容放到一个新模板中,然后在该模板中include它。(查看下文的:包容其他模板)

  • 如果你需要从父模板中获取一个block的内容,可以使用{{ parent() }}函数。如果你只是想在父级块上添加新内容,而不是完全覆盖它,这很有用:

{% block sidebar %} 

Table of Contents

{# ... #} {{ parent() }}{% endblock %}

模板的命名和存储位置 ¶

默认情况下,模板可以存放在两个不同的位置:

app/Resources/views

程序级的views目录可以存放整个程序的基础模板(程序布局和bundle模板),以及那些“用于覆写第三方bundle的模板”的模板(如何覆写第三方bundle的模板)。

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中引入模板

Symfony使用bundle:directory:filename字符串语法表示模板。这可以表示许多不同类型的模板,每种都存放在一个特定路径下:

  • AcmeBlogBundle:Blog:index.html.twig用于指定一个特定页面的模板。字符串分为三个部分,每个部分由冒号(:)隔开,含义如下:
  1. AcmeBlogBundle:(bundle)模板位于AcmeBlogBundle,比如src/Acme/BlogBundle

  2. Blog:(目录)表明模板位于Resourcs/viewsBlog子目录中;

  3. 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作者提供的bundle模板。

模版的命名语法可能看上去比较熟悉——它类似于控制器命名模式中提到的约定。

模版后缀 ¶

每个模版都有两个扩展名,用来指定格式(format)和模版引擎(engine)

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

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

可用的“engines”部分是可配置的,甚至可以添加一个新的引擎。请查看 如何配置和使用模板服务以了解更多细节。

Tags和Helps ¶

你已经了解了模板基础,它们是如何命名以及如何使用模板继承等基础知识。最难的部分已经过去。接下来,我们将了解大量的可用工具来帮我们完成常见的模板任务,比如包容其他模板,链接到一个页面或者引入图片。

Symfony框架中内置了几个特殊的Twig标签和功能函数,来帮助模板设计者简化工作。在PHP中,模板系统提供了一个可扩展的helper系统用于在模板上下文中提供有用的功能。

你已经看到了一些内置的Twig标签,比如({% block %}&{% extends %})等,还有PHP helper$view[‘slots’]。现在,你将会学到更多。

引入其他模版

你经常需要在多个不同的页面中包含同一个模板或者代码片段。比如在一个“新闻文章”程序中,用于显示文章的模板代码可能会被用到正文页,或者用到一个显示“人气文章”的页面,乃至一个“最新文章”的列表页等。

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

Twig:{# app/Resources/views/article/article_details.html.twig #}

{{ article.title }}

{{ article.body }}

php: 

getTitle() ?>

getBody() ?>

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

Twig:{# app/Resources/views/article/list.html.twig #}{% extends 'layout.html.twig' %} {% block body %} 

Recent Articles

{% for article in articles %} {{ include('article/article_details.html.twig', { 'article': article }) }} {% endfor %}{% endblock %}

php:extend('layout.html.php') ?> start('body') ?> 

Recent Articles

render( 'Article/article_details.html.php', array('article' => $article) ) ?> stop() ?>

这个模板被包容时,使用了{{ include() }}标签。请注意,模板命名要遵循相同的典型约定。在article_details.html.twig模板中使用article变量,这是我们传入模板的。本例中,你也可以完全不这样做,因为在list.html.twig模板中可用的所有变量也都可以在article_details.html.twig中使用(除非你设置with_context为false)。

{'article':article}语法是标准Twig哈希映射(hash maps)的写法(即是一个键值对数组)。如果你需要传递多个元素,可以写成{'foo': foo, 'bar': bar}

链接到页面

在你的程序中创建一个链接到其他页面,对于模板来说是再普通不过的事情了。使用pathTwig函数(或者php中的routerhelper)基于路由配置来生成URLs而非在模板中写死URLs。以后,如果你想修改一个特定页面的URL,你只需要改变路由配置即可;模板将自动生成新的URL。

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

Annotations:// src/AppBundle/Controller/WelcomeController.php // ...use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class WelcomeController extends Controller{ /** * @Route("/", name="_welcome") */ public function indexAction() { // ... }}
YAML:# app/config/routing.yml_welcome: path: / defaults: { _controller: AppBundle:Welcome:index }
XAML:  AppBundle:Welcome:index 
PHP:// app/config/routing.phpuse Symfony\Component\Routing\Route;use Symfony\Component\Routing\RouteCollection; $collection = new RouteCollection();$collection->add('_welcome', new Route('/', array( '_controller' => 'AppBundle:Welcome:index',))); return $collection;

要链到页面,只需使用Twig的path函数来指定这个路由即可。

Twig:Home
php:Home

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

Annotations:// src/AppBundle/Controller/ArticleController.php // ...use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class ArticleController extends Controller{ /** * @Route("/article/{slug}", name="article_show") */ public function showAction($slug) { // ... }}
TAML:# app/config/routing.ymlarticle_show: path: /article/{slug} defaults: { _controller: AppBundle:Article:show }
PHP:// app/config/routing.phpuse Symfony\Component\Routing\Route;use Symfony\Component\Routing\RouteCollection; $collection = new RouteCollection();$collection->add('article_show', new Route('/article/{slug}', array( '_controller' => 'AppBundle:Article:show',))); return $collection;
XAML:  AppBundle:Article:show 

这种情况下,你需要指定路由名称()以及一个。使用这个路由重新定义前文提到的模板,并正确链入文章。

Twig:{# app/Resources/views/article/recent_list.html.twig #}{% for article in articles %}  {{ article.title }} {% endfor %}
php:  getTitle() ?> 

你可以通过Twig的url函数来生成绝对路径:

Twig:Home php:Home

链接到Assets

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

Twig:Symfony! 
php:Symfony! 

asset函数的主要目的,是让你的程序更加portable(可移动)。如果你的程序在主机根目录下(如http://example.com),生成的路径应该是/images/logo.png。但是如果你的程序位于一个子目录中(如http://example.com/my_app),asset路径在生成时应该带有子目录(如/my_app/images/logo.png) 。asset函数负责打点这些,它根据你的程序 “是如何使用的” 而生成相应的正确路径。

另外,如果你使用asset函数,symfony可以自动追加一个query string(查询字符串)到你的资产,以保证被更新的静态资源不会在部署时被缓存。例如,/images/logo.png可能看起来是/images/logo.png?v2。参考version配置一文以了解更多。

如果你需要assets资源的绝对URL,可以使用absolute_url()Twig函数:

Symfony!

在Twig中包容样式表和Javascript

每个网站中都不能完全没有样式表和javascript文件。在Symfony中,这些内容可以利用模板继承来优雅地处理。

本节教给你包容stylesheet和javaScript资源时的背后思想。Symfony支持另外一个类库叫Assetic, 它允许你在遵循这一思想时,对这些资源做更多有趣事情。参考如何使用Assetic 进行资产管理以了解更多细节。

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

Twig:{# app/Resources/views/base.html.twig #}  {# ... #} {% block stylesheets %}  {% endblock %} 
{# ... #} {% block javascripts %} {% endblock %}
php:// app/Resources/views/base.html.php   start('stylesheets') ?>  stop() ?> 
start('javascripts') ?> stop() ?>

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

Twig:{# app/Resources/views/contact/contact.html.twig #}{% extends 'base.html.twig' %} {% block stylesheets %} {{ parent() }} {% endblock %} {# ... #}
php:// app/Resources/views/contact/contact.html.twigextend('base.html.php') ?> start('stylesheets') ?> stop() ?>

在子模板中,你只需要覆写stylesheetsblock并把你新的样式表标签放到该区块里。当然,由于你只是想把它添加到父块儿的内容中(而不是真的替代它们),所以你需要先用parent()函数来获取基础模板中的所有stylesheets区块中的内容。

你也可以包容位于你bundle的Resources/public文件夹下的assets资源。你需要运行php bin/console assets:install target [–symlink]命令,它会把文件移动到(或symlink到)正确的位置(默认目标位置是“web”文件夹)。

最终结果是,页面中同时包容了main.csscontact.css两个样式表。

引用Request,User或Session对象

Symfony在Twig中给了你一个全局的app变量,可以用于访问当前用户、请求以及更多对象。

参考如何在Twig中通过app变量访问到User, Request, Session和更多对象以了解细节。

输出转义

在渲染任意内容时,Twig自动进行“输出转义(output escaping)”,为的是保护你免受 Cross Site Scripting (XSS)跨站攻击。

假设descriptionI <3 this product

{{ description }}  {{ description|raw }} 

PHP模板不对内容进行自动转义。

更多细节,参考如何对模板输出进行转义。

总结

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

// creates a Response object whose content is the rendered template$response = $this->render('article/index.html.twig'); // creates a Response object whose content is simple text$response = new Response('response content');

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

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