PHP的Yii框架中View视图的使用进阶
视图名
渲染视图时,可指定一个视图名或视图文件路径/别名,大多数情况下使用前者因为前者简洁灵活,我们称用名字的视图为视图名.
视图名可以依据以下规则到对应的视图文件路径:
视图名可省略文件扩展名,这种情况下使用.php作为扩展,视图名about对应到about.php文件名;
视图名以双斜杠//开头,对应的视图文件路径为@app/views/ViewName,也就是说视图文件在yii\base\Application::viewPath路径下找,例如//site/about对应到@app/views/site/about.php。
视图名以单斜杠/开始,视图文件路径以当前使用模块的yii\base\Module::viewPath开始,如果不存在模块,使用@app/views/ViewName开始,例如,如果当前模块为user,/user/create对应成@app/modules/user/views/user/create.php,如果不在模块中,/user/create对应@app/views/user/create.php。
如果yii\base\View::context渲染视图并且上下文实现了yii\base\ViewContextInterface,视图文件路径由上下文的yii\base\ViewContextInterface::getViewPath()开始,这种主要用在控制器和小部件中渲染视图,例如如果上下文为控制器SiteController,site/about对应到@app/views/site/about.php。
如果视图渲染另一个视图,包含另一个视图文件的目录以当前视图的文件路径开始,例如被视图@app/views/post/index.php渲染的item对应到@app/views/post/item。
根据以上规则,在控制器中app\controllers\PostController调用$this->render('view'),实际上渲染@app/views/post/view.php视图文件,当在该视图文件中调用$this->render('_overview')会渲染@app/views/post/_overview.php视图文件。
视图中访问数据
在视图中有两种方式访问数据:推送和拉取。
推送方式是通过视图渲染方法的第二个参数传递数据,数据格式应为名称-值的数组,视图渲染时,调用PHPextract()方法将该数组转换为视图可访问的变量。例如,如下控制器的渲染视图代码推送2个变量到report视图:$foo=1和$bar=2。
echo$this->render('report',[ 'foo'=>1, 'bar'=>2, ]);
拉取方式可让视图从yii\base\View视图组件或其他对象中主动获得数据(如Yii::$app),在视图中使用如下表达式$this->context可获取到控制器ID,可让你在report视图中获取控制器的任意属性或方法,如以下代码获取控制器ID。
ThecontrollerIDis:<?=$this->context->id?> ?>
推送方式让视图更少依赖上下文对象,是视图获取数据优先使用方式,缺点是需要手动构建数组,有些繁琐,在不同地方渲染时容易出错。
视图间共享数据
yii\base\View视图组件提供yii\base\View::params参数属性来让不同视图共享数据。
例如在about视图中,可使用如下代码指定当前breadcrumbs的当前部分。
$this->params['breadcrumbs'][]='AboutUs';
在布局文件(也是一个视图)中,可使用依次加入到yii\base\View::params数组的值来生成显示breadcrumbs:
<?=yii\widgets\Breadcrumbs::widget([ 'links'=>isset($this->params['breadcrumbs'])?$this->params['breadcrumbs']:[], ])?>
布局
布局是一种特殊的视图,代表多个视图的公共部分,例如,大多数Web应用共享相同的页头和页尾,在每个视图中重复相同的页头和页尾,更好的方式是将这些公共放到一个布局中,渲染内容视图后在合适的地方嵌入到布局中。
创建布局
由于布局也是视图,它可像普通视图一样创建,布局默认存储在@app/views/layouts路径下,模块中使用的布局应存储在yii\base\Module::basePath模块目录下的views/layouts路径下,可配置yii\base\Module::layoutPath来自定义应用或模块的布局默认路径。
如下示例为一个布局大致内容,注意作为示例,简化了很多代码,在实际中,你可能想添加更多内容,如头部标签,主菜单等。
<?php useyii\helpers\Html; /*@var$thisyii\web\View*/ /*@var$contentstring字符串*/ ?> <?php$this->beginPage()?> <!DOCTYPEhtml> <htmllang="en"> <head> <metacharset="UTF-8"/> <?=Html::csrfMetaTags()?> <title><?=Html::encode($this->title)?></title> <?php$this->head()?> </head> <body> <?php$this->beginBody()?> <header>MyCompany</header> <?=$content?> <footer>©2014byMyCompany</footer> <?php$this->endBody()?> </body> </html> <?php$this->endPage()?>
如上所示,布局生成每个页面通用的HTML标签,在<body>标签中,打印$content变量,$content变量代表当yii\base\Controller::render()控制器渲染方法调用时传递到布局的内容视图渲染结果。
大多数视图应调用上述代码中的如下方法,这些方法触发关于渲染过程的事件,这样其他地方注册的脚本和标签会添加到这些方法调用的地方。
- yii\base\View::beginPage():该方法应在布局的开始处调用,它触发表明页面开始的yii\base\View::EVENT_BEGIN_PAGE事件。
- yii\base\View::endPage():该方法应在布局的结尾处调用,它触发表明页面结尾的yii\base\View::EVENT_END_PAGE时间。
- yii\web\View::head():该方法应在HTML页面的<head>标签中调用,它生成一个占位符,在页面渲染结束时会被注册的头部HTML代码(如,link标签,meta标签)替换。
- yii\web\View::beginBody():该方法应在<body>标签的开始处调用,它触发yii\web\View::EVENT_BEGIN_BODY事件并生成一个占位符,会被注册的HTML代码(如JavaScript)在页面主体开始处替换。
- yii\web\View::endBody():该方法应在<body>标签的结尾处调用,它触发yii\web\View::EVENT_END_BODY事件并生成一个占位符,会被注册的HTML代码(如JavaScript)在页面主体结尾处替换。
布局中访问数据
在布局中可访问两个预定义变量:$this和$content,前者对应和普通视图类似的yii\base\View视图组件后者包含调用yii\base\Controller::render()方法渲染内容视图的结果。
如果想在布局中访问其他数据,必须使用视图中访问数据一节介绍的拉取方式,如果想从内容视图中传递数据到布局,可使用视图间共享数据一节中的方法。
使用布局
如控制器中渲染一节描述,当控制器调用yii\base\Controller::render()方法渲染视图时,会同时使用布局到渲染结果中,默认会使用@app/views/layouts/main.php布局文件。
可配置yii\base\Application::layout或yii\base\Controller::layout使用其他布局文件,前者管理所有控制器的布局,后者覆盖前者来控制单个控制器布局。例如,如下代码使post控制器渲染视图时使用@app/views/layouts/post.php作为布局文件,假如layout属性没改变,控制器默认使用@app/views/layouts/main.php作为布局文件。
namespaceapp\controllers; useyii\web\Controller; classPostControllerextendsController { public$layout='post'; //... }
对于模块中的控制器,可配置模块的yii\base\Module::layout属性指定布局文件应用到模块的所有控制器。
由于layout可在不同层级(控制器、模块,应用)配置,在幕后Yii使用两步来决定控制器实际使用的布局。
第一步,它决定布局的值和上下文模块:
如果控制器的yii\base\Controller::layout属性不为空null,使用它作为布局的值,控制器的yii\base\Controller::module模块作为上下文模块。
如果yii\base\Controller::layout为空,从控制器的祖先模块(包括应用)开始找第一个yii\base\Module::layout属性不为空的模块,使用该模块作为上下文模块,并将它的yii\base\Module::layout的值作为布局的值,如果都没有找到,表示不使用布局。
第二步,它决定第一步中布局的值和上下文模块对应到实际的布局文件,布局的值可为:
路径别名(如@app/views/layouts/main).
绝对路径(如/main):布局的值以斜杠开始,在应用的[[yii\base\Application::layoutPath|layoutpath]布局路径中查找实际的布局文件,布局路径默认为@app/views/layouts。
相对路径(如main):在上下文模块的yii\base\Module::layoutPath布局路径中查找实际的布局文件,布局路径默认为yii\base\Module::basePath模块目录下的views/layouts目录。
布尔值false:不使用布局。
布局的值没有包含文件扩展名,默认使用.php作为扩展名。
嵌套布局
有时候你想嵌套一个布局到另一个,例如,在Web站点不同地方,想使用不同的布局,同时这些布局共享相同的生成全局HTML5页面结构的基本布局,可以在子布局中调用yii\base\View::beginContent()和yii\base\View::endContent()方法,如下所示:
<?php$this->beginContent('@app/views/layouts/base.php');?> ...childlayoutcontenthere... <?php$this->endContent();?>
如上所示,子布局内容应在yii\base\View::beginContent()和yii\base\View::endContent()方法之间,传给yii\base\View::beginContent()的参数指定父布局,父布局可为布局文件或别名。
使用以上方式可多层嵌套布局。
使用数据块
数据块可以在一个地方指定视图内容在另一个地方显示,通常和布局一起使用,例如,可在内容视图中定义数据块在布局中显示它。
调用yii\base\View::beginBlock()和yii\base\View::endBlock()来定义数据块,使用$view->blocks[$blockID]访问该数据块,其中$blockID为定义数据块时指定的唯一标识ID。
如下实例显示如何在内容视图中使用数据块让布局使用。
首先,在内容视图中定一个或多个数据块:
... <?php$this->beginBlock('block1');?> ...contentofblock1... <?php$this->endBlock();?> ... <?php$this->beginBlock('block3');?> ...contentofblock3... <?php$this->endBlock();?>
然后,在布局视图中,数据块可用的话会渲染数据块,如果数据未定义则显示一些默认内容。
... <?phpif(isset($this->blocks['block1'])):?> <?=$this->blocks['block1']?> <?phpelse:?> ...defaultcontentforblock1... <?phpendif;?> ... <?phpif(isset($this->blocks['block2'])):?> <?=$this->blocks['block2']?> <?phpelse:?> ...defaultcontentforblock2... <?phpendif;?> ... <?phpif(isset($this->blocks['block3'])):?> <?=$this->blocks['block3']?> <?phpelse:?> ...defaultcontentforblock3... <?phpendif;?> ...
使用视图组件
yii\base\View视图组件提供许多视图相关特性,可创建yii\base\View或它的子类实例来获取视图组件,大多数情况下主要使用view应用组件,可在应用配置中配置该组件,如下所示:
[ //... 'components'=>[ 'view'=>[ 'class'=>'app\components\View', ], //... ], ]
视图组件提供如下实用的视图相关特性,每项详情会在独立章节中介绍:
- 主题:允许为你的Web站点开发和修改主题;
- 片段缓存:允许你在Web页面中缓存片段;
- 客户脚本处理:支持CSS和JavaScript注册和渲染;
- 资源包处理:支持资源包的注册和渲染;
- 模板引擎:允许你使用其他模板引擎,如Twig,Smarty。
开发Web页面时,也可能频繁使用以下实用的小特性。
设置页面标题
每个Web页面应有一个标题,正常情况下标题的标签显示在布局中,但是实际上标题大多由内容视图而不是布局来决定,为解决这个问题,yii\web\View提供yii\web\View::title标题属性可让标题信息从内容视图传递到布局中。
为利用这个特性,在每个内容视图中设置页面标题,如下所示:
<?php $this->title='Mypagetitle'; ?> 然后在视图中,确保在<head>段中有如下代码: <title><?=Html::encode($this->title)?></title>
注册Meta元标签
Web页面通常需要生成各种元标签提供给不同的浏览器,如<head>中的页面标题,元标签通常在布局中生成。
如果想在内容视图中生成元标签,可在内容视图中调用yii\web\View::registerMetaTag()方法,如下所示:
<?php $this->registerMetaTag(['name'=>'keywords','content'=>'yii,framework,php']); ?>
以上代码会在视图组件中注册一个"keywords"元标签,在布局渲染后会渲染该注册的元标签,然后,如下HTML代码会插入到布局中调用yii\web\View::head()方法处:
<metaname="keywords"content="yii,framework,php">
注意如果多次调用yii\web\View::registerMetaTag()方法,它会注册多个元标签,注册时不会检查是否重复。
为确保每种元标签只有一个,可在调用方法时指定键作为第二个参数,例如,如下代码注册两次"description"元标签,但是只会渲染第二个。
$this->registerMetaTag(['name'=>'description','content'=>'ThisismycoolwebsitemadewithYii!'],'description'); $this->registerMetaTag(['name'=>'description','content'=>'Thiswebsiteisaboutfunnyraccoons.'],'description');
注册链接标签
和Meta标签类似,链接标签有时很实用,如自定义网站图标,指定Rss订阅,或授权OpenID到其他服务器。可以和元标签相似的方式调用yii\web\View::registerLinkTag(),例如,在内容视图中注册链接标签如下所示:
$this->registerLinkTag([ 'title'=>'LiveNewsforYii', 'rel'=>'alternate', 'type'=>'application/rss+xml', 'href'=>'http://www.yiiframework.com/rss.xml/', ]);
上述代码会转换成
<linktitle="LiveNewsforYii"rel="alternate"type="application/rss+xml"href="http://www.yiiframework.com/rss.xml/">