视图系统是yangzie的一大特色,视图是HTTP请求的响应之一(Response),响应可能是一个终端可见的视图,也可能是个HTTP状态,比如一个重定向,也就是所有返回到请求端的内容都是响应。
视图系统是响应的一个重要组成部分,大部分的响应都是视图(View),由于yangzie是一个负责开发web后端的开发框架,这里不得不提一下web中使用的协议:HTTP。http协议分成两个部分:消息头和消息体。消息头是用于控制请求和响应应该被如何控制的的协议控制信息,消息体是就是实际被输出的内容,在yangzie里,称为视图。这里的输出包含以下这些情况。
- html内容,比如一个完整的页面、部分页面的html片段
- json数据
- 一张图,如png,jpg
- xml内容,如xml数据,svg
- pdf、xls,cvs
- 可以下载的内容格式,如zip
- 其他你想通过请求返回的内容
在yangzie中需要做的只是在请求中指定一下响应格式。举个例子:
一个查看订单的请求地址是http://YOUR-DOMAIN/order/123456
那么默认情况下该请求返回的是一个web的查看html页面。但如果我们需要把这个订单打印成PDF或者导出excel,那么在yangzie中你需要做的就是指定响应格式,也就是,指定一个后缀。就这么简单:
返回PDF http://YOUR-DOMAIN/order/123456.pdf
返回excel http://YOUR-DOMAIN/order/123456.xls
yangzie的视图系统还包含:
- Layout,定义一个请求的整体显示布局,比如定义菜单区域,页头、页脚区域
- Master View,可以把大家都一样的内容提取出来,做成master view,然后让每个控制器只负责自己的内容区域即可。
- Section View。把layout或者master view中的部分内容交改具体的控制器来定义
- 基于HTTP的缓存功能,没错,动态响应的view也是可以被缓存的
- 组件系统,可以把重复的或者相识的页面做成一个组件重用
- PJAX的支持
- 移动端视图
那么,我们开始进入视图的世界,首先我们需要回顾一下视图和controler的关系.
1.控制器与视图
GET请求及其视图模板
一个请求都会唯一映射到一个Controller的Action,并由Action返回一个Respone。
对于GET请求,视图是以模板文件的方式单独存放到模块下的views目录中,并与action一一对应:
模板文件的命名规则是控制器名-action名.视图格式.php
模板采用的是php和html混搭的方式编写,控制器中通过$this->set_view_data(‘foo’, $bar);设置的值,视图模板中可通过$this->get_data(‘foo’)获取,然后根据情况动态输出。
POST请求
传统的表单提交是指通过<form method=”post” url=”/接口地址”>方式,由submit 按钮提交数据,在这种情况下,如果POST的action不明确指定返回什么响应,那么他会返回GET请求对应的模板数据。
这时可以通过set_view_data把提交上来的数据和错误消息都设置给视图模板,实现数据回显几错误消息提示。
但如果post请求如果处理成功,需要返回重定向给浏览器端(避免浏览器缓存表单数据),重定向通过返回YZE_Redirect来实现:
return new YZE_Redirect('/new/url', $this);
如果前端采取ajax提交或者该action是作为接口调用,则建议action中明确返回YZE_JSON_VIEW::success($controller, $data)的结构体或YZE_JSON_VIEW::error($controller, $data)
YZE_JSON_VIEW::success返回的json格式如下:
[
'success' => true,
"data" => $data,
"msg" => null
]
YZE_JSON_VIEW::error返回的json格式如下:
[
'success' => false,
"data" => $data,
"code" => $code,
"msg" => $message
]
YZE_XML_VIEW是对应的XML版本,返回xml格式的数据。
当然你也可以使用YZE_Notpl_View或则继承YZE_JSON_VIEW并重写方法的方式自定义结构体。
1.1.一个请求的多种响应格式
通常一个网络请求,可能会需要返回多种响应的格式,比如功能界面,接口的格式,文件下载,举例来说,比如有一个/user/12345的请求:
- /user/12345: 返回功能界面展示用户的详情
- /user/12345.json: 作为接口调用,给其他系统返回用户的信息的json格式数据
- /user/12345.pdf:作为pdf文件下载保存到本地
这些其实都是通过yangzie的视图系统来实现,对于这些请求,yangzie不用重复编写相同、相似的代码,因为处理他们的controller和action都是一样的,唯一要做的就是在views中创建对于的视图模版:
- 控制器名-action名称.tpl.php, 负责返回html模板内容
- 控制器名-action名称.json.php, 负责返回json内容
- 控制器名-action名称.pdf.php, 负责生成pdf内容
注意:如果请求的视图模板不存在,那么默认会使用tpl.php模版
1.2.布局模板Layout
不同的响应格式,其实都对应一个布局模板,也就是一个响应中的绝大部分公共部分,比如tpl.php的布局模板就是app/vendor/layouts/tpl.layout.php,但是对于其他大部分格式来说,布局模板并没什么用,比如json或则pdf,这个时候在输出这些格式时,可在模板中通过设置不用布局:
$this->layout = ""
同时要注意,json,pdf这些格式对应着自己的MIME,需要在输出前自行设置响应的Content-Type,如:
header("Content-Type: application/json; charset=utf-8");
但是对于tpl格式,布局模板就有大用处了,通常对于一个请求,其返回的响应只是一个功能界面中的部分内容,比如下面的页面内容:
访问1的地址返回的界面中,只有2这部分内容是该页面中的主体内容,其他的部分是公共的,访问该系统的其他页面,你会看到这些部分是不变的。
在yangzie中,提供layout来避免这些公共部分的重复处理,layout也是一个视图模版,负责把这些公共的部分处理好,并在具体输出action视图的地方用<?php echo $this->content_of_view();?>输出action的视图,下面是一个layout模板:
<?php
namespace yangzie;
?>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $this->get_data("yze_page_title")?> - <?php echo YZE_APP_NAME?></title>
<?php
yze_css_bundle("");
yze_module_css_bundle();
yze_js_bundle("yangzie");
yze_module_js_bundle();
?>
</head>
<body>
<!-- HEADER,如标题,顶部菜单 -->
<?php echo $this->content_of_view();?>
<!-- FOOTER,如页脚 -->
</body>
</html>
根据系统的需要,也可以定义多个tpl布局,比如error.layout.php,admin.layout.php等负责在出错或者专门给admin提供一个不同的布局界面;切换不同的布局只需要在controller中通过$this->layout=’admin’或则在view模版中通过$this->layout=’admin’来切换不同的布局界面。
1.3.资源打包
在layout模板文件中,你会看到如下的一些代码调用:
yze_css_bundle("");
yze_module_css_bundle();
yze_js_bundle("yangzie");
yze_module_js_bundle();
他们就是对资源(css,js)进行打包访问的接口调用,对于一个web系统,每个页面都会使用到很多css库和js库,尤其是系统复杂后,引用的css和js文件会更多,比如下图
每个资源文件的访问都是一个http请求,耗费一个服务器的链接资源,资源打包就是把这些文件合并成一个文件进行访问,而这些资源文件又分成两类:1. 整个项目的共用资源;2. 当前访问的模块中的资源。他们的加载方式若有不同。
公共资源文件
公共资源的打包是通过在app/__config__.php中文件进行设置的,js_bundle用于定义js资源组,css_bundle用于定义css资源组:
public function js_bundle($bundle) {
$config = array(
"foo" => array(
"/foo/a.min.js",
"/foo/b.min.js"
),
"bar" => array(
"/bar/a.js",
"/bar/b.js"
)
);
return $config[$bundle];
}
public function css_bundle($bundle) {
$config = array(
"foo" => array(
"/foo/css/a.min.css",
"/foo/css/b.min.css"
),
"bar" => array(
"/bar/a.css",
"/bar/b.css"
)
);
return $config[$bundle];
}
定义好资源组后就可以通过yze_js_bundle和yze_css_bundle加载对应的资源组,可以同时加载多个资源组,比如yze_css_bundle(“foo,bar”); yze_js_bundle(“foo,bar”);
共用资源文件的存储是在public_html下面,所以这些资源组中文件的访问是相对于public_html的,比如/bar/a.css, 就是位于public_html/bar/a.css
模块资源打包
模块资源打包和共用资源打包是一样的,定义资源组的方式是在模块的__config__.php中定义,定义方法和app的__config__.php中(app本身也是module),模块资源的存储是在模块下面的public_html中。
资源模块通过yze_module_css_bundle()和yze_module_js_bundle()加载,不需要传入参数,yangzie会自动根据当前请求的模块加载资源。
资源打包的好处已经存在的问题
好处:
- 一次访问加载一组中的资源文件,减少网络请求
- 支持HTTP的缓存机制,后面再次访问时,服务端会响应HTTP/1.0 304 Not Modified响应,可通过给资源加载函数传入version参数强制刷新,通常配合APP的version来使用,这由开发者自行处理:__config__中定义一个app的version常量,每次有更新时更新它,然后yze_css_bundle(“foo,bar”, version); yze_js_bundle(“foo,bar”, version)
存在的问题:
- 打包的资源时通过/load.php来加载的,这将导致这些资源的根路径是/, 也就是public_html目录,如果这些资源中(尤其是css)中有通过相对路径引用其他资源文件(如图片)时,会导致这些资源文件无法加载,需要开发者进行调整:
比如public_html/foo/css/a.css中有引用图片:background-image:url(./img/foo.png)时,需要引用的时public_html/foo/css/img/foo.png图片,但是通过load.php加载后,会变成引用public_html/img/foo.png - 有时候打包下载并不见得是一件好事,这个要看项目的实际情况,打包后请求变少了,但是文件也会响应的变大,可能下载的时间反而会变大;
1.4.Master View 母版页
母版页和Layout布局是一样的,只是用的地方不一样:
- layout是app级,也就是顶级的母版页
- Master是具体某个功能的模版页
Master页也是用来解决视图重用问题,并且他们之间存在一个上下级的关系,比如下面的几个系统界面:
这两个节目分别代表了两个功能模块,可以看到他们顶部的导航菜单是一样的,这是APP级别的共性,这个共性的部分就可以放到Layout布局文件进行定义。
然后看第二张图中的边栏部分,里面有很多的功能入口,但是不管进入那个功能,边栏都是一样的,那么这部分就是该模块的共性内容了,就可以通过Master view来实现重用。
如何定义 master view:
master view可通过视图模板文件的方式定义或则通过YZE_View_Component,前者就是php和html混合的文化,后者是一个自定义的类。
如果采用模板文件的方式,master view可以存放位置:
- 模块下的views中;
- app/vendor/views中
- 自己规定的位置
模版文件的命名为 master名.响应格式.php
如project.tpl.php, master 也支持响应格式,如果同一个请求的响应支持不同的响应格式,那么被使用的master也需要同时定义
如果采用YZE_View_Component定义master view,则需要按照yangzie的类命名规则存在代码即可(见类定义及存放规则)。
模板文件中,同样通过<?php $this->content_of_view()?>来输出其包含的具体请求的action输出的视图内容。
如何使用master view:
在具体的action视图中,通过在视图模板中通过$this->master_view来指定使用的master view, 如果是模板文件的master view,这指定模板名称,比如$this->master_view=’project’;框架会尝试从如下位置找模版文件
- 当前模块的views目录
- app/vendor/views
- 直接根据指定的去找
如果是YZE_View_Component的模版,则直接new 对应的master 对象:$this->master_view= new My_Master_View()
master view可以嵌套,通过$this->master_view设置上级master view
控制器中set_view_data()设置的数据在layout,master中同样可以通过get_data()获取到
1.5.视图的输出顺序
一个请求的处理入口是映射的action,但是视图的输出顺序是从layout开始,接着是master view最后才是具体action的view,这时因为视图是一个父级和子级的关系,这也就是为什么layout和master通过<?php $this->content_of_view()?>来控制下级输出在那个位置的原因。但也存在某些特殊情况下,下级view可能需要在上级master或者layout中的某个地方控制输出内容的情况,并且这些输出的代码逻辑和view中的逻辑是相关或者一体的,这种情况下就可以通过section view来实现。
1.6.Section View
Layout和Master解决了视图中有嵌套关系(父级控制子级的输出)的组件重用,已经能满足绝大部分的场景了,但是实际的项目情况中,可能还存在着下级需要控制上级的输出的情况,考虑如下的节目布局:
布局layout定义了顶部的header 部分,然后master view中定义了左边的sidebar部分,但是在sidebar的底部有两个地方是需要view来控制的,不同的view会控制输出的内容不同。
yangzie把这部分在父级view中需要子级view控制输出的内容叫着section,父级view中只需要在控制这部分输出的地方通过<?php $this->content_of_section($sectionName)?>来输出内容即可,跟<?php $this->content_of_view()?>一样,他们的内容都有子级view来决定,不同的地方是Section需要你个名称,比如上图中有两个Section输出:
- <?php $this->content_of_section(“Box 1”)?>
- <?php $this->content_of_section(“Box 2”)?>
按照view的控制逻辑,执行的入口是先进入view进行处理,view视图提供begin_section()和end_section($sectionName)的方式来指定section的输出内容,比如在视图模版中定义如下BOX 1:
<?php $this->begin_section()?>
the content of BOX1
<?php $this->end_section('Box 1')?>
在begin_section和end_section中的部分不会输出到视图中,而是输出到上级视图中调用<?php $this->content_of_section(“Box 1”)?>的地方。
1.7.View组件
任何一个项目都会存在视图重用的情况,在yangzie中可以两种方式实现组件重用:
- 模板文件的方式
- YZE_View_Component类
模板文件就是传统的php和html混合输出的文件,但使用方式并不是简单的include它,而是通过YZE_Simple_View类来使用:
$view = new YZE_Simple_View($tpl_name, $data, $controller, $format);
$view->output();
$tpl_name就是模板文件的绝对路径,但是文件名不需要.tpl.php这个部分,因为响应格式是通过参数$format来根据前端访问指定识别的。yangzie建议组件放在app/vendor中,有一个常量YZE_APP_VIEWS_INC就是指该目录:$view = new YZE_Simple_View(YZE_APP_VIEWS_INC.”foobar”, $data, $controller, $format)
$data是传入模板的数据
$controller 是当前请求的控制器对象
在模板中$this就是指视图对象自己,上面所说的设置$this->layout, $this->master_view,section等都正常使用。
YZE_View_Component提供面向对象的方式来定义和创建视图