2 个关注者

视图

视图是 MVC 架构的一部分。它们是负责向最终用户呈现数据的代码。在 Web 应用程序中,视图通常以 *视图模板* 的形式创建,视图模板是 PHP 脚本文件,主要包含 HTML 代码和表示性 PHP 代码。它们由 视图 应用程序组件 管理,该组件提供常用方法来促进视图组合和渲染。为简单起见,我们通常将视图模板或视图模板文件称为视图。

创建视图

如前所述,视图只是一个包含 HTML 和 PHP 代码的 PHP 脚本。以下是呈现登录表单的视图。如您所见,PHP 代码用于生成动态内容,例如页面标题和表单,而 HTML 代码将它们组织成一个可呈现的 HTML 页面。

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;

/* @var $this yii\web\View */
/* @var $form yii\widgets\ActiveForm */
/* @var $model app\models\LoginForm */

$this->title = 'Login';
?>
<h1><?= Html::encode($this->title) ?></h1>

<p>Please fill out the following fields to login:</p>

<?php $form = ActiveForm::begin(); ?>
    <?= $form->field($model, 'username') ?>
    <?= $form->field($model, 'password')->passwordInput() ?>
    <?= Html::submitButton('Login') ?>
<?php ActiveForm::end(); ?>

在视图中,您可以访问 $this,它指的是管理和渲染此视图模板的 视图组件

除了 $this 之外,视图中可能还有其他预定义变量,例如上面的示例中的 $model。这些变量表示由 控制器 或其他触发 视图渲染 的对象 *推送* 到视图中的数据。

提示:预定义变量在视图开头的注释块中列出,以便 IDE 能够识别它们。这也是记录视图的好方法。

安全

在创建生成 HTML 页面的视图时,务必在呈现来自最终用户的数据之前对它们进行编码和/或过滤。否则,您的应用程序可能会受到 跨站点脚本 攻击。

要显示纯文本,请先通过调用 yii\helpers\Html::encode() 对其进行编码。例如,以下代码在显示用户名之前对它进行编码

<?php
use yii\helpers\Html;
?>

<div class="username">
    <?= Html::encode($user->name) ?>
</div>

要显示 HTML 内容,请使用 yii\helpers\HtmlPurifier 首先过滤内容。例如,以下代码在显示帖子内容之前过滤它

<?php
use yii\helpers\HtmlPurifier;
?>

<div class="post">
    <?= HtmlPurifier::process($post->text) ?>
</div>

提示:虽然 HTMLPurifier 在使输出安全方面做得很好,但它并不快。如果您的应用程序需要高性能,您应该考虑 缓存 过滤结果。

组织视图

控制器模型 一样,也有一些约定来组织视图。

  • 对于由控制器渲染的视图,默认情况下应将其放置在 @app/views/ControllerID 目录下,其中 ControllerID 指的是 控制器 ID。例如,如果控制器类是 PostController,则目录将是 @app/views/post;如果它是 PostCommentController,则目录将是 @app/views/post-comment。如果控制器属于模块,则目录将是 模块目录 下的 views/ControllerID
  • 对于在 小部件 中渲染的视图,默认情况下应将其放置在 WidgetPath/views 目录下,其中 WidgetPath 代表包含小部件类文件的目录。
  • 对于由其他对象渲染的视图,建议您遵循与小部件类似的约定。

您可以通过覆盖控制器或小部件的 yii\base\ViewContextInterface::getViewPath() 方法来自定义这些默认视图目录。

渲染视图

您可以在 控制器小部件 或任何其他地方通过调用视图渲染方法来渲染视图。这些方法共享一个类似的签名,如下所示,

/**
 * @param string $view view name or file path, depending on the actual rendering method
 * @param array $params the data to be passed to the view
 * @return string rendering result
 */
methodName($view, $params = [])

在控制器中渲染

控制器 中,您可以调用以下控制器方法来渲染视图

例如,

namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        // renders a view named "view" and applies a layout to it
        return $this->render('view', [
            'model' => $model,
        ]);
    }
}

在小部件中渲染

小部件 中,您可以调用以下小部件方法来渲染视图。

例如,

namespace app\components;

use yii\base\Widget;
use yii\helpers\Html;

class ListWidget extends Widget
{
    public $items = [];

    public function run()
    {
        // renders a view named "list"
        return $this->render('list', [
            'items' => $this->items,
        ]);
    }
}

在视图中渲染

您可以通过调用 视图组件 提供的以下方法之一,在一个视图中渲染另一个视图。

例如,以下代码在视图中渲染 _overview.php 视图文件,该文件与当前正在渲染的视图位于同一目录下。请记住,视图中的 $this 指的是 视图 组件

<?= $this->render('_overview') ?>

在其他地方渲染

在任何地方,您都可以通过表达式 Yii::$app->view 访问 视图 应用程序组件,然后调用其上述方法来渲染视图。例如,

// displays the view file "@app/views/site/license.php"
echo \Yii::$app->view->renderFile('@app/views/site/license.php');

命名视图

当您渲染视图时,可以使用视图名称或视图文件路径/别名来指定视图。在大多数情况下,您会使用前者,因为它更简洁、更灵活。我们将使用名称指定的视图称为命名视图

视图名称根据以下规则解析为相应的视图文件路径

  • 视图名称可以省略文件扩展名。在这种情况下,将使用 .php 作为扩展名。例如,视图名称 about 对应于文件名 about.php
  • 如果视图名称以双斜杠 // 开头,则相应的视图文件路径将是 @app/views/ViewName。也就是说,视图是在 应用程序的视图路径 下查找的。例如,//site/about 将解析为 @app/views/site/about.php
  • 如果视图名称以单斜杠 / 开头,则视图文件路径是通过将视图名称与当前活动 模块视图路径 作为前缀来形成的。如果没有活动模块,将使用 @app/views/ViewName。例如,/user/create 将解析为 @app/modules/user/views/user/create.php,如果当前活动模块是 user。如果没有活动模块,视图文件路径将是 @app/views/user/create.php
  • 如果视图是在 上下文 中渲染的,并且上下文实现了 yii\base\ViewContextInterface,则视图文件路径是通过将上下文的 视图路径 作为前缀添加到视图名称来形成的。这主要适用于在控制器和小部件中渲染的视图。例如,about 将解析为 @app/views/site/about.php,如果上下文是控制器 SiteController
  • 如果一个视图是在另一个视图中渲染的,则包含另一个视图文件的目录将作为前缀添加到新视图名称中,以形成实际的视图文件路径。例如,item 将解析为 @app/views/post/item.php,如果它是在视图 @app/views/post/index.php 中渲染的。

根据上述规则,在控制器 app\controllers\PostController 中调用 $this->render('view') 实际上会渲染视图文件 @app/views/post/view.php,而在该视图中调用 $this->render('_overview') 会渲染视图文件 @app/views/post/_overview.php

在视图中访问数据

有两种方法可以在视图中访问数据:推和拉。

通过将数据作为第二个参数传递给视图渲染方法,您正在使用推方法。数据应表示为名称-值对的数组。当视图被渲染时,PHP extract() 函数将被调用到此数组上,以便将数组提取到视图中的变量中。例如,以下视图渲染代码在控制器中将推送两个变量到 report 视图:$foo = 1$bar = 2

echo $this->render('report', [
    'foo' => 1,
    'bar' => 2,
]);

拉方法主动从 视图组件 或在视图中可访问的其他对象(例如 Yii::$app)中检索数据。以以下代码为例,在视图中,您可以通过表达式 $this->context 获取控制器对象。因此,您可以在 report 视图中访问控制器的任何属性或方法,例如以下所示的控制器 ID

The controller ID is: <?= $this->context->id ?>

推方法通常是访问视图中数据的首选方法,因为它使视图对上下文对象的依赖性降低。它的缺点是您需要一直手动构建数据数组,如果视图是共享的并在不同的地方渲染,这可能会变得乏味且容易出错。

在视图之间共享数据

视图组件 提供了 params 属性,您可以使用它在视图之间共享数据。

例如,在 about 视图中,您可以使用以下代码指定面包屑的当前段。

$this->params['breadcrumbs'][] = 'About Us';

然后,在 布局 文件(它也是一个视图)中,您可以使用通过 params 传递的数据来显示面包屑

<?= yii\widgets\Breadcrumbs::widget([
    'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>

布局

布局是一种特殊的视图类型,它表示多个视图的公共部分。例如,大多数 Web 应用程序的页面共享相同的页面标题和页脚。虽然您可以在每个视图中重复相同的页面标题和页脚,但更好的方法是在布局中执行一次,并将内容视图的渲染结果嵌入到布局中的适当位置。

创建布局

因为布局也是视图,所以它们可以像普通视图一样创建。默认情况下,布局存储在 @app/views/layouts 目录中。对于在 模块 中使用的布局,它们应该存储在 模块目录 下的 views/layouts 目录中。您可以通过配置应用程序或模块的 yii\base\Module::$layoutPath 属性来自定义默认布局目录。

以下示例显示了一个布局的外观。请注意,为了说明目的,我们大大简化了布局中的代码。在实践中,您可能想向其中添加更多内容,例如头部标签、主菜单等。

<?php
use yii\helpers\Html;

/* @var $this yii\web\View */
/* @var $content string */
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <?= Html::csrfMetaTags() ?>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
    <header>My Company</header>
    <?= $content ?>
    <footer>&copy; 2014 by My Company</footer>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

如您所见,布局生成了所有页面通用的 HTML 标签。在 <body> 部分中,布局回显 $content 变量,该变量代表内容视图的渲染结果,并在调用 yii\base\Controller::render() 时推送到布局中。

大多数布局应该像上面代码中显示的那样调用以下方法。这些方法主要触发有关渲染过程的事件,以便在其他地方注册的脚本和标签可以正确注入到这些方法被调用的位置。

  • beginPage():此方法应在布局的开始时调用。它触发 EVENT_BEGIN_PAGE 事件,该事件表示页面的开始。
  • endPage():此方法应在布局的末尾调用。它触发 EVENT_END_PAGE 事件,该事件表示页面的结束。
  • head():此方法应在 HTML 页面的 <head> 部分中调用。它生成一个占位符,当页面完成渲染时,该占位符将被已注册的头部 HTML 代码(例如链接标签、元标签)替换。
  • beginBody():此方法应在 <body> 部分的开始时调用。它触发 EVENT_BEGIN_BODY 事件,并生成一个占位符,该占位符将被针对主体开始位置注册的 HTML 代码(例如 JavaScript)替换。
  • endBody():此方法应在 <body> 部分的末尾调用。它触发 EVENT_END_BODY 事件,并生成一个占位符,该占位符将被针对主体结束位置注册的 HTML 代码(例如 JavaScript)替换。

在布局中访问数据

在布局中,您可以访问两个预定义的变量:$this$content。前者指的是 视图 组件,与普通视图中的情况相同,而后者包含由控制器调用 render() 方法呈现的内容视图的呈现结果。

如果您想在布局中访问其他数据,则必须使用 在视图中访问数据 小节中描述的拉取方法。如果您想将数据从内容视图传递到布局,可以使用 在视图之间共享数据 小节中描述的方法。

使用布局

正如 在控制器中呈现 小节中所述,当您通过在控制器中调用 render() 方法呈现视图时,将应用布局到呈现结果。默认情况下,将使用布局 @app/views/layouts/main.php

您可以通过配置 yii\base\Application::$layoutyii\base\Controller::$layout 来使用不同的布局。前者控制所有控制器使用的布局,而后者会覆盖单个控制器的前者。例如,以下代码使 post 控制器在呈现其视图时使用 @app/views/layouts/post.php 作为布局。其他控制器(假设它们的 layout 属性未被修改)将继续使用默认的 @app/views/layouts/main.php 作为布局。

namespace app\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    public $layout = 'post';
    
    // ...
}

对于属于模块的控制器,您还可以配置模块的 layout 属性,以对这些控制器使用特定布局。

由于 layout 属性可以在不同的级别(控制器、模块、应用程序)进行配置,因此在幕后,Yii 会采取两个步骤来确定实际用于特定控制器的布局文件。

在第一步中,它确定布局值和上下文模块。

  • 如果控制器的 yii\base\Controller::$layout 属性不为 null,则将其用作布局值,并将控制器的 module 用作上下文模块。
  • 如果控制器的 yii\base\Controller::$layout 属性为 null,则遍历控制器的所有祖先模块(包括应用程序本身),并找到第一个其 layout 属性不为 null 的模块。使用该模块及其 layout 值作为上下文模块和选定的布局值。如果找不到这样的模块,则意味着不会应用任何布局。

在第二步中,它根据第一步中确定的布局值和上下文模块来确定实际的布局文件。布局值可以是

  • 路径别名(例如 @app/views/layouts/main)。
  • 绝对路径(例如 /main):布局值以斜杠开头。实际布局文件将在应用程序的 布局路径 下查找,该路径默认为 @app/views/layouts
  • 相对路径(例如 main):实际布局文件将在上下文模块的 布局路径 下查找,该路径默认为 模块目录 下的 views/layouts 目录。
  • 布尔值 false:不应用任何布局。

如果布局值不包含文件扩展名,它将使用默认扩展名 .php

嵌套布局

有时您可能希望将一个布局嵌套在另一个布局中。例如,在网站的不同部分,您希望使用不同的布局,而所有这些布局都共享相同的基本布局,该布局生成整个 HTML5 页面结构。您可以通过在子布局中调用 beginContent()endContent() 来实现此目标,如下所示

<?php $this->beginContent('@app/views/layouts/base.php'); ?>

...child layout content here...

<?php $this->endContent(); ?>

如上所示,子布局内容应包含在 beginContent()endContent() 之间。传递给 beginContent() 的参数指定父布局是什么。它可以是布局文件或别名。

使用上述方法,您可以将布局嵌套在多个级别中。

使用块

块允许您在一个地方指定视图内容,而在另一个地方显示它。它们通常与布局一起使用。例如,您可以在内容视图中定义一个块,并在布局中显示它。

您可以调用 beginBlock()endBlock() 来定义一个块。然后可以通过 $view->blocks[$blockID] 访问该块,其中 $blockID 代表在定义块时分配给该块的唯一 ID。

以下示例显示了如何使用块在内容视图中自定义布局的特定部分。

首先,在内容视图中定义一个或多个块

...

<?php $this->beginBlock('block1'); ?>

...content of block1...

<?php $this->endBlock(); ?>

...

<?php $this->beginBlock('block3'); ?>

...content of block3...

<?php $this->endBlock(); ?>

然后,在布局视图中,如果块可用,则呈现块,或者如果未定义块,则显示一些默认内容。

...
<?php if (isset($this->blocks['block1'])): ?>
    <?= $this->blocks['block1'] ?>
<?php else: ?>
    ... default content for block1 ...
<?php endif; ?>

...

<?php if (isset($this->blocks['block2'])): ?>
    <?= $this->blocks['block2'] ?>
<?php else: ?>
    ... default content for block2 ...
<?php endif; ?>

...

<?php if (isset($this->blocks['block3'])): ?>
    <?= $this->blocks['block3'] ?>
<?php else: ?>
    ... default content for block3 ...
<?php endif; ?>
...

使用视图组件

视图组件 提供了许多与视图相关的功能。虽然您可以通过创建 yii\base\View 或其子类的单个实例来获取视图组件,但在大多数情况下,您将主要使用 view 应用程序组件。您可以在 应用程序配置 中配置此组件,如下所示

[
    // ...
    'components' => [
        'view' => [
            'class' => 'app\components\View',
        ],
        // ...
    ],
]

视图组件提供以下有用的与视图相关的功能,每个功能在单独的部分中进行了更详细的描述

在您开发网页时,您也可能会经常使用以下次要但有用的功能。

设置页面标题

每个网页都应该有一个标题。通常,标题标签会显示在 布局 中。然而,在实践中,标题通常是在内容视图中而不是布局中确定的。为了解决这个问题,yii\web\View 提供了 title 属性,用于将标题信息从内容视图传递到布局。

要使用此功能,您可以在每个内容视图中设置页面标题,如下所示

<?php
$this->title = 'My page title';
?>

然后在布局中,确保您在 <head> 部分中包含以下代码

<title><?= Html::encode($this->title) ?></title>

注册元标签

网页通常需要生成各种不同方所需的元标签。与页面标题一样,元标签出现在 <head> 部分中,通常在布局中生成。

如果您想在内容视图中指定要生成的元标签,您可以在内容视图中调用 yii\web\View::registerMetaTag(),如下所示

<?php
$this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']);
?>

以上代码将注册一个带有“keywords”元标签的视图组件。注册的元标签在布局完成呈现后呈现。以下 HTML 代码将生成并插入您在布局中调用 yii\web\View::head() 的位置

<meta name="keywords" content="yii, framework, php">

请注意,如果您多次调用 yii\web\View::registerMetaTag(),它将注册多个元标签,无论这些元标签是否相同。

为了确保元标签类型只有一个实例,您可以在调用该方法时指定一个键作为第二个参数。例如,以下代码注册了两个“description”元标签。但是,只有第二个元标签会被呈现。

$this->registerMetaTag(['name' => 'description', 'content' => 'This is my cool website made with Yii!'], 'description');
$this->registerMetaTag(['name' => 'description', 'content' => 'This website is about funny raccoons.'], 'description');

元标签 一样,链接标签在许多情况下都很有用,例如自定义 favicon、指向 RSS 提要或将 OpenID 委托给其他服务器。您可以使用与元标签类似的方式处理链接标签,方法是使用 yii\web\View::registerLinkTag()。例如,在内容视图中,您可以注册一个链接标签,如下所示

$this->registerLinkTag([
    'title' => 'Live News for Yii',
    'rel' => 'alternate',
    'type' => 'application/rss+xml',
    'href' => 'https://yiiframework.cn/rss.xml/',
]);

以上代码将生成

<link title="Live News for Yii" rel="alternate" type="application/rss+xml" href="https://yiiframework.cn/rss.xml/">

类似于 registerMetaTag(),您可以在调用 registerLinkTag() 时指定一个键来避免生成重复的链接标签。

视图事件

视图组件 在视图呈现过程中触发多个事件。您可以响应这些事件,将内容注入视图或在呈现结果发送给最终用户之前处理它们。

例如,以下代码在页面主体末尾注入当前日期

\Yii::$app->view->on(View::EVENT_END_BODY, function () {
    echo date('Y-m-d');
});

呈现静态页面

静态页面指的是那些主要内容是静态的,不需要访问从控制器推送的动态数据的网页。

您可以通过将静态页面的代码放在视图中,然后在控制器中使用以下代码来输出静态页面

public function actionAbout()
{
    return $this->render('about');
}

如果一个网站包含许多静态页面,则重复类似代码会非常繁琐。为了解决这个问题,您可以在控制器中引入一个名为 独立操作 的操作,即 yii\web\ViewAction。例如

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function actions()
    {
        return [
            'page' => [
                'class' => 'yii\web\ViewAction',
            ],
        ];
    }
}

现在,如果您在 @app/views/site/pages 目录下创建一个名为 about 的视图,您可以通过以下 URL 显示此视图。

http://localhost/index.php?r=site%2Fpage&view=about

GET 参数 view 告诉 yii\web\ViewAction 请求哪个视图。然后,该操作将在 @app/views/site/pages 目录下查找此视图。您可以配置 yii\web\ViewAction::$viewPrefix 来更改搜索这些视图的目录。

最佳实践

视图负责以最终用户所需的方式呈现模型。一般来说,视图

  • 主要应该包含表现性代码,例如 HTML 和简单的 PHP 代码来遍历、格式化和呈现数据。
  • 不应包含执行数据库查询的代码。此类代码应在模型中完成。
  • 应该避免直接访问请求数据,例如 $_GET$_POST。这属于控制器。如果需要请求数据,控制器应该将它们推送到视图中。
  • 可以读取模型属性,但不应修改它们。

为了使视图更易于管理,请避免创建过于复杂或包含过多冗余代码的视图。您可以使用以下技术来实现此目标。

  • 使用 布局 来表示常见的表现性部分(例如页面页眉、页脚)。
  • 将一个复杂的视图分解为几个较小的视图。可以使用我们已描述的渲染方法渲染这些较小的视图并将它们组装成一个更大的视图。
  • 创建并使用 小部件 作为视图的构建块。
  • 创建并使用辅助类来转换和格式化视图中的数据。

发现错别字或您认为此页面需要改进?
在 github 上编辑它 !