# 模板
SF2缺省使用TWig引擎作為模板的渲染。
一般的應用,都會由不止一個頁面構成。
比如我的藏書管理程序包括首頁,藏書列表,書評列表和某本書的詳情頁面,也包括“和我聯絡”這樣的頁面。這些頁面有些是動態頁面,有些是靜態頁面。但是它們都有類似的布局。我采用的是最常見的“頭+主體+尾”的三段式布局。如下兩個頁面分別是藏書列表和書籍詳情頁面:


很明顯,頭部的內容基本保持不變(站點標識,導航條),尾部的內容頁基本不變(版權申明等)。變化的是中間主體的內容。如果是藏書列表,就應該是分頁顯示的書籍列表信息;如果是書籍詳情,就顯示某本書更詳細的信息。
針對這樣的布局,在模板設計時我們應該遵循DRY原則,將相似的部分抽取出來成為一個獨立的部分并使得這些部分可以在不同的頁面中重用。
Twig提供了豐富的功能來完成這些工作。
## `include`一個子模板
在我們剛才討論的頁面布局中,頭(`header`)和尾(`footer`)是相對獨立和固定的內容,所以可以作為獨立的模板存在,并被包含入主模板。
因此,主模板(`layout.html.twig`)的書寫大致是這樣的:
~~~
<body>
<!-- Header Section -->
{% include "AppBundle:default:header.html.twig" %}
{% block content %}
{% endblock %}
{% include "AppBundle:default:footer.html.twig" %}
</body>
~~~
是的,就這么簡單。特別請注意`block content`到`endblock`這兩行。這兩行定義了一個名字為`content`的內容塊,但是沒有任何內容。這部分內容要由基于該主模板派生的各個模板去填充。
`include`模板還可以傳遞參數。比如下面的這個例子:
~~~
{{ include ("AppBundle:default:slider.html.twig", {'random':random}) }}
~~~
這個指令在包含`slider.html.twig`模板的時候,也會同時將本模板中`random`這個變量傳遞到被包含的這個模板中的`random`變量,從而達到變量在兩個模板中傳遞的目的。
`include`嚴格來說,是個控制結構,但是我們在上面的代碼中看到了兩種用法都是可行的。
## `extends`一個模板
上文定義的`layout`模板,只是一個框架,一般不會在我們的應用中單獨呈現。我們要呈現給用戶的頁面都是基于這個模板派生而來。
我們來看一個比較簡單的書籍列表的頁面模板:
~~~
{% extends 'AppBundle:default:layout.html.twig' %}
{% block meta %}
<meta name="description" content="任氏有無軒書籍列表,第{{cur}}頁,總{{total}}頁。">
<meta name="keyword" content="任氏有無軒,列表,索引">
{% endblock %}
{% block title %}任氏有無軒 | 藏書列表 | 第{{cur}}頁,總{{total}}頁{% endblock %}
{% block content %}
<div class="widewrapper">
<div class="container content">
<div class="row">
......
</div>
<div class="row">
<section id="data" class="col-md-12">
<table class="table table-striped table-hover">
......
</table>
</section>
<section id="pagination" class="col-md-12">
......
</section>
</div>
</div>
</div>
{% endblock %}
~~~
最關鍵的是要`extends`一個主模板。然后在本模板中,針對主模板中定義的不同塊(`block`),如果需要加以覆蓋的,就用:`{% block blockname %}...{% endblock %}`的語法加以重寫。如果我們不覆蓋主模板中的某個塊,那么在本模板中會顯示主模板中該快的內容。
## 在模板中嵌入一個控制器
模板的第三種用法,也是比較高級的用法是在模板中嵌入一個控制器。
我們考慮我的藏書管理程序首頁的一個用例。在該頁面中,我需要在一個幻燈片效果中顯示一本隨機挑選的書,在另一處`<div>`中顯示三本隨機挑選的書。
一種常規的做法是,在顯示首頁的控制器(`AppBundle:Default:indexAction`)中,通過調用相關的倉庫方法獲得這些書,然后通過變量傳遞的方式傳遞給相應的模板,并在模板中顯示。這樣做是可以達到目的的。
但是這么做可能有問題。如果這樣的顯示要求是多個頁面都要求的,我們會面臨在A控制器中調用倉庫方法然后獲得數據傳遞給a模板;在B控制器中調用同一個倉庫方法獲得數據然后傳遞給b模板……的過程。這里有重復的過程:都要調用一個倉庫方法。而且,假定這一方法調用后返回數據的呈現在不同模板中也是一樣的,重復性會更大:我們需要在各個模板中`include`一個子模板,然后傳遞給這個子模板以數據。這個過程更繁瑣。
因此,比較推薦的方法是,在模板中內嵌一個控制器,這個控制器負責獲取數據并渲染一個模板,而該模板的渲染內容將嵌入當前調用模板中。
我們來看一個實例,是用來顯示與一本書相關的tag的。
首先是當前模板(`detail.html.twig`)中對控制器的嵌入:
~~~
<div class="text">
<h3>TAG</h3>
<small>{% render (controller('AppBundle:Book:tagsbyid', {"id":book.id})) %}</small>
<a class="btn btn-info btn-sm" data-toggle="modal" href="#addtag" >增加更多TAG ?</a><br/>
</div>
~~~
然后是顯示某本書的tag的控制器:
~~~
public function tagsbyidAction($id)
{
$em = $this->getDoctrine()->getManager();
$tags = $em->getRepository('AppBundle:BookBook')->getTagsByBookId($id);
return $this->render('AppBundle:book:tags.html.twig', array('tags' => $tags));
}
~~~
這里通過調用倉庫的方法(`getTagsByBookId`),獲得與這本書關聯的tag數據,然后再通過`tags.html.twig`模板顯示:
~~~
{% for tag in tags %}
<a href="{{path('book_list', {'type':'tag', 'page':1, 'key':tag.tag})}}">{{tag.tag}}</a>
{% endfor %}
~~~
這樣的安排是非常去耦合化的,便于程序的開發和維護。
## 小結
由于Twig的語法相對簡單,所以我們沒有在之前的[Twig介紹](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/02.03%20twig.html)和本節的介紹中過多著眼其語法的介紹——這部分會在后面的開發過程中結合實踐逐一介紹——而更多地介紹Twig模板的設計過程和一些重要的概念。
我們以上討論的內容可以用如下這張的圖來說明:

- 引言
- 1 LAMP
- 1.1 安裝虛擬機
- 1.2 安裝Vagrant
- 1.3 安裝Ubuntu
- 1.4 安裝Apache 2
- 1.5 安裝PHP
- 1.6 安裝MySQL服務器
- 1.7 最后的微調
- 1.8 設置一個虛擬主機
- 1.9 一個趁手的IDE
- 2 Symfony 3和重要構件
- 2.1 Symfony 3
- 2.2 Doctrine
- 2.3 Twig
- 2.4 Composer
- 3 Symfony重要概念
- 3.1 MVC
- 3.2 Bundle/包
- 3.3 Route/路由
- 3.4 Controller/控制器
- 3.5 Entity/實體
- 3.6 Repository/倉庫
- 3.7 Template/模板
- 3.8 Test/測試
- 4 藏書管理程序的結構
- 5 創建應用
- 5.1 建立版本管理
- 5.2 建立數據庫
- 5.3 應用結構
- 5.4 建立數據庫實體
- 5.5 樣本數據
- 5.6 路由
- 5.7 模板
- 5.8 開始編寫首頁
- 5.9 書籍詳情頁面
- 5.10 書籍列表頁面
- 5.11 書籍搜索
- 6 用戶和后臺
- 7 結語