我们知道,controller负责处理每一个进入Symfony2应用程序的请求。实际上,controller把大部分的繁重工作都委托给 了其它地方,以使代码能够被测试和重用。当一个controller需要生成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。 它允许你写简洁,可读法模板语言。对页面设计师更友好,在许多方面比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循环语句等。
{# 这是一个注释 #}, "注释“。
Twig也包含filters,在渲染之前修改内容。下面的语句显示把title变量全部渲染为大型。
{{ title|upper }}
Twig默认情况下有一大群的标签(tags)和过滤器(filters)可以使用。当然你也可以根据需要添加扩展。注册一个Twig扩展非常容 易,创建一个新服务并把它标记为Twig.extension 标签。就跟你看到的一样,Twig也支持功能和新功能的添加。比如,下面使用一个标准的for标签和cycle功能函数来打印10个div 标签,用odd,even 类代替。
{% for i in 0..10 %} <div alss="{{ cycle(['odd','even'],i) }}"> <!--一些其它HTML --> </div> {% emdfor %}
Twig模板缓存
Twig很快。
每个Twig模板被编译到原生的PHP类,它将在运行时被渲染。编译过的类被保存在app/cache/{environment}/twig
目录下并在某些情况下,对整个调试非常有用。当debug模式可用时,一个twig模板如果发生改变将会被自动重新编译。这就意味着你可以在开发过程中随
意的修改模板,而不必担心需要去清除内存了。当debug模式被关闭时,你必须手动的清除Twig缓存目录,以便能够重新生成Twig模板。
模板继承和布局
大多数的时候,模板在项目中用来共享通用的元素,比如
header,footer,sidebar等等。在Symfony2中,我们将采用不同的思考角度来对待这个问题。一个模板可以被另外的模板装饰。这个
的工作原理跟PHP类非常像,模板继承让你可以创建一个基础”layout"模板,它包含你的站点的所有通用元素并被定义成blocks。这里的
block可以类比为PHP基类的方法。 一个字模板可以继承基础layout模板并重写它任何一个block。
现在首先创建一个base layout文件:
Twig:
{# 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>
PHP代码格式:
<!-- app/Resources/views/base.html.php --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php $view['slots']->output('title', 'Test Application') ?></title> </head> <body> <div id="sidebar"> <?php if ($view['slots']->has('sidebar')): ?> <?php $view['slots']->output('sidebar') ?> <?php else: ?> <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> <?php endif; ?> </div> <div id="content"> <?php $view['slots']->output('body') ?> </div> </body> </html>
这个模板定义了基本的HTML初始文档是一个简单的两列式页面。在这个页面中有三处{% block %}定义,分别定义了title,sidebar和body。每个block都可以被继承它的子模板重写或者保留它现在的默认实现。该模板也能被直接渲 染,只不过只是显示基础模板的定义内容。
下面定义一个子模板:
Twig格式:
{# 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 %}
PHP代码格式:
<!-- src/Acme/BlogBundle/Resources/views/Blog/index.html.php --> <?php $view->extend('::base.html.php') ?> <?php $view['slots']->set('title', 'My cool blog posts') ?> <?php $view['slots']->start('body') ?> <?php foreach ($blog_entries as $entry): ?> <h2><?php echo $entry->getTitle() ?></h2> <p><?php echo $entry->getBody() ?></p> <?php endforeach; ?> <?php $view['slots']->stop() ?>
父模板被一个特殊的字符串语法表示 ::base.html.twig ,它表示该模板在项目的 app/Resources/views 目录下。
模板继承的关键字 {% extends %}标签。 该标签告诉模板化引擎首先评估父模板,它会设置布局和定义多个blocks。然后是子模板,上例中父模板中定义的title和body 两个blocks将会被子模板中的定义所取代。依靠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,所以来自父模板的内容被显示出来,而没有被子模板替代。位于父模板中的{% block %}标签是默认值,如果没有被子模板重写覆盖,它将作为默认值使用。
你可以根据你的需要进行多层继承。 Symfony2项目中一般使用一种三层继承模式来组织模板和页面。当我们使用模板继承时,需要注意:
如果在模板中使用{% extends %},那么它必须是模板的第一个标签。
你基础模板中{% block %}越多越好,记住,子模板不必等一父模板中所有的block。你父模板中block定义的越多,你的布局就越灵活。
如果你发现在多个模板中有重复的内容,这可能就意味着你需要为该内容在父模板中定义一个{% block %}。有些时候更好的解决方案可能是把这些内容放到一个新模板中,然后在该模板中include它。
如果你需要从父模板中获取一个block的内容,你可以使用{{ parent() }}函数。
{% block sidebar %} <h3>Table of Contents</h3> ... {{ parent() }} {% endblock %}
模板的命名和存储位置
默认情况下,模板可以被保存到两个位置:
app/Resources/views 目录下,可以存放整个应用程序级的基础模板以及那些重写bundle模板的模板。
path/to/bundle/Resources/views 目录下,每个bundle自己的模板。
Symfony2使用bundle:controller:template 字符串语法表示模板。这可以表示许多不同类型的模板,每种都存放在特定的路径下:
AcmeBlogBundle:Blog:index.html.twig 用于指定一个特定页面的模板。
AcmeBlogBundle 表示bundle,说明模板位于AcmeBlogBundle,比如src/Acme/BlogBundle。
Blog 表示BlogController,指定模板位于Resourcs/views的Blog子目录中,index.html.twig为模板名字。
假设AcmeBlogBundle位于src/Acme/BlogBundle, 最终的路径将是:src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
AcmeBlogBundle::layout.html.twig 该表示法指向AcmeBlogBundle的基模板。没有controller部分,所以模板应该位于AcmeBlogBundle的 Resources/views/layout.html.twig
::base.html.twig 表示一个应用程序级的基模板或者布局文件。注意,该语句两个冒号开头,意味着没有bundle和controller部分,这说明该文件不在某个bundle中,而是位于根目录下 app/Resources/views
在重写Bundle模板一节,你将发现位于AcmeBlogBundle的模板是如何被位于app/Resources/AcmeBlogBundle/views/目录下的所有模板同名重写的,
这种方式给了我们一个有力的途径来重写供应商提供的bundle的模板。
模板后缀(suffix)
bundle:controller:template 句法说明了每个模板文件的存放位置。每个模板名字也有两个扩展名来指定格式和模板引擎。
AcmeBlogBundle:Blog:index.html.twig HTML格式,Twig引擎
AcmeBlogBundle:Blog:index.html.php HTML格式,PHP引擎
AcmeBlogBundle:Blog:index.css.twig CSS格式,Twig引擎
默认情况下,Symfony2的任何模板都可以被写成Twig或者PHP引擎的,它由后缀决定。其中后缀的前一部分(.html,.css)表示最终生成的格式。
标签和助手类
你已经基本了解了模板,它们如何命名如何使用模板继承等。最难理解的部分已经过去。接下来我们将了解大量的可用工具来帮助我们执行最通用的模板任务比如包含另外一个模板,链接一个页面或者包含一个图片等。
Symfony2 中包含了血多序列化的Twig标签和功能函数来帮助设计者更容易的完成工作。在PHP中,模板系统提供了一个可扩展的helper系统,它可以在模板上下 文中提供许多有用的内容。我们已经看过一些内建的Twig标签,比如{% block %}{% extends %}等,还有PHP 助手$view['slots']。
包含其它模板:
你可能经常想在多个不同的页面中包含同一个模板或者代码片段。比如一个应用程序中有“新闻文章”,模板代码在显示一片文章时可能用到文章详细页面,一个现实最流行文章的页面,或者一个最新文章的列表页面等。
当你需要重用一些PHP代码,你通常都是把这些代码放到一个PHP类或者函数中。同样在模板中你也可以这么做。通过把可重用的代码放到一个它自己的模板中,然后把这个模板包含到其他模板中。比如我们创建一个可重用模板如下:
Twig格式:
{# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} <h2>{{ article.title }}</h2> <h3 class="byline">by {{ article.authorName }}</h3> <p> {{ article.body }} </p>
PHP代码格式:
<!-- src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.php --> <h2><?php echo $article->getTitle() ?></h2> <h3 class="byline">by <?php echo $article->getAuthorName() ?></h3> <p> <?php echo $article->getBody() ?> </p>
然后我们把它包含到其它模板定义中:
Twig格式:
{# 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 %}
PHP代码格式:
<!-- src/Acme/ArticleBundle/Resources/Article/list.html.php --> <?php $view->extend('AcmeArticleBundle::layout.html.php') ?> <?php $view['slots']->start('body') ?> <h1>Recent Articles</h1> <?php foreach ($articles as $article): ?> <?php echo $view->render('AcmeArticleBundle:Article:articleDetails.html.php', array('article' => $article)) ?> <?php endforeach; ?> <?php $view['slots']->stop() ?>
模板的包含使用{% include %}标签。模板的名称要使用通用方式。在articleDetails.html.twig模板中使用article变量,这里通过在 list.html.twig模板中使用with命令传入。{'article':article}语法是标准Twig哈希映射的写法。如果我们需要传递 多个元素,可以写成{'foo': foo, 'bar': bar}。
嵌入Controllers
有些情况下,你需要比包含简单模板做到更多。假设你有一个菜单
栏sidebar在你的布局文件中来显示最新的文章。获取三篇最新文章可能需要查询数据库或者执行其它包含很多逻辑的操作,这样就不能在一个模板中进行
了。这种情况的解决方案是简答键入一个完整的controller到你的模板。首先创建一个controller来渲染特定数量的最近文章:
//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模板则相当简单:
Twig格式:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %}
PHP代码格式:
<!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php --> <?php foreach ($articles as $article): ?> <a href="/article/<?php echo $article->getSlug() ?>"> <?php echo $article->getTitle() ?> </a> <?php endforeach; ?>
为了能包含controller,你需要使用一个标准的字符语法来表示controller,格式类似bundle:controller:action
Twig格式:
{# app/Resources/views/base.html.twig #} ... <div id="sidebar"> {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %} </div>
PHP代码格式:
<!-- app/Resources/views/base.html.php --> ... <div id="sidebar"> <?php echo $view['actions']->render('AcmeArticleBundle:Article:recentArticles', array('max' => 3)) ?> </div>
无论什么时候,你需要一个变量或者一些列信息时,你不必在模板中访问,而是考虑渲染一个controller。因为Controller能够更快的执行并且很好的提高了代码的组织和重用。
链接到页面:
在你的应用程序中创建一个链接到其它页面对于一个模板来说是再普通不过的事情
了。我们采用path
Twig函数基于路由配置来生成URL而非在模板中硬编码URL。以后如果你想修改一个特定页面的URL,你只需要改变路由配置即可,模板将自动生成新的
URL。比如我们打算链接到"_welcome"页面,首先定义其路由配置:
YAML格式:
_welcome: pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index }
XML格式:
<route id="_welcome" pattern="/"> <default key="_controller">AcmeDemoBundle:Welcome:index</default> </route>
PHP代码格式:
$collection = new RouteCollection(); $collection->add('_welcome', new Route('/', array( '_controller' => 'AcmeDemoBundle:Welcome:index', ))); return $collection;
我们可以在模板中使用Twig函数 path来引用这个路由链接该页面。
Twig格式:
<a href="{{ path('_welcome') }}">Home</a>
PHP代码格式:
<a href="<?php echo $view['router']->generate('_welcome') ?>">Home</a>
上面的内容会生成一个URL /
我们看另一个复杂一些的路由定义:
YAML格式:
article_show: pattern: /article/{slug} defaults: { _controller: AcmeArticleBundle:Article:show }
XML格式:
<route id="article_show" pattern="/article/{slug}"> <default key="_controller">AcmeArticleBundle:Article:show</default> </route>
PHP代码格式:
$collection = new RouteCollection(); $collection->add('article_show', new Route('/article/{slug}', array( '_controller' => 'AcmeArticleBundle:Article:show', ))); return $collection;
这种情况下你需要指定路由名称(article_show)还要一个参数值{slug}。现在让我们再来看上面的recentList模板 ,我们修改以前的硬编码而使用path来链接正确的文章。
Twig格式:
{# 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 %}
PHP代码格式:
<!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php --> <?php foreach ($articles in $article): ?> <a href="<?php echo $view['router']->generate('article_show', array('slug' => $article->getSlug()) ?>"> <?php echo $article->getTitle() ?> </a> <?php endforeach; ?>
你也可以通过url Twig函数来生成一个绝对路径的URL:
<a href="{{ url('_welcome') }}">Home</a>
在PHP代码模板中,你需要给generate()方法传入第三个参数true 来实现生成绝对路径URL
<a href="<?php echo $view['router']->generate('_welcome', array(), true) ?>">Home</a>
链接到资源
模板通常也需要一些图片,Javascript,样式文件和其它资产。当然你可以硬编码它们的路径。比如/images/logo.png。 但是Symfony2 提供了一个更加动态的Twig函数 asset()。
Twig格式:
<img src="{{ asset('images/logo.png') }}" alt="Symfony!" /> <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
PHP代码格式:
<img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" alt="Symfony!" /> <link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" type="text/css" />
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 。
包含样式表和Javascript文件到Twig模板:
每个网站中都不可能缺少了样式表和
javascript文件。在Symfony中,这些内容可以通过模板继承很好的进行包含处理。Symfony打包了另外一个类库叫做Assetic,
它允许你对这些资源做更多的有趣操作。首先在你的基模板中添加两个blocks来保存你的资源,一个叫stylesheets,放在head标签里,另一
个叫javascript,放在body结束标签上面一行。这些blocks将包含你整个站点所需要的所有stylesheets和
javascripts。
{# 'app/Resources/views/base.html.twig' #} <html> <head> {# ... #} {% block stylesheets %} <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} </head> <body> {# ... #} {% block javascripts %} <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script> {% endblock %} </body> </html>
这太简单了!但如果你想从子模板中包含一个额外的stylesheet或者javascript怎么办呢?比如假设你有一个联系页面需要包含一个contact.css样式表只用于该页面。在你的contact页面模板内部,你可以这样实现:
{# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} {% extends '::base.html.twig' %} {% block stylesheets %} {{ parent() }} <link href="{{ asset('/css/contact.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} {# ... #}
在子模板中你只需要重写stylesheets block并把你新的样式表标签放到该块里。当然因为你想添加到父模板该块内容中,而不是替代它们所以你需要在此之前
先使用parent()函数来获取父模板中的所有stylesheets。你也可以包含资源位置到你的bundle的Resources/public 文件夹。你需要执行如下命令行:
$php app/console assets:install target [--symlink]
它会把文件移动到正确的位置,默认情况下的目标位置是web文件夹。
<link href="{{ asset('bundles/acmedemo/css/contact.css') }}" type="text/css" rel="stylesheet" />
上面代码最终结果是页面会包含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则不是。
当然,你也可以向其添加你自己的全局模板变量。其应用示例:
Twig格式:
<p>Username: {{ app.user.username }}</p> {% if app.debug %} <p>Request method: {{ app.request.method }}</p> <p>Application Environment: {{ app.environment }}</p> {% endif %}
PHP代码格式:
<p>Username: <?php echo $app->getUser()->getUsername() ?></p> <?php if ($app->getDebug()): ?> <p>Request method: <?php echo $app->getRequest()->getMethod() ?></p> <p>Application Environment: <?php echo $app->getEnvironment() ?></p> <?php endif; ?>
配置和使用templating 服务
Symfony2模板系统的核心是模板化引擎。这个特殊的对象负责渲染模板并返回他们正确的内容。当你在controller中渲染一个模板时,其实你是使用了模板化引擎服务,比如:
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内部是预先配置好自动工作的。当然它也可以在应用程序的配置文件中进行配置。比如:
YAML格式:
# app/config/config.yml framework: # ... templating: { engines: ['twig'] }
XML格式:
<!-- app/config/config.xml --> <framework:templating> <framework:engine id="twig" /> </framework:templating>
PHP代码格式:
// app/config/config.php $container->loadFromExtension('framework', array( // ... 'templating' => array( 'engines' => array('twig'), ), ));
重写Bundle模板:
Symfony2社群现在正为他们创建和维护了很多不同内容的高质量的
Bundle而感到骄傲,一旦你使用第三方的bundle你可能想重写或者个性化它们的一个或者多个模板。假设你已经包含了开源
AcmeBlogBundle到你的项目中,比如目录是src/Acme/BlogBundle,你想重写blog列表(list)页面来按照你自己的应
用程序风格个性化它。我们打开AcmeBlogBundle的Blog controller,可以发现如下内容:
public function indexAction() { $blogs = // some logic to retrieve the blogs $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs)); }
当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目录不存在,可以手工建立。之后你就可以随心所欲的个性化处理该模板了。
该逻辑同样适用于基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内部重写模版。
重写核心模板:
因为Symfony2
框架本身就是一个bundle,所以其核心模板也可以按照同样的方式进行重写。比如,核心模板TwigBundle包含很多不同”execption"和
“error" 的模板,你可以通过从Resources/views/Exception
目录下每一项到app/Resources/TwigBundle/Views/Exception 目录下 。
三级继承:
Symfony2中一般采用三级继承来完成模板创建。它使用三种不同类型的模板:
首先,创建一个app/Resources/views/base.html.twig 文件,它包含你应用程序主要的布局,该模板被调用时写成 ::base.html.twig。
然后,为你站点的每个部分section创建一个模板。比如一个AcmeBlogBundle, 它有自己的一个模板AcmeBlogBundle::layout.html.twig。该模板只包含特定的元素,比如body。
{# src/Acme/BlogBundle/Resources/views/layout.html.twig #} {% extends '::base.html.twig' %} {% block body %} <h1>Blog Application</h1> {% block content %}{% endblock %} {% endblock %}
最后,是为每个页面创建一个单独的模板并继承合适的section模板。比如,为index页创建的模板 AcmeBlogBundle:Blog:index.html.twig 用来列出所有的blog。
{# 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 %}
注意该模板是继承了section模板(AcmeBlogBundle:layout.html.twig),而section模板又继承了应用程序基模板(::base.html.twig)。这就是通常说的三级继承模式。
在你创建你的应用程序时,你可以选择这种模式,也可以为每个页面创建模板时直接继承应用程序的基模板(比如: {% extends '::base.html.twig' %} 。
三模板模式对于一个bundle开发商来说是最好的方法,因为这样bundle使用者可以很容易的重写bundle的基模板来适合自己应用程序的基本布局。
安全输出转换:
当我们从模板中生成HTML时,总会有风险存在,比如一些模板变量可能输出一些意外的HTML或者危险的客户端代码。结果这些动态内容会打破结果页面的HTML或者允许一些恶意的访问者执行一些页面攻击。举个例子:
Twig格式:
Hello {{ name }}
PHP代码格式:
Hello <?php echo $name ?>
想象一下用户输入下面的代码作为他们的name时,结果会是什么?
<script>alert('hello!')</script>
没有任何的输出安全转义,结果模板会触发JavaScript弹出框:
Hello <script>alert('hello!')</script>
这种情况看上去,没多大害处,如果一个用户知道这一步,它完全有能力写一个javascript在我们未知的安全区域来执行一些恶意行为。这个 问题的解决办法是输出安全转义。 如果添加了输出安全转义,同样的模板会渲染出无害的内容,script标签会作为普通文本输出到屏幕上。
Hello <script>alert('helloe')</script>
PHP和Twig模板化系统采用了不同的方式来解决这个问题。如果你使用Twig,默认情况下是输出安全转义的,你的输出是受到保护的。如果是PHP,则输出安全转义不是自动的,需要你手工的进行处理。
Twig中的安全输出
如果你使用Twig模板,那么输出安全是默认的。你不需要对用户提交的输出内
容进行手动保护。在某些情况下,你需要关闭输出安全保护,当你渲染某个可信变量或者包含某个标签时。假设管理员用户可以编写一些代码有HTML标签的文
章。默认情况下,Twig将转义文章体。为了渲染正常,需要添加一个raw 过滤器:
{{article.body | raw }}
你也可以在{% block %}区域或者整个模板中关闭安全保护。
PHP中的安全输出保护:
在PHP中安全输出保护不是自动完成的,这就意味着除非你显示的选择来对某个输出变量进行保护,否则输出都是不安全的。我们使用view的方法escape()来对输出变量进行安全输出保护:
Hello <?php echo $view->escape($name) ?>
默认情况下,escape()方法默认情况下假设变量是被渲染在一个HTML上下文中的。也就是说只针对HTML安全。 它的第二个参数让你能够改变它针对的上下文。
比如需要在javascript上下文中输出某些东西,就是用 js 上下文。
var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';
模板调试
当使用php代码时,我们可以使用var_dump()来快速的查看一个值的变量传递。同样在Twig中,我们可以使用debug扩展来达到相同的效果,只是需要预先在配置文件中开启。
YAML 格式:
# app/config/config.yml services: acme_hello.twig.extension.debug: class: Twig_Extension_Debug tags: - { name: 'twig.extension' }
XML格式:
<!-- app/config/config.xml --> <services> <service id="acme_hello.twig.extension.debug" class="Twig_Extension_Debug"> <tag name="twig.extension" /> </service> </services>
PHP代码格式:
// app/config/config.php use SymfonyComponentDependencyInjectionDefinition; $definition = new Definition('Twig_Extension_Debug'); $definition->addTag('twig.extension'); $container->setDefinition('acme_hello.twig.extension.debug', $definition);
这时候,我们就可以使用dump函数来查看模板参数了:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {{ dump(articles) }} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %}
模板格式:
模板是一个渲染任何格式内容的通用的方式。大多数情况下,我们使用模板来渲染
HTML内容。模板同样也能渲染想javascript,CSS,XML以及你能想象到的其它格式内容。比如,同一个资源resource经常被渲染为不
同的格式。把文章目录页渲染为XML,你需要在模板的名称中包含相应的格式即可。
XML 模板名: AcmeArticleBundle:Article:index.xml.twig
XML 模板文件名:index.xml.twig
事实上,这里只是命名上有了变化,而模板并没有真正的基于不同的格式渲染不同。在大多数情况下,你可能想让单一的controller根据请求的格式不同渲染多个不同的格式。下面是一个通常的写法:
public function indexAction() { $format = $this->getRequest()->getRequestFormat(); return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); }
Request对象的getRequestFormat()方法默认返回值为html, request的格式通常是在路由时决定的。比如/contact 设置的请求格式是html,而 /contact.xml 设置的请求格式则是 XML。创建一个包含请求格式的链接,只需要在参数哈希表中包含 _format键值即可。
Twig格式:
<a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}"> PDF Version </a>
PHP代码格式:
<a href="<?php echo $view['router']->generate('article_show', array('id' => 123, '_format' => 'pdf')) ?>"> PDF Version </a>
总结思考:
Symfony2中的模板引擎是一个强大的工具,你可以用它来根据需要生成包括
HTML,XML以及其它任何格式的内容。尽管模板时controller生成内容的通常方式,但是不是必须的。controller返回的
Response对象可以使用模板也可以没有模板。
// 使用模板生成Response对象 $response = $this->render('AcmeArticleBundle:Article:index.html.twig'); // 使用简单文本内容生成Response对象 $response = new Response('response content');
Symfony2的模板引起非诚灵活,默认情况下支持传统的PHP模板和圆滑强大的Twig模板,他们都拥有非常丰富的帮助函数来执行一些常见任务,Symfony2推荐使用Twig模板,因为它更加简洁,高效,能更好的处理继承等。