# 首頁的編寫
由于本站點采用了新的架構(即不再應用中包含數據的CRUD操作,而改為調用對應的RESTful API的方式),因此站點編寫工作量大大降低。
SF基于MVC架構,所以編程部分在C部分,也就是控制器(Controller)部分。
一個常規控制器的流程如下:

我們之所以首先用首頁來進行控制器編寫的說明,主要是因為它是一個站點的入口,具有特別重要的地位,而且它往往獨一無二與別的頁面不同(別的頁面如書籍詳情等會重復)。另外,首頁中也用到控制器嵌入等重要技術,值得首先加以描述。
## 頂端項目欄和搜索框
### 創建一個新的路由
首頁的頂端我們要設計成項目名稱和搜索框。修改的是`AppBundle:default:nav.html.twig`文件,并最終呈現在`AppBundle:default:index.html.twig`的渲染效果中。
既然是搜索欄,我們要為搜索這個動作設定一個路由。出于簡化編程和示范的目的,我們約定搜索只搜索書籍。我們修改`src/AppBundle/Resources/config/routing.yml`并增加一個新的路由如下:
~~~
books_search:
path: /books/search
defaults: {_controller: AppBundle:Book:search}
requirements:
_method: POST
~~~
### 創建一個新的控制器
從路由的設置我們看到,我們同時要創建一個新的控制器(`BookController`)并在其中創建一個搜索的方法(`searchAction`)來處理這個工作。
創建新的控制器很簡單,我們可以簡單地從`DefaultController.php`出發,將其拷貝黏貼為`BookController.php`,并作相應修改:
~~~
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class BookController extends Controller
{
/**
* Test changes in Linux
*/
public function searchAction(Request $request)
{
// replace this example code with whatever you need
dump($request);
die();
}
}
~~~
簡單說明如下:
1. 這個控制器的代碼并未完成,我們只是將通過表單提交的數據簡單地加以顯示而已。該方法的具體實現需要借助于別的尚未完成的模板,所以我們這里進行了簡單處理。
### 修改`nav.html.twig`
至此,我們可以修改`nav.html.twig`文件如下:
~~~
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{path('home')}}">任氏有無軒</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right" action="{{path('books_search')}}" method="POST">
<div class="form-group">
<input type="text" placeholder="書籍名稱" class="form-control" name="key">
</div>
<button type="submit" class="btn btn-success">搜索</button>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
~~~
簡單說明如下:
1. 我們修改了項目名稱為“任氏有無軒”。
2. 我們為表單提供了`action`和`method`參數。
3. 我們使用了Twig專用的輔助函數`path`來為制定表單指定動作。它指向我們在上一步定義的`books_search`路由。
4. `method`只是簡單的定義為POST。
5. 我們為一個文本輸入框定義了一個名字為`key`。
我們刷新一下頁面,會看到如下效果:

而如果我們在“書籍名稱”框中輸入一些文字并點擊“搜索”后會出現如下界面(部分分支已展開):

可見,`VarDump`包中提供的`dump`函數能比PHP內置的`var_dump`函數更有組織地、更靈活地顯示一個變量的值。從圖中也能看到,我們通過表單提交的變量(`key`)獲得了正確的賦值。
## Jumbotron的編寫
頂端項目欄和搜索欄之下是所謂的Jumbotron部分。這是首頁特有的內容。在本應用中,我們用來顯示最近購買的一本書。
要顯示最近購買的一本書,有兩種做法。一種是在所謂的`Default:indexAction`中直接獲取最新的一本書,并將代表這本書的對象直接傳遞給模板;另外一種是在模板中嵌入一個控制器,讓控制器獲取相應的最新圖書并顯示。在Jumbotron的編寫中我們采用第二種方式。
我們首先編寫控制器如下:
~~~
public function latestAction()
{
$b= json_decode(file_get_contents('http://api/book/latestBook'));
return $this->render("AppBundle:book:latest.html.twig", ['book' => $b->out[0]]);
}
~~~
由于我們采用了API調用,這個控制器可以寫得非常簡單。
### `latest.html.twig`模板
我們可以簡單地將當前`index.html.twig`模板中Jumbotron的`<div>...</div>`部分提取出來,成為`latest.html.twig`模板的基礎,并進行一些修改。此時,我們從控制器的編寫中可以看到,該模板會使用到一個`book`變量。
~~~
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-8">
<h1>{{book.title}}</h1>
<p>作者:{{book.author}}({{book.region}})</p>
<p>收錄時間:{{book.purchdate|date('Y年m月d日')}}</p>
<p>版次:{{book.ver}}</p>
<p><a class="btn btn-primary btn-lg" href="{{path('book_detail', {'id':book.bookid})}}" role="button">瀏覽本書 »</a></p>
</div>
<div class="col-md-4">
<img src="/covers/default.jpg" title="{{book.author}} - {{book.title}}"/>
</div>
</div>
</div>
</div>
~~~
注意如下幾點:
1. 我們這里用Bootstrap布局來安排我們的書籍信息部分和書籍封面部分,左右分列。
2. 所有的書籍信息的顯示都來自從控制器傳遞過來的`book`變量。
3. 我們再次用到`path`函數來構造瀏覽書籍詳情的連接。這里我們還為該路徑提供了一個參數,請讀者必須注意參數傳遞的方式:`{'id':book.bookid}`。
4. 暫時我們用一個缺省的書籍封面作為所有書籍的封面。在后續章節中,我們還會進一步編寫一個方法來顯示書籍封面。
我們簡化了Jumbotron的編寫。在我自己開發的[rsywx.net](https://rsywx.net/)站點中,
### `index.html.twig`的修改
最后,我們修改`index.html.twig`,用嵌入控制器的方式替代原來的Jumbotron的`<div>...</div>`部分:
~~~
{% block content %}
{{ render (controller('AppBundle:Book:latest')) }}
<div class="container">
... ...
~~~
此處我們用到Twig的一個高級語法`render`,它用來嵌入一個從控制器的返回。
### 效果
讓我們保存所有修改,然后重新刷新首頁頁面:

由于我們對“最新登錄書籍”的定義是按照其ID,所以我們會看到之前在樣本數據填充中最后生成的書籍記錄會被選出。
## Headline的編寫
Jumbotron之下是所謂的Headline。原布局中由3個`col-md-4`構成,在我們的應用中,會改成4個,它們分別是:
1. 藏書統計信息;
2. 書評統計信息;
3. 博客最新文章[1](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#fn_1);
4. 維客的鏈接和其它鏈接[2](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#fn_2);
這四個小板塊的渲染會采用嵌入控制器和變量直接傳遞的方式。
### `index.html.twig`的進一步改寫
我們進一步改寫`index.html.twig`文件,將原先三個`<div>`的部分抽取出來成為`AppBundle:book:summary.html.twig`和`AppBundle:reading:summary.html.twig`兩個模板。
~~~
<!-- book:summary.html.twig -->
<p>截止{{'today'|date('Y年m月d日')}},任氏有無軒藏書{{summary.summary.0.bc|number_format(0,'.',',')}}本。約{{summary.summary.0.wc|number_format(0,'.',',')}}千字,{{summary.summary.0.pc|number_format(0,'.',',')}}頁。</p>
<p>
最近({{summary.last.0.purchdate|date('Y年m月d日')}})收藏/整理的書籍是<strong>{{summary.last.0.author}}</strong>的<strong><a href="{{path('book_detail', {'id':summary.last.0.bookid})}}">《{{summary.last.0.title}}》</a></strong>。</p>
~~~
~~~
<!-- reading:summary.html.twig -->
<p>截至{{"now"|date('Y年m月d日')}},任氏有無軒主人撰寫了{{rs.summary}}篇評論。</p>
<p>最近({{rs.last.datein|date('Y年m月d日')}})評論的書籍是<strong><a href="{{path("book_detail", {'id': rs.book.bookid})}}">《{{rs.book.title}}》</a></strong>,題為<strong>“<a href="{{rs.last.URI}}">{{rs.last.title}}</a>”</strong>。</p>
~~~
對`index.html.twig`改寫如下:
~~~
{{ render (controller('AppBundle:Book:latest')) }}
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-3 feature">
<h3><a href="{{path("book_list")}}"><i class="glyphicons book"></i>藏書</a></h3>
{{ render (controller('AppBundle:Book:summary')) }}
</div>
<div class="col-md-3 feature">
<h3><a href="{{path('reading_list')}}"><i class="glyphicons tags"></i>讀書</a></h3>
{{ render (controller('AppBundle:Reading:summary')) }}
</div>
<div class="col-md-3 feature">
<h3><a href="http://www.rsywx.net/wordpress"><i class="glyphicons pen"></i>博客</a></h3>
<p>本博客自2003年開始設立。寫的少不是因為我不思考。我思考的越多,寫下來的就越少。</p>
<p>最近的文章發布于{{wp.post_date|date('Y年m月d日H時i分')}},題為“<strong><a href="https://rsywx.net/wordpress/{{wp.post_date|date('Y/m/d')}}/{{wp.post_name}}">{{wp.post_title}}</a></strong>”。 </p>
</div>
<div class="col-md-3 feature">
<h3><a href="http://www.rsywx.com"><i class="glyphicons notes_2"></i>維客</a></h3>
<p>這里是我整理的一些資源,主要是電子書和我最喜愛的<strong><a href="{{path ('lakers', {'year':2015})}}">湖人隊的賽程</a></strong>。</p>
<p> 還可以和我<strong><a href="{{path('contact')}}">取得聯系</a></strong>。</p>
</div>
</div> <!-- /row -->
</div> <!-- /container -->
~~~
### 編寫對應的控制器
我們需要在`BookController`和`ReadingController`中增加對應的控制器方法分別響應上述模板中對控制器的調用:
~~~
// BookController.php
public function summaryAction()
{
$summary = json_decode(file_get_contents('http://api/book/summary'))->out;
return $this->render("AppBundle:book:summary.html.twig", ['summary' => $summary]);
}
// ReadingController.php
public function summaryAction()
{
$summary = json_decode(file_get_contents('http://api/reading/summary'));
return $this->render("AppBundle:reading:summary.html.twig", ['rs' => $summary->out]);
}
~~~
### 效果
我們可以看效果了。再次刷新首頁[3](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#fn_3)得到效果如下:

## 小結
至此,我們已經基本完成首頁的編寫(還有一些內容我們會在后續章節講述)。
1. 我們通過Twig模板的繼承和包含,對布局模板進行了重構,分成了幾個互相獨立又互相依賴的子模板。這樣做的好處是,每個模板的HTML代碼總量都不大,方便調整和調試。
2. 通過內嵌控制器,我們進一步重構了模板,并就此編寫了對應的控制器動作和模板。
3. 模板的編寫基本基于當今流行的BootStrap框架,大大縮短了時間,提升了效率。
我們還將看兩個頁面的編寫。重點是分頁(在“書籍列表頁面”中講述)和圖片處理、jQuery的集成(在“書籍詳情頁面”中講述)。
不過在此之前,我們先來熟悉一下SF調試環境下的狀態欄。

該狀態欄只有在調試環境下出現。從左到右,該狀態欄圖標的含義為:
1. HTTP返回狀態。200表示正常。
2. 當前調用路由名。
3. 峰值內存占用。
4. 當前身份。
5. 渲染耗時。
6. SF當前版本。
將鼠標停在各圖標上還有更詳細的信息。該狀態欄對調試還是很有一定的用處的。
> 1. 在本教程中,為了方便起見,博客文章是“虛擬”的。在實際應用中,用到了對后臺WordPress數據庫的調用。[??](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#reffn_1 "Jump back to footnote [1] in the text.")
> 2. 這部分代碼非常簡單,只有靜態代碼。所以本教程中不再展開描述。[??](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#reffn_2 "Jump back to footnote [2] in the text.")
> 3. 該頁面效果所使用的博客數據庫是真實的,而不是虛擬的。[??](https://taylorr.gitbooks.io/building-a-web-site-with-symfony/content/05.08%20index.html#reffn_3 "Jump back to footnote [3] 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 結語