介绍(Introduction) 入门(Getting Started) 应用结构(Application Structure) 请求处理(Handling Requests) 关键概念(Key Concepts) 配合数据库工作(Working with Databases) 接收用户数据(Getting Data from Users) 显示数据(Displaying Data) 安全(Security) 缓存(Caching) RESTful Web 服务 开发工具(Development Tools) 测试(Testing) 高级专题(Special Topics) 小部件(Widgets) 助手类(Helpers)

数据小部件

Yii提供了一套数据小部件 widgets ,这些小部件可以用于显示数据。 DetailView 小部件能够用于显示一条记录数据, ListViewGridView 小部件能够用于显示一个拥有分页、 排序和过滤功能的一个列表或者表格。

DetailView

yii\widgets\DetailView 小部件显示的是单一 yii\widgets\DetailView::$model 数据的详情。

它非常适合用常规格式显示一个模型(例如在一个表格的一行中显示模型的每个属性)。 这里说的模型可以是 \yii\base\Model 或者其子类的一个实例,例如子类 active record,也可以是一个关联数组。

DetailView使用 yii\widgets\DetailView::$attributes 属性来决定显示模型哪些属性以及如何格式化。 可用的格式化选项,见 formatter section 章节。

一个典型的DetailView的使用方法如下:

echo DetailView::widget([
    'model' => $model,
    'attributes' => [
        'title',               // title attribute (in plain text)
        'description:html',    // description attribute formatted as HTML
        [                      // the owner name of the model
            'label' => 'Owner',
            'value' => $model->owner->name,
        ],
        'created_at:datetime', // creation date formatted as datetime
    ],
]);

ListView

yii\widgets\ListView 小部件用于显示数据提供者 data provider 提供的数据。 每个数据模型用指定的视图文件 yii\widgets\ListView::$itemView 来渲染。 因为它提供开箱即用式的(译者注:封装好的)分页、排序以及过滤这样一些特性, 所以它可以很方便地为最终用户显示信息并同时创建数据管理界面。

一个典型的用法如下例所示:

use yii\widgets\ListView;
use yii\data\ActiveDataProvider;

$dataProvider = new ActiveDataProvider([
    'query' => Post::find(),
    'pagination' => [
        'pageSize' => 20,
    ],
]);
echo ListView::widget([
    'dataProvider' => $dataProvider,
    'itemView' => '_post',
]);

_post 视图文件可包含如下代码:

<?php
use yii\helpers\Html;
use yii\helpers\HtmlPurifier;
?>
<div class="post">
    <h2><?= Html::encode($model->title) ?></h2>
    
    <?= HtmlPurifier::process($model->text) ?>    
</div>

在上面的视图文件中,当前的数据模型 $model 是可用的。另外,下面的这些变量也是可用的:

  • $key:混合类型,键的值与数据项相关联。
  • $index:整型,是由数据提供者返回的数组中以0起始的数据项的索引。
  • $widget:类型是ListView,是小部件的实例。

假如你需要传递附加数据到每一个视图中,你可以像下面这样用 yii\widgets\ListView::$viewParams 属性传递键值对:

echo ListView::widget([
    'dataProvider' => $dataProvider,
    'itemView' => '_post',
    'viewParams' => [
        'fullView' => true,
        'context' => 'main-page',
        // ...
    ],
]);

在视图中,上述这些附加数据也是可以作为变量来使用的。

GridView

数据网格或者说 GridView 小部件是Yii中最强大的部件之一。如果你需要快速建立系统的管理后台, GridView 非常有用。它从数据提供者 data provider 中取得数据并使用 yii\grid\GridView::columns 属性的一组列配置,在一个表格中渲染每一行数据。

表中的每一行代表一个数据项的数据,并且一列通常表示该项的属性 (某些列可以对应于属性或静态文本的复杂表达式)。

使用GridView的最少代码如下:

use yii\grid\GridView;
use yii\data\ActiveDataProvider;

$dataProvider = new ActiveDataProvider([
    'query' => Post::find(),
    'pagination' => [
        'pageSize' => 20,
    ],
]);
echo GridView::widget([
    'dataProvider' => $dataProvider,
]);

上面的代码首先创建了一个数据提供者,然后使用GridView显示每一行的每个属性,每一行的数据是从数据提供者取来的。 展现出来的表格封装了排序以及分页功能。

表格列

表格的列是通过 yii\grid\Column 类来配置的,这个类是通过 GridView 配置项中的 yii\grid\GridView::columns 属性配置的。根据列的类别和设置的不同,各列能够以不同方式展示数据。 默认的列类是 yii\grid\DataColumn,用于展现模型的某个属性, 并且可以排序和过滤。

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],
        // 数据提供者中所含数据所定义的简单的列
        // 使用的是模型的列的数据
        'id',
        'username',
        // 更复杂的列数据
        [
            'class' => 'yii\grid\DataColumn', //由于是默认类型,可以省略 
            'value' => function ($data) {
                return $data->name; // 如果是数组数据则为 $data['name'] ,例如,使用 SqlDataProvider 的情形。
            },
        ],
    ],
]);

请注意,假如配置中没有指定 yii\grid\GridView::columns 属性, 那么Yii会试图显示数据提供者的模型中所有可能的列。

列类

通过使用不同类,网格列可以自定义:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        [
            'class' => 'yii\grid\SerialColumn', // <-- 这里
            // 你还可以在此配置其他属性
        ],

除了我们下面将要展开讨论的Yii自带的列类,你还可以创建你自己的列类。

每个列类是从 yii\grid\Column 扩展而来, 从而在配置网格列的时候,你可以设置一些公共的选项。

  • yii\grid\Column::header 允许为头部行设置内容。
  • yii\grid\Column::footer 允许为尾部行设置内容。
  • yii\grid\Column::visible 定义某个列是否可见
  • yii\grid\Column::content 允许你传递一个有效的PHP回调来为一行返回数据,格式如下:

    function ($model, $key, $index, $column) {
        return 'a string';
    }
    

你可以传递数组来指定各种容器式的HTML选项:

  • yii\grid\Column::headerOptions
  • yii\grid\Column::footerOptions
  • yii\grid\Column::filterOptions
  • yii\grid\Column::contentOptions

数据列

yii\grid\DataColumn 用于显示和排序数据。这是默认的列的类型, 所以在使用 DataColumn 为列类时,可省略类的指定(译者注:不需要'class'选项的意思)。

数据列的主要配置项是 yii\grid\DataColumn::format 属性。它的值对应于 formatter application component 应用组件里面的一些方法, 默认是使用 \yii\i18n\Formatter 应用组件:

echo GridView::widget([
    'columns' => [
        [
            'attribute' => 'name',
            'format' => 'text'
        ],
        [
            'attribute' => 'birthday',
            'format' => ['date', 'php:Y-m-d']
        ],
    ],
]); 

在上面的代码中,text 对应于 \yii\i18n\Formatter::asText()。列的值作为第一个参数传递。 在第二列的定义中,date 对应于 \yii\i18n\Formatter::asDate()。 同样地,列值也是通过第一个参数传递的,而 'php:Y-m-d' 用作第二个参数的值。

可用的格式化方法列表,请参照 section about Data Formatting

数据列配置,还有一个”快捷格式化串”的方法,详情见API文档 yii\grid\GridView::columns。 (译者注:举例说明, "name:text:Name" 快捷格式化串,表示列名为 name 格式为 text 显示标签是 Name

动作列

yii\grid\ActionColumn 用于显示一些动作按钮,如每一行的更新、删除操作。

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        [
            'class' => 'yii\grid\ActionColumn',
            // you may configure additional properties here
        ],

可配置的属性如下:

  • yii\grid\ActionColumn::controller 是应该执行这些动作的控制器ID。 如果没有设置,它将使用当前控制器。
  • yii\grid\ActionColumn::template 定义在动作列中使用的构建每个单元格的模板。 在大括号内括起来的的令牌被当做是控制器的 action 方法ID (在动作列的上下文中也称作按钮名称)。 它们将会被 yii\grid\ActionColumn::$buttons 中指定的对应按钮的关联的渲染回调函数替代。 例如,令牌 {view} 将被 buttons['view'] 关联的渲染回调函数的返回结果所替换。 如果没有找到回调函数,令牌将被替换成一个空串。默认的令牌有 {view} {update} {delete}
  • yii\grid\ActionColumn::buttons 是一个按钮的渲染回调数数组。数组中的键是按钮的名字(没有花括号),并且值是对应的按钮渲染回调函数。 这些回调函数须使用下面这种原型:

    function ($url, $model, $key) {
        // return the button HTML code
    }
    

    在上面的代码中,$url 是列为按钮创建的URL,$model是当前要渲染的模型对象, 并且 $key 是在数据提供者数组中模型的键。

  • yii\grid\ActionColumn::urlCreator 是使用指定的模型信息来创建一个按钮URL的回调函数。 该回调的原型和 yii\grid\ActionColumn::createUrl() 是一样的。 假如这个属性没有设置,按钮的URL将使用 yii\grid\ActionColumn::createUrl() 来创建。

  • yii\grid\ActionColumn::visibleButtons is an array of visibility conditions for each button. The array keys are the button names (without curly brackets), and the values are the boolean true/false or the anonymous function. When the button name is not specified in this array it will be shown by default. The callbacks must use the following signature:

    function ($model, $key, $index) {
        return $model->status === 'editable';
    }
    

    Or you can pass a boolean value:

    [
        'update' => \Yii::$app->user->can('update')
    ]
    

复选框列

yii\grid\CheckboxColumn 显示一个复选框列。

想要添加一个复选框到网格视图中,将它添加到 yii\grid\GridView::$columns 的配置中,如下所示:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        // ...
        [
            'class' => 'yii\grid\CheckboxColumn',
            // 你可以在这配置更多的属性
        ],
    ],

用户可点击复选框来选择网格中的一些行。被选择的行可通过调用下面的 JavaScript代码来获得:

var keys = $('#grid').yiiGridView('getSelectedRows');
// keys 为一个由与被选行相关联的键组成的数组

序号列

yii\grid\SerialColumn 渲染行号,以 1 起始并自动增长。

使用方法和下面的例子一样简单:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'], // <-- here
        // ...

数据排序

注意:这部分正在开发中。

数据过滤

为了过滤数据的 GridView 需要一个模型 model 来 从过滤表单接收数据,以及调整数据提供者的查询对象,以满足搜索条件。 使用活动记录 active records 时,通常的做法是 创建一个能够提供所需功能的搜索模型类(可以使用 Gii 来生成)。 这个类为搜索定义了验证规则并且提供了一个将会返回数据提供者对象 的 search() 方法。

为了给 Post 模型增加搜索能力,我们可以像下面的例子一样创建 PostSearch 模型:

<?php

namespace app\models;

use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;

class PostSearch extends Post
{
    public function rules()
    {
        // 只有在 rules() 函数中声明的字段才可以搜索
        return [
            [['id'], 'integer'],
            [['title', 'creation_date'], 'safe'],
        ];
    }

    public function scenarios()
    {
        // 旁路在父类中实现的 scenarios() 函数
        return Model::scenarios();
    }

    public function search($params)
    {
        $query = Post::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        // 从参数的数据中加载过滤条件,并验证
        if (!($this->load($params) && $this->validate())) {
            return $dataProvider;
        }

        // 增加过滤条件来调整查询对象
        $query->andFilterWhere(['id' => $this->id]);
        $query->andFilterWhere(['like', 'title', $this->title])
              ->andFilterWhere(['like', 'creation_date', $this->creation_date]);

        return $dataProvider;
    }
}

Tip: See Query Builder and especially Filter Conditions to learn how to build filtering query.

你可以在控制器中使用如下方法为网格视图获取数据提供者:

$searchModel = new PostSearch();
$dataProvider = $searchModel->search(Yii::$app->request->get());

return $this->render('myview', [
    'dataProvider' => $dataProvider,
    'searchModel' => $searchModel,
]);

然后你在视图中将 $dataProvider$searchModel 对象分派给 GridView 小部件:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        // ...
    ],
]);

Separate filter form

Most of the time using GridView header filters is enough, but in case you need a separate filter form, you can easily add it as well. You can create partial view _search.php with the following contents:

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

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

<div class="post-search">
    <?php $form = ActiveForm::begin([
        'action' => ['index'],
        'method' => 'get',
    ]); ?>

    <?= $form->field($model, 'title') ?>

    <?= $form->field($model, 'creation_date') ?>

    <div class="form-group">
        <?= Html::submitButton('Search', ['class' => 'btn btn-primary']) ?>
        <?= Html::submitButton('Reset', ['class' => 'btn btn-default']) ?>
    </div>

    <?php ActiveForm::end(); ?>
</div>

and include it in index.php view like so:

<?= $this->render('_search', ['model' => $searchModel]) ?>

Note: if you use Gii to generate CRUD code, the separate filter form (_search.php) is generated by default, but is commented in index.php view. Uncomment it and it's ready to use!

Separate filter form is useful when you need to filter by fields, that are not displayed in GridView or for special filtering conditions, like date range. For filtering by date range we can add non DB attributes createdFrom and createdTo to the search model:

class PostSearch extends Post
{
    /**
     * @var string
     */
    public $createdFrom;

    /**
     * @var string
     */
    public $createdTo;
}

Extend query conditions in the search() method like so:

$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom])
      ->andFilterWhere(['<=', 'creation_date', $this->createdTo]);

And add the representative fields to the filter form:

<?= $form->field($model, 'creationFrom') ?>

<?= $form->field($model, 'creationTo') ?>

处理关系型模型

当我们在一个网格视图中显示活动数据的时候,你可能会遇到这种情况,就是显示关联表的列的值, 例如:发帖者的名字,而不是显示他的 id。当 Post 模型有一个关联的属性名(译者注: Post 模型中用 hasOne 定义 getAuthor() 函数) 叫 author 并且作者模型(译者注:本例的作者模型是 users )有一个属性叫 name, 那么你可以通过在 yii\grid\GridView::$columns 中定义属性名为 author.name 来处理。 这时的网格视图能显示作者名了,但是默认是不支持按作者名排序和过滤的。 你需要调整上个章节介绍的 PostSearch 模型,以添加此功能。

为了使关联列能够排序,你需要连接关系表, 以及添加排序规则到数据提供者的排序组件中:

$query = Post::find();
$dataProvider = new ActiveDataProvider([
    'query' => $query,
]);

// 连接与 `users` 表相关联的 `author` 表
// 并将 `users` 表的别名设为 `author`
$query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]);
// since version 2.0.7, the above line can be simplified to $query->joinWith('author AS author');
// 使得关联字段可以排序
$dataProvider->sort->attributes['author.name'] = [
    'asc' => ['author.name' => SORT_ASC],
    'desc' => ['author.name' => SORT_DESC],
];

// ...

过滤也需要像上面一样调用joinWith方法。你也需要在属性和规则中定义该列,就像下面这样:

public function attributes()
{
    // 添加关联字段到可搜索属性集合
    return array_merge(parent::attributes(), ['author.name']);
}

public function rules()
{
    return [
        [['id'], 'integer'],
        [['title', 'creation_date', 'author.name'], 'safe'],
    ];
}

然后在 search() 方法中,你仅需要添加一个额外过滤条件:

$query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name')]);

信息:在上面的代码中,我们使用相同的字符串作为关联名称和表别名; 然而,当你的表别名和关联名称不相同的时候,你得注意在哪使用你的别名,在哪使用你的关联名称。 一个简单的规则是在每个构建数据库查询的地方使用别名,而在所有其他和定义相关的诸如: >attributes()rules() 等地方使用关联名称。

>例如,你使用 au 作为作者关系表的别名,那么联查语句就要写成像下面这样:

>$query->joinWith(['author' => function($query) { $query->from(['au' => 'users']); }]);
>```

当别名已经在关联函数中定义了时,也可以只调用 `$query->joinWith(['author']);`。

在过滤条件中,别名必须使用,但属性名称保持不变:

```php
$query->andFilterWhere(['LIKE', 'au.name', $this->getAttribute('author.name')]);

排序定义也同样如此:

$dataProvider->sort->attributes['author.name'] = [
     'asc' => ['au.name' => SORT_ASC],
     'desc' => ['au.name' => SORT_DESC],
];

同样,当指定使用 yii\data\Sort::defaultOrder 来排序的时候, >你需要使用关联名称替代别名:

$dataProvider->sort->defaultOrder = ['author.name' => SORT_ASC];

信息:更多关于 joinWith 和在后台执行查询的相关信息, 可以查看 active record docs on joining with relations

SQL视图用于过滤、排序和显示数据

还有另外一种方法可以更快、更有用 - SQL 视图。例如,我们要在 GridView 中显示用户和他们的简介,可以这样创建 SQL 视图:

CREATE OR REPLACE VIEW vw_user_info AS
    SELECT user.*, user_profile.lastname, user_profile.firstname
    FROM user, user_profile
    WHERE user.id = user_profile.user_id

然后你需要创建活动记录模型来代表这个视图:


namespace app\models\views\grid;

use yii\db\ActiveRecord;

class UserView extends ActiveRecord
{

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'vw_user_info';
    }

    public static function primaryKey()
    {
        return ['id'];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            // 在这定义你的规则
        ];
    }

    /**
     * @inheritdoc
     */
    public static function attributeLabels()
    {
        return [
            // 在这定义你的属性标签
        ];
    }


}

之后你可以使用这个 UserView 活动记录和搜索模型,无需附加的排序和过滤属性的规则。 所有属性都可开箱即用。请注意,这种方法有利有弊:

  • 你不需要指定不同排序和过滤条件,一切都包装好了;
  • 它可以更快,因为数据的大小,SQL查询的执行(对于每个关联数据你都不需要额外的查询)都得到优化;
  • 因为在SQL视图中这仅仅是一个简单的映射UI,所以在你的实体中,它可能缺乏某方面的逻辑,所以,假如你有一些诸如 isActiveisDeleted 或者其他影响到UI的方法, 你也需要在这个类中复制他们。

单个页面多个网格视图部件

你可以在一个单独页面中使用多个网格视图,但是一些额外的配置是必须的,为的就是它们相互之间不干扰。 当使用多个网格视图实例的时候,你必须要为生成的排序和分页对象配置不同的参数名, 以便于每个网格视图有它们各自独立的排序和分页。 你可以通过设置 yii\data\Sort::sortParam 和 yii\data\Pagination::pageParam,对应于数据提供者的 yii\data\BaseDataProvider::$sort 和 yii\data\BaseDataProvider::$pagination 实例。

假如我们想要同时显示 PostUser 模型,这两个模型已经在 $userProvider$postProvider 这两个数据提供者中准备好, 具体做法如下:

use yii\grid\GridView;

$userProvider->pagination->pageParam = 'user-page';
$userProvider->sort->sortParam = 'user-sort';

$postProvider->pagination->pageParam = 'post-page';
$postProvider->sort->sortParam = 'post-sort';

echo '<h1>Users</h1>';
echo GridView::widget([
    'dataProvider' => $userProvider,
]);

echo '<h1>Posts</h1>';
echo GridView::widget([
    'dataProvider' => $postProvider,
]);

Using GridView with Pjax

The yii\widgets\Pjax widget allows you to update a certain section of a page instead of reloading the entire page. You can use it to to update only the yii\grid\GridView content when using filters.

use yii\widgets\Pjax;
use yii\grid\GridView;

Pjax::begin([
    // PJax options
]);
    Gridview::widget([
        // GridView options
    ]);
Pjax::end();

Pjax also works for the links inside the yii\widgets\Pjax widget and for the links specified by yii\widgets\Pjax::$linkSelector. But this might be a problem for the links of an yii\grid\ActionColumn. To prevent this, add the HTML attribute data-pjax="0" to the links when you edit the yii\grid\ActionColumn::$buttons property.

GridView/ListView with Pjax in Gii

Since 2.0.5, the CRUD generator of Gii has an option called $enablePjax that can be used via either web interface or command line.

yii gii/crud --controllerClass="backend\\controllers\PostController" \
  --modelClass="common\\models\\Post" \
  --enablePjax=1

Which generates a yii\widgets\Pjax widget wrapping the yii\grid\GridView or yii\widgets\ListView widgets.

Further reading

在线
158