首页的编写
由于本站点采用了新的架构(即不再应用中包含数据的CRUD操作,而改为调用对应的RESTful API的方式),因此站点编写工作量大大降低。
SF基于MVC架构,所以编程部分在C部分,也就是控制器(Controller)部分。
一个常规控制器的流程如下:
我们之所以首先用首页来进行控制器编写的说明,主要是因为它是一个站点的入口,具有特别重要的地位,而且它往往独一无二与别的页面不同(别的页面如书籍详情等会重复)。另外,首页中也用到控制器嵌入等重要技术,值得首先加以描述。
顶端项目栏和搜索框
创建一个新的路由
首页的顶端我们要设计成项目名称和搜索框。修改的是AppBundlenav.html.twig
文件,并最终呈现在AppBundleindex.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();
}
}
简单说明如下:
- 这个控制器的代码并未完成,我们只是将通过表单提交的数据简单地加以显示而已。该方法的具体实现需要借助于别的尚未完成的模板,所以我们这里进行了简单处理。
修改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>
简单说明如下:
- 我们修改了项目名称为“任氏有无轩”。
- 我们为表单提供了
action
和method
参数。 - 我们使用了Twig专用的辅助函数
path
来为制定表单指定动作。它指向我们在上一步定义的books_search
路由。 method
只是简单的定义为POST。- 我们为一个文本输入框定义了一个名字为
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>
注意如下几点:
- 我们这里用Bootstrap布局来安排我们的书籍信息部分和书籍封面部分,左右分列。
- 所有的书籍信息的显示都来自从控制器传递过来的
book
变量。 - 我们再次用到
path
函数来构造浏览书籍详情的连接。这里我们还为该路径提供了一个参数,请读者必须注意参数传递的方式:{'id':book.bookid}
。 - 暂时我们用一个缺省的书籍封面作为所有书籍的封面。在后续章节中,我们还会进一步编写一个方法来显示书籍封面。
我们简化了Jumbotron的编写。在我自己开发的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个,它们分别是:
index.html.twig的进一步改写
我们进一步改写index.html.twig
文件,将原先三个<div>
的部分抽取出来成为AppBundlesummary.html.twig
和AppBundlesummary.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得到效果如下:
小结
至此,我们已经基本完成首页的编写(还有一些内容我们会在后续章节讲述)。
- 我们通过Twig模板的继承和包含,对布局模板进行了重构,分成了几个互相独立又互相依赖的子模板。这样做的好处是,每个模板的HTML代码总量都不大,方便调整和调试。
- 通过内嵌控制器,我们进一步重构了模板,并就此编写了对应的控制器动作和模板。
- 模板的编写基本基于当今流行的BootStrap框架,大大缩短了时间,提升了效率。
我们还将看两个页面的编写。重点是分页(在“书籍列表页面”中讲述)和图片处理、jQuery的集成(在“书籍详情页面”中讲述)。
不过在此之前,我们先来熟悉一下SF调试环境下的状态栏。
该状态栏只有在调试环境下出现。从左到右,该状态栏图标的含义为:
- HTTP返回状态。200表示正常。
- 当前调用路由名。
- 峰值内存占用。
- 当前身份。
- 渲染耗时。
- SF当前版本。
将鼠标停在各图标上还有更详细的信息。该状态栏对调试还是很有一定的用处的。
1. 在本教程中,为了方便起见,博客文章是“虚拟”的。在实际应用中,用到了对后台WordPress数据库的调用。 ↩
2. 这部分代码非常简单,只有静态代码。所以本教程中不再展开描述。 ↩
3. 该页面效果所使用的博客数据库是真实的,而不是虚拟的。 ↩