模板

模板属于MVC结构中的V。它的出现是为了更好地分离程序代码(对用户不可见)HTML呈现(用户可见)。

对模板开发者的要求是能掌握一些基本的模板语法,写出基本框架,加入对应的控制和输出,最终形成一个可以呈现给用户的页面。

SF模板引擎:Twig

SF的创始人Fabien Potencier研制了一个全新的模板引擎:Twig 。我很喜欢这个引擎。我个人认为它有这么几个优点:

  • 语法简单,上手快;
  • 支持嵌套、继承等模板高级特性;
  • 扩展性好,可以开发Twig插件扩充功能;
  • 编译速度快,效率高。
    Twig是SF框架中的一个部件,也可以独立被其它框架使用。在SF应用创建后,已经支持Twig。

页面结构的设计

我们的应用不会只有一个页面,但是众多页面却可能有着相同的布局。比如,都采用“顶部导航栏+中间内容+底部导航栏”的上中下布局,或者采用“左导航栏+中间内容+底部导航栏”的布局,等等等等。

我一般会采用基于Bootstrap的模板加以定制。我现在任氏有无轩 使用的是从WrapBootstrap 上购买的一个商业模板。在本应用开发中,我们会用一个免费的Bootstrap模板,Jumbotron

5.7. 模板  - 图1

这个页面布局很合适我们的应用。

  • 顶部的黑色导航区可以放应用名称,导航链接,右边的用户登录部分可以用搜索框来代替;
  • 下面的Jumbotron部分可以用在某些页面中作为强调部分;
  • 主内容区三个并排的列可以在首页中显示不同栏目的内容,也很方便变成一个列用在别的页面中;
  • 最下面的一块地方正好可以用来显示一些版权信息之类的。
    为了在我们的应用中使用这个布局,我们需要做一些额外的工作。

保存Bootstrap模板

我们首先要将Jumbotron这个页面保存下来。在浏览器中选择保存页面。如果有选择保存方式的话,选择“全部”。这样我们保存的就不仅是该页面的HTML文件,而且包括该HTML页面中引用到的CSS,JS和图片文件等。

在我们SF项目的web目录下创建几个目录,如cssjsimg,将保存下来的页面引用到的CSS,JS和图片文件分别拷贝到对应的目录中去。

接着,我们将刚才保存的那个HTML文件移到src/AppBundle/Resources/views/default并改名为layout.html.twig

修改首页显示该模板

下一步,我们修改当前首页所渲染的模板,使其显示我们刚才创建的模板,这样方便我们进一步修改该模板并看到效果。

让我们修改src/AppBundle/Controller/DefaultController.php中的indexAction为:

  1. public function indexAction(Request $request)
  2. {
  3. // replace this example code with whatever you need
  4. return $this->render('AppBundle:default:layout.html.twig');
  5. }

刷新我们的应用页面,可以看到Bootstrap的页面已经替换了原来的SF欢迎页面。但是由于该页面中引用的CSS、JS文件的相对路径已经发生变化,所以需要进行调整。具体怎样调整这里就不再赘述,原则上只是一些文件路径和文件名的调整罢了。

如果一切顺利,我们应该看到和之前在Bootstrap上一样的页面效果。

模板的继承和包含

Twig模板引擎的一个强大之处在于它支持模板的继承和包含,而且还可以嵌入控制器1。这为我们定义灵活的页面布局并同时保持一致性提供了方便。

包含

包含是include,它并不是Twig的重点,所以我们只是提一下。它用来在一个模板中导入另一个模板。通常情形下,那些相对固定且在多个页面中都会重复出现的内容比较适合被剥离到一个单独的模板文件中。在需要这些内容的地方加以导入即可。

考虑我们使用的Bootstrap样板,我们会很快地发现,顶部导航栏和底部的导航栏都具有这样的性质:相对固定、作为页眉和页脚也会在多个页面重复出现。所以,这两部分可以被提取出去作为一个独立的模板供其它模板使用。

继承

Twig的强大之处在于模板的继承。

学习过OOP的一定知道类继承是怎样的机制。简单地说,子类继承了父类中所有公共特性和公共方法,同时也可以按照子类的特殊要求加入新的特性和方法。

Twig模板的继承与此也有类似之处。不过由于我们讨论的是页面布局,所以这里的继承也是对页面布局的继承。

根据上面对Bootstrap样板的分析,我们可以看到,以页面布局来看,我们的页面基本上有三个部分:

  • 顶部导航栏,我们不妨称之为"nav2"。
  • 中间的内容部分,我们不妨称之为"content"。
  • 底部的信息栏,我们不妨称之为"footer"。
    这三部分中,1/3我们已经讨论过了,我们需要将它们独立出去,成为两个独立的模板供包含。

2这部分是非常动态的,各个不同页面要显示的主要内容肯定不同。所以,它也必须独立出去,由各个页面各自完成。

于是我们可以对主模板进行如下图所示的调整:

5.7. 模板  - 图2

简要说明一下:

  • 原本的layout模板被拆分为四块:headernavindexfooter
  • index模板成为我们首页最终使用的模板,它将继承layout模板,并用属于该页面的内容覆盖content页面组块。
  • 其它三个模板分别在layout模板中被引用。
    随着应用的开发,我们会创建更多的页面。但是,其创建方式和创建index页面的方式类似。

Twig模板中的block

可以这么说,block(块)是Twig模板构造页面的基础。

layout模板中,我们会注意到这么一段:

  1. <html lang="zh">
  2. {% include "AppBundle:default:header.html.twig" %}
  3. <body>
  4. {% include "AppBundle:default:nav.html.twig" %}
  5. {% block content %}{% endblock %}
  6. {% include "AppBundle:default:footer.html.twig" %}
  7. </body>
  8. </html>

{% block content %}{% endblock %}layout模板中定义了一个名为content的块。我们注意到,layout中这个块中没有任何内容。它的内容是由各个具体的页面填充的,比如在index模板中:

  1. {% extends "AppBundle:default:layout.html.twig" %}
  2. {% block content %}
  3. <div class="jumbotron">
  4. ... ...
  5. </div> <!-- /container -->
  6. {% endblock %}

首先,index模板声明它会扩展(也就是继承自)layout.html.twig模板。因此,所有在layout模板中出现的布局因素(包括layout需要引用的其它模板中的布局因素)也都会在index中出现。

但是,index模板重写了content块,替换成自己的内容。而index模板中未覆盖的那些块,会仍然采用layout中的相应内容。

这样的一个过程,正是继承的过程。

一般而言,每当我们要创建一个新的页面,我们会采用类似的方式:

  • 从主模板(本例中的layout)继承;
  • 重写相应的块。一般而言,也就是一个诸如名为content的块;
  • 根据需要,重写另外的一些块,比如titleextralinkextrajs等以达到高度定制的目的。

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. 在模板中嵌入控制器是高级用法,但是其语法却非常简明。我们在后续章节会看到详细讲解。
2. 我们没有把它叫做header是因为我们还会有一个header的模板,用来统一管理HTML页面中的头信息(如meta,CSS/JS的引用等)。