# 模板
模板屬于MVC結構中的V。它的出現是為了更好地分離程序代碼(對用戶不可見)HTML呈現(用戶可見)。
對模板開發者的要求是能掌握一些基本的模板語法,寫出基本框架,加入對應的控制和輸出,最終形成一個可以呈現給用戶的頁面。
## SF模板引擎:Twig
SF的創始人Fabien Potencier研制了一個全新的模板引擎:[Twig](http://twig.sensiolabs.org/)?。我很喜歡這個引擎。我個人認為它有這么幾個優點:
1. 語法簡單,上手快;
2. 支持嵌套、繼承等模板高級特性;
3. 擴展性好,可以開發Twig插件擴充功能;
4. 編譯速度快,效率高。
Twig是SF框架中的一個部件,也可以獨立被其它框架使用。在SF應用創建后,已經支持Twig。
## 頁面結構的設計
我們的應用不會只有一個頁面,但是眾多頁面卻可能有著相同的布局。比如,都采用“頂部導航欄+中間內容+底部導航欄”的上中下布局,或者采用“左導航欄+中間內容+底部導航欄”的布局,等等等等。
我一般會采用基于[Bootstrap](http://getbootstrap.com/)的模板加以定制。我現在[任氏有無軒](https://rsywx.net/)?使用的是從[WrapBootstrap](https://wrapbootstrap.com/)?上購買的一個商業模板。在本應用開發中,我們會用一個免費的Bootstrap模板,[Jumbotron](http://getbootstrap.com/examples/jumbotron/):

這個頁面布局很合適我們的應用。
1. 頂部的黑色導航區可以放應用名稱,導航鏈接,右邊的用戶登錄部分可以用搜索框來代替;
2. 下面的Jumbotron部分可以用在某些頁面中作為強調部分;
3. 主內容區三個并排的列可以在首頁中顯示不同欄目的內容,也很方便變成一個列用在別的頁面中;
4. 最下面的一塊地方正好可以用來顯示一些版權信息之類的。
為了在我們的應用中使用這個布局,我們需要做一些額外的工作。
## 保存Bootstrap模板
我們首先要將Jumbotron這個頁面保存下來。在瀏覽器中選擇保存頁面。如果有選擇保存方式的話,選擇“全部”。這樣我們保存的就不僅是該頁面的HTML文件,而且包括該HTML頁面中引用到的CSS,JS和圖片文件等。
在我們SF項目的`web`目錄下創建幾個目錄,如`css`,`js`,`img`,將保存下來的頁面引用到的CSS,JS和圖片文件分別拷貝到對應的目錄中去。
接著,我們將剛才保存的那個HTML文件移到`src/AppBundle/Resources/views/default`并改名為`layout.html.twig`。
## 修改首頁顯示該模板
下一步,我們修改當前首頁所渲染的模板,使其顯示我們剛才創建的模板,這樣方便我們進一步修改該模板并看到效果。
讓我們修改`src/AppBundle/Controller/DefaultController.php`中的`indexAction`為:
~~~
public function indexAction(Request $request)
{
// replace this example code with whatever you need
return $this->render('AppBundle:default:layout.html.twig');
}
~~~
刷新我們的應用頁面,可以看到Bootstrap的頁面已經替換了原來的SF歡迎頁面。但是由于該頁面中引用的CSS、JS文件的相對路徑已經發生變化,所以需要進行調整。具體怎樣調整這里就不再贅述,原則上只是一些文件路徑和文件名的調整罷了。
如果一切順利,我們應該看到和之前在Bootstrap上一樣的頁面效果。
## 模板的繼承和包含
Twig模板引擎的一個強大之處在于它支持模板的繼承和包含,而且還可以嵌入控制器[1](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.07%20template.html#fn_1)。這為我們定義靈活的頁面布局并同時保持一致性提供了方便。
### 包含
包含是include,它并不是Twig的重點,所以我們只是提一下。它用來在一個模板中導入另一個模板。通常情形下,那些相對固定且在多個頁面中都會重復出現的內容比較適合被剝離到一個單獨的模板文件中。在需要這些內容的地方加以導入即可。
考慮我們使用的Bootstrap樣板,我們會很快地發現,頂部導航欄和底部的導航欄都具有這樣的性質:相對固定、作為頁眉和頁腳也會在多個頁面重復出現。所以,這兩部分可以被提取出去作為一個獨立的模板供其它模板使用。
### 繼承
Twig的強大之處在于模板的繼承。
學習過OOP的一定知道類繼承是怎樣的機制。簡單地說,子類繼承了父類中所有公共特性和公共方法,同時也可以按照子類的特殊要求加入新的特性和方法。
Twig模板的繼承與此也有類似之處。不過由于我們討論的是頁面布局,所以這里的繼承也是對頁面布局的繼承。
根據上面對Bootstrap樣板的分析,我們可以看到,以頁面布局來看,我們的頁面基本上有三個部分:
1. 頂部導航欄,我們不妨稱之為"`nav`[2](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.07%20template.html#fn_2)"。
2. 中間的內容部分,我們不妨稱之為"`content`"。
3. 底部的信息欄,我們不妨稱之為"`footer`"。
這三部分中,1/3我們已經討論過了,我們需要將它們獨立出去,成為兩個獨立的模板供包含。
2這部分是非常動態的,各個不同頁面要顯示的主要內容肯定不同。所以,它也必須獨立出去,由各個頁面各自完成。
于是我們可以對主模板進行如下圖所示的調整:

簡要說明一下:
1. 原本的`layout`模板被拆分為四塊:`header`,`nav`,`index`,`footer`。
2. `index`模板成為我們首頁最終使用的模板,它將繼承`layout`模板,并用屬于該頁面的內容覆蓋`content`頁面組塊。
3. 其它三個模板分別在`layout`模板中被引用。
隨著應用的開發,我們會創建更多的頁面。但是,其創建方式和創建`index`頁面的方式類似。
## Twig模板中的`block`
可以這么說,block(塊)是Twig模板構造頁面的基礎。
在`layout`模板中,我們會注意到這么一段:
~~~
<html lang="zh">
{% include "AppBundle:default:header.html.twig" %}
<body>
{% include "AppBundle:default:nav.html.twig" %}
{% block content %}{% endblock %}
{% include "AppBundle:default:footer.html.twig" %}
</body>
</html>
~~~
`{% block content %}{% endblock %}`在`layout`模板中定義了一個名為`content`的塊。我們注意到,`layout`中這個塊中沒有任何內容。它的內容是由各個具體的頁面填充的,比如在`index`模板中:
~~~
{% extends "AppBundle:default:layout.html.twig" %}
{% block content %}
<div class="jumbotron">
... ...
</div> <!-- /container -->
{% endblock %}
~~~
首先,`index`模板聲明它會擴展(也就是繼承自)`layout.html.twig`模板。因此,所有在`layout`模板中出現的布局因素(包括`layout`需要引用的其它模板中的布局因素)也都會在`index`中出現。
但是,`index`模板重寫了`content`塊,替換成自己的內容。而`index`模板中未覆蓋的那些塊,會仍然采用`layout`中的相應內容。
這樣的一個過程,正是繼承的過程。
一般而言,每當我們要創建一個新的頁面,我們會采用類似的方式:
1. 從主模板(本例中的`layout`)繼承;
2. 重寫相應的塊。一般而言,也就是一個諸如名為`content`的塊;
3. 根據需要,重寫另外的一些塊,比如`title`,`extralink`,`extrajs`等以達到高度定制的目的。
## Twig模板的存放位置
缺省情況下,所有的Twig模板文件都應該存放在`src/AppBundle/Resources/views`之下。為了更方便的組織模板文件,我們可以在這個目錄之下再創建一些子目錄。
引用一個模板的時候,無論是在模板之中還是在控制器之中,方法都是類似“`AppBundle:目錄:模板`”的格式。比如,`AppBundle:default:nav.html.twig`對應的模板文件是`src/AppBundle/Resources/views/default/nav.html.twig`文件。
更深層的路徑也是支持的,比如`AppBundle:Theme1/default:index.html.twig`對應的模板文件是`src/AppBundle/Resources/views/Theme1/default/index.html.twig`。這里的關鍵是,“`目錄`”部分可以被替換成更完整的路徑層次,但是我們不能修改`AppBundle`和最終調用的模板文件的名字。
> 1. 在模板中嵌入控制器是高級用法,但是其語法卻非常簡明。我們在后續章節會看到詳細講解。[??](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.07%20template.html#reffn_1 "Jump back to footnote [1] in the text.")
> 2. 我們沒有把它叫做`header`是因為我們還會有一個`header`的模板,用來統一管理HTML頁面中的頭信息(如`meta`,CSS/JS的引用等)。[??](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.07%20template.html#reffn_2 "Jump back to footnote [2] in the text.")
- 引言
- 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 結語