本文實例講述了Symfony2學習筆記之模板用法。分享給大家供大家參考,具體如下:
我們知道,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 Symfony\Component\DependencyInjection\Definition; $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模板,因為它更加簡潔,高效,能更好的處理繼承等。
希望本文所述對大家基於Symfony框架的PHP程序設計有所幫助。