yii2-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\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;
    }
}

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

$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' => [
        // ...
    ],
]);

处理关系型模型

当我们在一个网格视图中显示活动数据的时候,你可能会遇到这种情况,就是显示关联表的列的值,例如:发帖者的名字,而不是显示他的 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']); }]);
// 使得关联字段可以排序
$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']);

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

$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

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

待定

Yii 2.0: yii2-ckeditor-widget

Installation

Skins & Plugins

Usage

Further Information

Resources

Installation

The preferred way to install this extension is throughcomposer.

Either run php composer.phar require "2amigos/yii2-ckeditor-widget" "*" or add "2amigos/yii2-ckeditor-widget" : "*"

to the require section of your application’s composer.json file.

Skins & Plugins

This widget works with default’s dev-full/stable branch of CKEditor, with a set of plugins and skins. If you wish to configure a different skins or plugins that the one proposed, you will have to download them separately and configure the widget’s clientOptions attribute accordingly.

Usage

The library comes with two widgets: CKEditor and CKEditorInline. One is for classic edition and the other for inline editing respectively.

Using a model with a basic preset:

use dosamigos\ckeditor\CKEditor;
 
<?= $form->field($model, 'text')->widget(CKEditor::className(), [
        'options' => ['rows' => 6],        'preset' => 'basic'
    ]) ?>

Using inline edition with basic preset:

use dosamigos\ckeditor\CKEditorInline;
 
<?php CKEditorInline::begin(['preset' => 'basic']);?>
    This text can be edited now :)
<?php CKEditorInline::end();?>Your code here...

Further Information

Please, check the CKEditor plugin site documentation for further information about its configuration options.

Resources

2amigOS!
web development has never been so fun
www.2amigos.us

Be the first person to leave a comment

Please  to leave your comment.

关于Yii2中CSS,JS文件的引入心得

在yii2中,由于yii2版本升级导致了,很多yii2的用法跟yii1有着很大的区别,这几天一直在view层的视图界面徘徊着,遇到什么问题呢? 问题就是搞不清我该如何去引入CSS,JS文件了!也看了社区中其它有经验的大神的教程,也按着做了,但是还是有一些问题存在着,比如说yii2的项目打开后,头部和尾部是公共的,该如何去掉?以及如何才能不改动原main.php文件的情况下,去引入JS,CSS文件,也许有一种办法就是写一个xxxAsset.php的配置文件,然后通过xxx Asset::register($this)可以引入文件,但是这下我又遇到问题了,碰到了这句代码无效,不起作用,在firebug打开后,head里没有引入任何的CSS,JS文件,样式也变得乱七八糟,后来我又查了下相关资料,恰巧,下了一个其他人发的yii2后台模版,于是,今天早上我大概了看了下后台的样式是怎么布局的,总结了下:
1、在前台view中最简单不过的就是像之前那样一个文件一个文件的引入,于是在顶部使用use调用代码段

use yii\helpers\Html;

然后在下面的Html中可以这样调用

<?=Html::jsFile('@web/***/js/***.js')?>//这里***代表你的目录名或者文件名<?=Html::cssFile('@web/***/css/***.css')?>//***同上

这样的话就不需要动其他文件,直接引入文件就好了,需要哪个引入哪个,当然这样写的话就是每次得写很多行代码去加载,最好还是写到配置文件中,但是用配置文件来引入这个问题我暂时还没弄通,后面如果找到原因我会分享给大家
2、前台这样引入,那么在controller中怎么自定义样式文件呢
在控制器中加上以下代码

public $layout = 'layout';//在类中定义一个变量,名为$layout

注意的是这个layout在你的view中有个目录叫layouts,在这个目录下,我新建了一个文件名为layout.php,在其中我加上一句代码

<?php echo $content; ?>

这样控制器就会自动去找当前视图目录下的layouts目录下的加载视图文件的php文件
以上的几行简短的代码就解决了新手不知道该如何去加载CSS,JS文件的问题,大家如果觉得写***Asset.php文件会有问题,就用我这种办法,后期等熟悉了yii2之后在改用其他的办法去加载
另外,我再补充下,在view中怎么去跳转链接到其他的视图文件
同样在顶部先引入类库

use yii\helpers\Url;

然后再需要链接跳转的地方这样写:

<?phpecho Url::toRoute('post/index');?>//post为你的当前控制器名,index为view模版

是不是特别简单啊!
我也是yii2的新手,希望把这一点点心得分享给同样是yii2迷途路上的新手们.

yii2-Active Record 详解(下)

AR的生命周期

理解AR的生命周期对于你操作数据库非常重要。生命周期通常都会有些典型的事件存在。对于开发AR的behaviors来说非常有用。

当你实例化一个新的AR对象时,我们将获得如下的生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 会触发一个 yii\db\ActiveRecord::EVENT_INIT 事件

当你通过 yii\db\ActiveRecord::find() 方法查询数据时,每个AR实例都将有以下生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 会触发一个 yii\db\ActiveRecord::EVENT_INIT 事件
  3. yii\db\ActiveRecord::afterFind(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_FIND 事件

当通过 yii\db\ActiveRecord::save() 方法写入或者更新数据时, 我们将获得如下生命周期:

  1. yii\db\ActiveRecord::beforeValidate(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE 事件
  2. yii\db\ActiveRecord::afterValidate(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_VALIDATE 事件
  3. yii\db\ActiveRecord::beforeSave(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_INSERT 或 yii\db\ActiveRecord::EVENT_BEFORE_UPDATE 事件
  4. 执行实际的数据写入或更新
  5. yii\db\ActiveRecord::afterSave(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_INSERT 或 yii\db\ActiveRecord::EVENT_AFTER_UPDATE 事件

最后,当调用 yii\db\ActiveRecord::delete() 删除数据时, 我们将获得如下生命周期:

  1. yii\db\ActiveRecord::beforeDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_DELETE 事件
  2. 执行实际的数据删除
  3. yii\db\ActiveRecord::afterDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_DELETE 事件

查询关联的数据

使用 AR 方法也可以查询数据表的关联数据(如,选出表A的数据可以拉出表B的关联数据)。 有了 AR, 返回的关联数据连接就像连接关联主表的 AR 对象的属性一样。

建立关联关系后,通过 $customer->orders 可以获取 一个 Order 对象的数组,该数组代表当前客户对象的订单集。

定义关联关系使用一个可以返回 yii\db\ActiveQuery 对象的 getter 方法, yii\db\ActiveQuery对象有关联上下文的相关信息,因此可以只查询关联数据。

例如:

class Customer extends \yii\db\ActiveRecord
{
    public function getOrders()
    {
        // 客户和订单通过 Order.customer_id -> id 关联建立一对多关系
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends \yii\db\ActiveRecord
{
    // 订单和客户通过 Customer.id -> customer_id 关联建立一对一关系
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

以上使用了 yii\db\ActiveRecord::hasMany() 和 yii\db\ActiveRecord::hasOne() 方法。 以上两例分别是关联数据多对一关系和一对一关系的建模范例。 如,一个客户有很多订单,一个订单只归属一个客户。 两个方法都有两个参数并返回 yii\db\ActiveQuery 对象。

  • $class:关联模型类名,它必须是一个完全合格的类名。
  • $link: 两个表的关联列,应为键值对数组的形式。 数组的键是 $class 关联表的列名, 而数组值是关联类 $class 的列名。 基于表外键定义关联关系是最佳方法。

建立关联关系后,获取关联数据和获取组件属性一样简单, 执行以下相应getter方法即可:

// 取得客户的订单
$customer = Customer::findOne(1);
$orders = $customer->orders; // $orders 是 Order 对象数组

以上代码实际执行了以下两条 SQL 语句:

SELECT * FROM customer WHERE id=1;
SELECT * FROM order WHERE customer_id=1;

提示:再次用表达式 $customer->orders将不会执行第二次 SQL 查询, SQL 查询只在该表达式第一次使用时执行。 数据库访问只返回缓存在内部前一次取回的结果集,如果你想查询新的 关联数据,先要注销现有结果集:unset($customer->orders);

有时候需要在关联查询中传递参数,如不需要返回客户全部订单, 只需要返回购买金额超过设定值的大订单, 通过以下getter方法声明一个关联数据 bigOrders

class Customer extends \yii\db\ActiveRecord
{
    public function getBigOrders($threshold = 100)
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where('subtotal > :threshold', [':threshold' => $threshold])
            ->orderBy('id');
    }
}

hasMany() 返回 yii\db\ActiveQuery 对象,该对象允许你通过 yii\db\ActiveQuery 方法定制查询。

如上声明后,执行 $customer->bigOrders 就返回 总额大于100的订单。使用以下代码更改设定值:

$orders = $customer->getBigOrders(200)->all();

>注意:关联查询返回的是 yii\db\ActiveQuery 的实例,如果像特性(如类属性)那样连接关联数据, 返回的结果是关联查询的结果,即 yii\db\ActiveRecord 的实例, 或者是数组,或者是 null ,取决于关联关系的多样性。如,$customer->getOrders() 返回 ActiveQuery实例,而 $customer->orders 返回Order 对象数组 (如果查询结果为空则返回空数组)。

中间关联表

有时,两个表通过中间表关联,定义这样的关联关系, 可以通过调用 yii\db\ActiveQuery::via() 方法或 yii\db\ActiveQuery::viaTable() 方法来定制 yii\db\ActiveQuery 对象 。

举例而言,如果 order 表和 item 表通过中间表 order_item 关联起来, 可以在 Order 类声明 items 关联关系取代中间表:

class Order extends \yii\db\ActiveRecord
{
    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
}

两个方法是相似的,除了 yii\db\ActiveQuery::via() 方法的第一个参数是使用 AR 类中定义的关联名。 以上方法取代了中间表,等价于:

class Order extends \yii\db\ActiveRecord
{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }

    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}

延迟加载和即时加载(又称惰性加载与贪婪加载)

如前所述,当你第一次连接关联对象时, AR 将执行一个数据库查询 来检索请求数据并填充到关联对象的相应属性。 如果再次连接相同的关联对象,不再执行任何查询语句,这种数据库查询的执行方法称为“延迟加载”。如:

// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// 没有 SQL 语句被执行
$orders2 = $customer->orders; //取回上次查询的缓存数据

延迟加载非常实用,但是,在以下场景中使用延迟加载会遭遇性能问题:

// SQL executed: SELECT * FROM customer LIMIT 100
$customers = Customer::find()->limit(100)->all();

foreach ($customers as $customer) {
    // SQL executed: SELECT * FROM order WHERE customer_id=...
    $orders = $customer->orders;
    // ...处理 $orders...
}

假设数据库查出的客户超过100个,以上代码将执行多少条 SQL 语句? 101 条!第一条 SQL 查询语句取回100个客户,然后, 每个客户要执行一条 SQL 查询语句以取回该客户的所有订单。

为解决以上性能问题,可以通过调用 yii\db\ActiveQuery::with() 方法使用即时加载解决。

// SQL executed: SELECT * FROM customer LIMIT 100;
//               SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)
    ->with('orders')->all();

foreach ($customers as $customer) {
    // 没有 SQL 语句被执行
    $orders = $customer->orders;
    // ...处理 $orders...
}

如你所见,同样的任务只需要两个 SQL 语句。 >须知:通常,即时加载 N 个关联关系而通过 via() 或者 viaTable() 定义了 M 个关联关系, 将有 1+M+N 条 SQL 查询语句被执行:一个查询取回主表行数, 一个查询给每一个 (M) 中间表,一个查询给每个 (N) 关联表。 注意:当用即时加载定制 select() 时,确保连接 到关联模型的列都被包括了,否则,关联模型不会载入。如:

$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
// $orders[0]->customer 总是空的,使用以下代码解决这个问题:
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();

有时候,你想自由的自定义关联查询,延迟加载和即时加载都可以实现,如:

$customer = Customer::findOne(1);
// 延迟加载: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
$orders = $customer->getOrders()->where('subtotal>100')->all();

// 即时加载: SELECT * FROM customer LIMIT 100
//          SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
$customers = Customer::find()->limit(100)->with([
    'orders' => function($query) {
        $query->andWhere('subtotal>100');
    },
])->all();

逆关系

关联关系通常成对定义,如:Customer 可以有个名为 orders 关联项, 而 Order 也有个名为customer 的关联项:

class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends ActiveRecord
{
    ....
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

如果我们执行以下查询,可以发现订单的 customer 和 找到这些订单的客户对象并不是同一个。连接 customer->orders 将触发一条 SQL 语句 而连接一个订单的 customer 将触发另一条 SQL 语句。

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 输出 "不相同"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

为避免多余执行的后一条语句,我们可以为 customer或 orders 关联关系定义相反的关联关系,通过调用 yii\db\ActiveQuery::inverseOf() 方法可以实现。

class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
    }
}

现在我们同样执行上面的查询,我们将得到:

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 输出相同
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

以上我们展示了如何在延迟加载中使用相对关联关系, 相对关系也可以用在即时加载中:

// SELECT * FROM customer
// SELECT * FROM order WHERE customer_id IN (1, 2, ...)
$customers = Customer::find()->with('orders')->all();
// 输出相同
if ($customers[0]->orders[0]->customer === $customers[0]) {
    echo '相同';
} else {
    echo '不相同';
}

>注意:相对关系不能在包含中间表的关联关系中定义。 即是,如果你的关系是通过yii\db\ActiveQuery::via() 或 yii\db\ActiveQuery::viaTable()方法定义的, 就不能调用yii\db\ActiveQuery::inverseOf()方法了。

JOIN 类型关联查询

使用关系数据库时,普遍要做的是连接多个表并明确地运用各种 JOIN 查询。 JOIN SQL语句的查询条件和参数,使用 yii\db\ActiveQuery::joinWith() 可以重用已定义关系并调用 而不是使用 yii\db\ActiveQuery::join() 来实现目标。

// 查找所有订单并以客户 ID 和订单 ID 排序,并贪婪加载 "customer" 表
$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all();
// 查找包括书籍的所有订单,并以 `INNER JOIN` 的连接方式即时加载 "books" 表
$orders = Order::find()->innerJoinWith('books')->all();

以上,方法 yii\db\ActiveQuery::innerJoinWith() 是访问 INNER JOIN 类型的 yii\db\ActiveQuery::joinWith() 的快捷方式。

可以连接一个或多个关联关系,可以自由使用查询条件到关联查询, 也可以嵌套连接关联查询。如:

// 连接多重关系
// 找出24小时内注册客户包含书籍的订单
$orders = Order::find()->innerJoinWith([
    'books',
    'customer' => function ($query) {
        $query->where('customer.created_at > ' . (time() - 24 * 3600));
    }
])->all();
// 连接嵌套关系:连接 books 表及其 author 列
$orders = Order::find()->joinWith('books.author')->all();

代码背后, Yii 先执行一条 JOIN SQL 语句把满足 JOIN SQL 语句查询条件的主要模型查出, 然后为每个关系执行一条查询语句, bing填充相应的关联记录。

yii\db\ActiveQuery::joinWith() 和 yii\db\ActiveQuery::with() 的区别是 前者连接主模型类和关联模型类的数据表来检索主模型, 而后者只查询和检索主模型类。 检索主模型

由于这个区别,你可以应用只针对一条 JOIN SQL 语句起效的查询条件。 如,通过关联模型的查询条件过滤主模型,如前例, 可以使用关联表的列来挑选主模型数据,

当使用 yii\db\ActiveQuery::joinWith() 方法时可以响应没有歧义的列名。 In the above examples, we use item.id and order.id to disambiguate the id column references 因为订单表和项目表都包括 id 列。

当连接关联关系时,关联关系默认使用即时加载。你可以 通过传参数 $eagerLoading 来决定在指定关联查询中是否使用即时加载。

默认 yii\db\ActiveQuery::joinWith() 使用左连接来连接关联表。 你也可以传 $joinType 参数来定制连接类型。 你也可以使用 yii\db\ActiveQuery::innerJoinWith()。

以下是 INNER JOIN 的简短例子:

// 查找包括书籍的所有订单,但 "books" 表不使用即时加载
$orders = Order::find()->innerJoinWith('books', false)->all();
// 等价于:
$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();

有时连接两个表时,需要在关联查询的 ON 部分指定额外条件。 这可以通过调用 yii\db\ActiveQuery::onCondition() 方法实现:

class User extends ActiveRecord
{
    public function getBooks()
    {
        return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
    }
}

在上面, yii\db\ActiveRecord::hasMany() 方法回传了一个 yii\db\ActiveQuery 对象, 当你用 yii\db\ActiveQuery::joinWith() 执行一条查询时,取决于正被调用的是哪个 yii\db\ActiveQuery::onCondition(), 返回 category_id 为 1 的 items

当你用 yii\db\ActiveQuery::joinWith() 进行一次查询时,“on-condition”条件会被放置在相应查询语句的 ON 部分, 如:

// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1
// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1
$users = User::find()->joinWith('books')->all();

注意:如果通过 yii\db\ActiveQuery::with() 进行贪婪加载或使用惰性加载的话,则 on 条件会被放置在对应 SQL语句的 WHERE 部分。 因为,此时此处并没有发生 JOIN 查询。比如:

// SELECT * FROM user WHERE id=10
$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;

关联表操作

AR 提供了下面两个方法用来建立和解除两个关联对象之间的关系:

  • yii\db\ActiveRecord::link()
  • yii\db\ActiveRecord::unlink()

例如,给定一个customer和order对象,我们可以通过下面的代码使得customer对象拥有order对象:

$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);

yii\db\ActiveRecord::link() 调用上述将设置 customer_id 的顺序是 $customer 的主键值,然后调用 yii\db\ActiveRecord::save() 要将顺序保存到数据库中。

作用域

当你调用yii\db\ActiveRecord::find() 或 yii\db\ActiveRecord::findBySql()方法时,将会返回一个yii\db\ActiveQuery实例。之后,你可以调用其他查询方法,如 yii\db\ActiveQuery::where(),yii\db\ActiveQuery::orderBy(), 进一步的指定查询条件。

有时候你可能需要在不同的地方使用相同的查询方法。如果出现这种情况,你应该考虑定义所谓的作用域。作用域是本质上要求一组的查询方法来修改查询对象的自定义查询类中定义的方法。 之后你就可以像使用普通方法一样使用作用域。

只需两步即可定义一个作用域。首先给你的model创建一个自定义的查询类,在此类中定义的所需的范围方法。例如,给Comment模型创建一个 CommentQuery类,然后在CommentQuery类中定义一个active()的方法为作用域,像下面的代码:

namespace app\models;

use yii\db\ActiveQuery;

class CommentQuery extends ActiveQuery
{
    public function active($state = true)
    {
        $this->andWhere(['active' => $state]);
        return $this;
    }
}

重点:

  1. 类必须继承 yii\db\ActiveQuery (或者是其他的 ActiveQuery ,比如 yii\mongodb\ActiveQuery)。
  2. 必须是一个public类型的方法且必须返回 $this 实现链式操作。可以传入参数。
  3. 检查 yii\db\ActiveQuery 对于修改查询条件是非常有用的方法。

其次,覆盖yii\db\ActiveRecord::find() 方法使其返回自定义的查询对象而不是常规的yii\db\ActiveQuery。对于上述例子,你需要编写如下代码:

namespace app\models;

use yii\db\ActiveRecord;

class Comment extends ActiveRecord
{
    /**
     * @inheritdoc
     * @return CommentQuery
     */
    public static function find()
    {
        return new CommentQuery(get_called_class());
    }
}

就这样,现在你可以使用自定义的作用域方法了:

$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();

你也能在定义的关联里使用作用域方法,比如:

class Post extends \yii\db\ActiveRecord
{
    public function getActiveComments()
    {
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active();

    }
}

或者在执行关联查询的时候使用(on-the-fly 是啥?):

$posts = Post::find()->with([
    'comments' => function($q) {
        $q->active();
    }
])->all();

默认作用域

如果你之前用过 Yii 1.1 就应该知道默认作用域的概念。一个默认的作用域可以作用于所有查询。你可以很容易的通过重写yii\db\ActiveRecord::find()方法来定义一个默认作用域,例如:

public static function find()
{
    return parent::find()->where(['deleted' => false]);
}

注意,你之后所有的查询都不能用 yii\db\ActiveQuery::where(),但是可以用 yii\db\ActiveQuery::andWhere() 和 yii\db\ActiveQuery::orWhere(),他们不会覆盖掉默认作用域。(译注:如果你要使用默认作用域,就不能在 xxx::find()后使用where()方法,你必须使用andXXX()或者orXXX()系的方法,否则默认作用域不会起效果,至于原因,打开where()方法的代码一看便知)

事务操作

当执行几个相关联的数据库操作的时候

TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226

, yii\db\ActiveRecord::afterSave(), yii\db\ActiveRecord::beforeDelete() and/or yii\db\ActiveRecord::afterDelete() 生命周期周期方法(life cycle methods 我觉得这句翻译成“模板方法”会不会更好点?)。开发者可以通过重写yii\db\ActiveRecord::save()方法然后在控制器里使用事务操作,严格地说是似乎不是一个好的做法 (召回”瘦控制器 / 肥模型”基本规则)。

这些方法在这里(如果你不明白自己实际在干什么,请不要使用他们),Models:

class Feature extends \yii\db\ActiveRecord
{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['id' => 'product_id']);
    }
}

class Product extends \yii\db\ActiveRecord
{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['product_id' => 'id']);
    }
}

重写 yii\db\ActiveRecord::save() 方法:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

(译注:我觉得上面应该是原手册里的bug)

在控制器层使用事务:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

作为这些脆弱方法的替代,你应该使用原子操作方案特性。

class Feature extends \yii\db\ActiveRecord
{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['product_id' => 'id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['name', 'value'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }
}

class Product extends \yii\db\ActiveRecord
{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['id' => 'product_id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['title', 'price'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }

    public function afterValidate()
    {
        parent::afterValidate();
        // FIXME: TODO: WIP, TBD
    }

    public function afterSave($insert)
    {
        parent::afterSave($insert);
        if ($this->getScenario() === 'userCreates') {
            // FIXME: TODO: WIP, TBD
        }
    }
}

Controller里的代码将变得很简洁:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

控制器非常简洁:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

乐观锁(Optimistic Locks)

TODO

被污染属性

当你调用yii\db\ActiveRecord::save()用于保存活动记录(Active Record)实例时,只有被污染的属性才会被保存。一个属性是否认定为被污染取决于它的值自从最后一次从数据库加载或者最近一次保存到数据库后到现在是否被修改过。注意:无论活动记录(Active Record)是否有被污染属性,数据验证始终会执行。

活动记录(Active Record)会自动维护一个污染数据列表。它的工作方式是通过维护一个较旧属性值版本,并且将它们与最新的进行比较。你可以通过调用yii\db\ActiveRecord::getDirtyAttributes()来获取当前的污染属性。你也可以调用yii\db\ActiveRecord::markAttributeDirty()来显示的标记一个属性为污染属性。

如果你对最近一次修改前的属性值感兴趣,你可以调用yii\db\ActiveRecord::getOldAttributes() 或 yii\db\ActiveRecord::getOldAttribute()。

另见

yii2-ActiveRecord 详解(上)

Active Record (活动记录,以下简称AR)提供了一个面向对象的接口, 用以访问数据库中的数据。一个 AR 类关联一张数据表, 每个 AR 对象对应表中的一行,对象的属性(即 AR 的特性Attribute)映射到数据行的对应列。 一条活动记录(AR对象)对应数据表的一行,AR对象的属性则映射该行的相应列。 您可以直接以面向对象的方式来操纵数据表中的数据,妈妈再不用担心我需要写原生 SQL 语句啦。

例如,假定 Customer AR 类关联着 customer 表,且该类的 name 属性代表 customer 表的 name 列。 你可以写以下代码来哉 customer 表里插入一行新的记录:

用 AR 而不是原生的 SQL 语句去执行数据库查询,可以调用直观方法来实现相同目标。如,调用 yii\db\ActiveRecord::save() 方法将执行插入或更新轮询,将在该 AR 类关联的数据表新建或更新一行数据:

$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();  // 一行新数据插入 customer 表

上面的代码和使用下面的原生 SQL 语句是等效的,但显然前者更直观, 更不易出错,并且面对不同的数据库系统(DBMS, Database Management System)时更不容易产生兼容性问题。

$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [
    ':name' => 'Qiang',
])->execute();

下面是所有目前被 Yii 的 AR 功能所支持的数据库列表:

  • MySQL 4.1 及以上:通过 yii\db\ActiveRecord
  • PostgreSQL 7.3 及以上:通过 yii\db\ActiveRecord
  • SQLite 2 和 3:通过 yii\db\ActiveRecord
  • Microsoft SQL Server 2010 及以上:通过 yii\db\ActiveRecord
  • Oracle: 通过 yii\db\ActiveRecord
  • CUBRID 9.1 及以上:通过 yii\db\ActiveRecord
  • Sphinx:通过 yii\sphinx\ActiveRecord,需求 yii2-sphinx 扩展
  • ElasticSearch:通过 yii\elasticsearch\ActiveRecord,需求 yii2-elasticsearch 扩展
  • Redis 2.6.12 及以上:通过 yii\redis\ActiveRecord,需求 yii2-redis 扩展
  • MongoDB 1.3.0 及以上:通过 yii\mongodb\ActiveRecord,需求 yii2-mongodb 扩展

如你所见,Yii 不仅提供了对关系型数据库的 AR 支持,还提供了 NoSQL 数据库的支持。 在这个教程中,我们会主要描述对关系型数据库的 AR 用法。 然而,绝大多数的内容在 NoSQL 的 AR 里同样适用。

声明 AR 类

要想声明一个 AR 类,你需要扩展 yii\db\ActiveRecord 基类, 并实现 tableName 方法,返回与之相关联的的数据表的名称:

namespace app\models;

use yii\db\ActiveRecord;

class Customer extends ActiveRecord
{
    /**
     * @return string 返回该AR类关联的数据表名
     */
    public static function tableName()
    {
        return 'customer';
    }
}

访问列数据

AR 把相应数据行的每一个字段映射为 AR 对象的一个个特性变量(Attribute) 一个特性就好像一个普通对象的公共属性一样(public property)。 特性变量的名称和对应字段的名称是一样的,且大小姓名。

使用以下语法读取列的值:

// "id" 和 "mail" 是 $customer 对象所关联的数据表的对应字段名
$id = $customer->id;
$email = $customer->email;

要改变列值,只要给关联属性赋新值并保存对象即可:

$customer->email = 'james@example.com';
$customer->save();

建立数据库连接

AR 用一个 yii\db\Connection 对象与数据库交换数据。 默认的,它使用 db 组件作为其连接对象。详见数据库基础章节, 你可以在应用程序配置文件中设置下 db 组件,就像这样,

return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=testdb',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ],
];

如果在你的应用中应用了不止一个数据库,且你需要给你的 AR 类使用不同的数据库链接(DB connection) ,你可以覆盖掉 yii\db\ActiveRecord::getDb() 方法:

class Customer extends ActiveRecord
{
    // ...

    public static function getDb()
    {
        return \Yii::$app->db2;  // 使用名为 "db2" 的应用组件
    }
}

查询数据

AR 提供了两种方法来构建 DB 查询并向 AR 实例里填充数据:

  • yii\db\ActiveRecord::find()
  • yii\db\ActiveRecord::findBySql()

以上两个方法都会返回 yii\db\ActiveQuery 实例,该类继承自yii\db\Query, 因此,他们都支持同一套灵活且强大的 DB 查询方法,如 where()join()orderBy(),等等。 下面的这些案例展示了一些可能的玩法:

// 取回所有活跃客户(状态为 *active* 的客户)并以他们的 ID 排序:
$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();

// 返回ID为1的客户:
$customer = Customer::find()
    ->where(['id' => 1])
    ->one();

// 取回活跃客户的数量:
$count = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->count();

// 以客户ID索引结果集:
$customers = Customer::find()->indexBy('id')->all();
// $customers 数组以 ID 为索引

// 用原生 SQL 语句检索客户:
$sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();

小技巧:在上面的代码中,Customer::STATUS_ACTIVE 是一个在 Customer 类里定义的常量。(译注:这种常量的值一般都是tinyint)相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。

有两个快捷方法:findOnefindAll() 用来返回一个或者一组ActiveRecord实例。前者返回第一个匹配到的实例,后者返回所有。 例如:

// 返回 id 为 1 的客户
$customer = Customer::findOne(1);

// 返回 id 为 1 且状态为 *active* 的客户
$customer = Customer::findOne([
    'id' => 1,
    'status' => Customer::STATUS_ACTIVE,
]);

// 返回id为1、2、3的一组客户
$customers = Customer::findAll([1, 2, 3]);

// 返回所有状态为 "deleted" 的客户
$customer = Customer::findAll([
    'status' => Customer::STATUS_DELETED,
]);

以数组形式获取数据

有时候,我们需要处理很大量的数据,这时可能需要用一个数组来存储取到的数据, 从而节省内存。你可以用 asArray() 函数做到这一点:

// 以数组而不是对象形式取回客户信息:
$customers = Customer::find()
    ->asArray()
    ->all();
// $customers 的每个元素都是键值对数组

批量获取数据

Query Builder(查询构造器) 里,我们已经解释了当需要从数据库中查询大量数据时,你可以用 batch query(批量查询)来限制内存的占用。 你可能也想在 AR 里使用相同的技巧,比如这样……

// 一次提取 10 个客户信息
foreach (Customer::find()->batch(10) as $customers) {
    // $customers 是 10 个或更少的客户对象的数组
}
// 一次提取 10 个客户并一个一个地遍历处理
foreach (Customer::find()->each(10) as $customer) {
    // $customer 是一个 ”Customer“ 对象
}
// 贪婪加载模式的批处理查询
foreach (Customer::find()->with('orders')->each() as $customer) {
}

操作数据

AR 提供以下方法插入、更新和删除与 AR 对象关联的那张表中的某一行:

  • yii\db\ActiveRecord::save()
  • yii\db\ActiveRecord::insert()
  • yii\db\ActiveRecord::update()
  • yii\db\ActiveRecord::delete()

AR 同时提供了一下静态方法,可以应用在与某 AR 类所关联的整张表上。 用这些方法的时候千万要小心,因为他们作用于整张表! 比如,deleteAll() 会删除掉表里所有的记录。

  • yii\db\ActiveRecord::updateCounters()
  • yii\db\ActiveRecord::updateAll()
  • yii\db\ActiveRecord::updateAllCounters()
  • yii\db\ActiveRecord::deleteAll()

下面的这些例子里,详细展现了如何使用这些方法:

// 插入新客户的记录
$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->insert();

// 更新现有客户记录
$customer = Customer::findOne($id);
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->update();

// 删除已有客户记录
$customer = Customer::findOne($id);
$customer->delete();

// 删除多个年龄大于20,性别为男(Male)的客户记录
Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);

// 所有客户的age(年龄)字段加1:
Customer::updateAllCounters(['age' => 1]);

须知:save() 方法会调用 insert()update() 中的一个, 用哪个取决于当前 AR 对象是不是新对象(在函数内部,他会检查 yii\db\ActiveRecord::isNewRecord 的值)。 若 AR 对象是由 new 操作符 初始化出来的,save() 方法会在表里插入一条数据; 如果一个 AR 是由 find() 方法获取来的, 则 save()更新表里的对应行记录。

数据输入与有效性验证

由于AR继承自yii\base\Model,所以它同样也支持Model的数据输入、验证等特性。例如,你可以声明一个rules方法用来覆盖掉yii\base\Model::rules()里的;你也可以给AR实例批量赋值;你也可以通过调用yii\base\Model::validate()执行数据验证。

当你调用 save()insert()update() 这三个方法时,会自动调用yii\base\Model::validate()方法。如果验证失败,数据将不会保存进数据库。

下面的例子演示了如何使用AR 获取/验证用户输入的数据并将他们保存进数据库:

// 新建一条记录
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 获取用户输入的数据,验证并保存
}

// 更新主键为$id的AR
$model = Customer::findOne($id);
if ($model === null) {
    throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 获取用户输入的数据,验证并保存
}

读取默认值

你的表列也许定义了默认值。有时候,你可能需要在使用web表单的时候给AR预设一些值。如果你需要这样做,可以在显示表单内容前通过调用loadDefaultValues()方法来实现: `php $customer = new Customer(); $customer->loadDefaultValues(); // … 渲染 $customer 的 HTML 表单 … `

Yii2安装

你可以通过两种方式安装 Yii:使用 Composer 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的扩展或更新 Yii 了。

注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。

通过 Composer 安装

如果还没有安装 Composer,你可以按 getcomposer.org 中的方法安装。在 Linux 和 Mac OS X 中可以运行如下命令:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

在 Windows 中,你需要下载并运行 Composer-Setup.exe

如果遇到任何问题或者想更深入地学习 Composer,请参考 Composer 文档(英文)Composer 中文

如果你已经安装有 Composer 请确保使用的是最新版本,你可以用 composer self-update 命令更新 Composer 为最新版本。

Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下命令即可安装 Yii :

composer global require "fxp/composer-asset-plugin:~1.0.0"
composer create-project --prefer-dist yiisoft/yii2-app-basic basic

第一条命令安装 Composer asset plugin,它是通过 Composer 管理 bower 和 npm 包所必须的,此命令全局生效,一劳永逸。 第二条命令会将 Yii 安装在名为 basic 的目录中,你也可以随便选择其他名称。

注意:在安装过程中 Composer 可能会询问你 GitHub 账户的登录信息,因为可能在使用中超过了 GitHub API (对匿名用户的)使用限制。因为 Composer 需要为所有扩展包从 GitHub 中获取大量信息,所以超限非常正常。(译注:也意味着作为程序猿没有 GitHub 账号,就真不能愉快地玩耍了)登陆 GitHub 之后可以得到更高的 API 限额,这样 Composer 才能正常运行。更多细节请参考 Composer 文档(该段 Composer 中文文档期待您的参与)。

技巧:如果你想安装 Yii 的最新开发版本,可以使用以下命令代替,它添加了一个 stability 选项中文版):

composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic

注意,Yii 的开发版(dev 版)不应该用于生产环境中,它可能会破坏运行中的代码。

通过归档文件安装

通过归档文件安装 Yii 包括三个步骤:

  1. yiiframework.com 下载归档文件。
  2. 将下载的文件解压缩到 Web 目录中。
  3. 修改 config/web.php 文件,给 cookieValidationKey 配置项添加一个密钥(若你通过 Composer 安装,则此步骤会自动完成):
    // !!! 在下面插入一段密钥(若为空) - 以供 cookie validation 的需要
    'cookieValidationKey' => '在此处输入你的密钥',
    

其他安装方式

上文介绍了两种安装 Yii 的方法,安装的同时也会创建一个立即可用的 Web 应用程序。对于小的项目或用于学习上手,这都是一个不错的起点。

但是其他的安装方式也存在:

  • 如果你只想安装核心框架,然后从零开始构建整个属于你自己的应用程序模版,可以参考从头构建自定义模版一节的介绍。
  • 如果你要开发一个更复杂的应用,可以更好地适用于团队开发环境的,可以考虑安装高级应用模版

验证安装的结果

安装完成后,就可以使用浏览器通过如下 URL 访问刚安装完的 Yii 应用了:

http://localhost/basic/web/index.php

这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 basic 目录中,且该 Web 服务器正运行在你自己的电脑上(localhost)。你可能需要将其调整为适应自己的安装环境。

Yii 安装成功

你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请通过以下任意一种方式,检查当前 PHP 环境是否满足 Yii 最基本需求:

  • 通过浏览器访问 URL http://localhost/basic/requirements.php
  • 执行如下命令:
    cd basic
    php requirements.php
    

你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。主要是需要 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 PDO PHP 扩展 和相应的数据库驱动(例如访问 MySQL 数据库所需的 pdo_mysql)。

配置 Web 服务器

>补充:如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。

通过上述方法安装的应用程序在 Windows,Max OS X,Linux 中的 Apache HTTP 服务器Nginx HTTP 服务器且PHP版本为5.4或更高都可以直接运行。Yii 2.0 也兼容 Facebook 公司的 HHVM,由于 HHVM 和标准 PHP 在边界案例上有些地方略有不同,在使用 HHVM 时需稍作处理。

在生产环境的服务器上,你可能会想配置服务器让应用程序可以通过 URL http://www.example.com/index.php 访问而不是 http://www.example.com/basic/web/index.php。这种配置需要将 Web 服务器的文档根目录指向 basic/web 目录。可能你还会想隐藏掉 URL 中的 index.php,具体细节在 URL 解析和生成一章中有介绍,你将学到如何配置 Apache 或 Nginx 服务器实现这些目标。

>补充:将 basic/web 设置为文档根目录,可以防止终端用户访问 basic/web 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。

>补充:如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考共享主机环境 一章。

推荐使用的 Apache 配置

在 Apache 的 httpd.conf 文件或在一个虚拟主机配置文件中使用如下配置。注意,你应该将 path/to/basic/web 替换为实际的 basic/web 目录。

# 设置文档根目录为 “basic/web”
DocumentRoot "path/to/basic/web"

<Directory "path/to/basic/web">
    # 开启 mod_rewrite 用于美化 URL 功能的支持(译注:对应 pretty URL 选项)
    RewriteEngine on
    # 如果请求的是真实存在的文件或目录,直接访问
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # 如果请求的不是真实文件或目录,分发请求至 index.php
    RewriteRule . index.php

    # ...其它设置...
</Directory>

推荐使用的 Nginx 配置

为了使用 Nginx,你应该已经将 PHP 安装为 FPM SAPI 了。使用如下 Nginx 配置,将 path/to/basic/web 替换为实际的 basic/web 目录,mysite.local 替换为实际的主机名以提供服务。

server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## 监听 ipv4 上的 80 端口
    #listen [::]:80 default_server ipv6only=on; ## 监听 ipv6 上的 80 端口

    server_name mysite.local;
    root        /path/to/basic/web;
    index       index.php;

    access_log  /path/to/basic/log/access.log main;
    error_log   /path/to/basic/log/error.log;

    location / {
        # 如果找不到真实存在的文件,把请求分发至 index.php
        try_files $uri $uri/ /index.php?$args;
    }

    # 若取消下面这段的注释,可避免 Yii 接管不存在文件的处理过程(404)
    #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
    #    try_files $uri =404;
    #}
    #error_page 404 /404.html;

    location ~ \.php$ {
        include fastcgi.conf;
        fastcgi_pass   127.0.0.1:9000;
        #fastcgi_pass unix:/var/run/php5-fpm.sock;
        try_files $uri =404;
    }

    location ~ /\.(ht|svn|git) {
        deny all;
    }
}

使用该配置时,你还应该在 php.ini 文件中设置 cgi.fix_pathinfo=0 ,能避免掉很多不必要的 stat() 系统调用。

还要注意当运行一个 HTTPS 服务器时,需要添加 fastcgi_param HTTPS on; 一行,这样 Yii 才能正确地判断连接是否安全。

yii-查询构建器

查询构建器建立在 Database Access Objects 基础之上,可让你创建 程序化的、DBMS无关的SQL语句。相比于原生的SQL语句,查询构建器可以帮你 写出可读性更强的SQL相关的代码,并生成安全性更强的SQL语句。

使用查询构建器通常包含以下两个步骤:

  1. 创建一个 yii\db\Query 对象来代表一条 SELECT SQL 语句的不同子句(例如 SELECT, FROM)。
  2. 执行 yii\db\Query 的一个查询方法(例如:all())从数据库当中检索数据。

如下所示代码是查询构造器的一个典型用法:

$rows = (new \yii\db\Query())
    ->select(['id', 'email'])
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->limit(10)
    ->all();

上面的代码将会生成并执行如下的SQL语句,其中 :last_name 参数绑定了 字符串 'Smith'

SELECT `id`, `email` 
FROM `user`
WHERE `last_name` = :last_name
LIMIT 10

提示: 你平时更多的时候会使用 yii\db\Query 而不是 [yii\db\QueryBuilder]]。 当你调用其中一个查询方法时,后者将会被前者隐式的调用。yii\db\QueryBuilder主要负责将 DBMS 不相关的 yii\db\Query 对象转换成 DBMS 相关的 SQL 语句(例如, 以不同的方式引用表或字段名称)。

创建查询

为了创建一个 yii\db\Query 对象,你需要调用不同的查询构建方法来代表SQL语句的不同子句。 这些方法的名称集成了在SQL语句相应子句中使用的关键字。例如,为了指定 SQL 语句当中的 FROM 子句,你应该调用 from() 方法。所有的查询构建器方法返回的是查询对象本身, 也就是说,你可以把多个方法的调用串联起来。

接下来,我们会对这些查询构建器方法进行一一讲解:

yii\db\Query::select()

yii\db\Query::select() 方法用来指定 SQL 语句当中的 SELECT 子句。 你可以像下面的例子一样使用一个数组或者字符串来定义需要查询的字段。当 SQL 语句 是由查询对象生成的时候,被查询的字段名称将会自动的被引号括起来。

$query->select(['id', 'email']);

// 等同于:

$query->select('id, email');

就像写原生 SQL 语句一样,被选取的字段可以包含表前缀,以及/或者字段别名。 例如:

$query->select(['user.id AS user_id', 'email']);

// 等同于:

$query->select('user.id AS user_id, email');

如果使用数组格式来指定字段,你可以使用数组的键值来表示字段的别名。 例如,上面的代码可以被重写为如下形式:

$query->select(['user_id' => 'user.id', 'email']);

如果你在组建查询时没有调用 yii\db\Query::select() 方法,那么选择的将是 '*' , 也即选取的是所有的字段。

除了字段名称以外,你还可以选择数据库的表达式。当你使用到包含逗号的数据库表达式的时候, 你必须使用数组的格式,以避免自动的错误的引号添加。例如:

$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']); 

从 2.0.1 的版本开始你就可以使用子查询了。在定义每一个子查询的时候, 你应该使用 yii\db\Query 对象。例如:

$subQuery = (new Query())->select('COUNT(*)')->from('user');

// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`
$query = (new Query())->select(['id', 'count' => $subQuery])->from('post');

你应该调用 yii\db\Query::distinct() 方法来去除重复行,如下所示:

// SELECT DISTINCT `user_id` ...
$query->select('user_id')->distinct();

你可以调用 yii\db\Query::addSelect() 方法来选取附加字段,例如:

$query->select(['id', 'username'])
    ->addSelect(['email']);

yii\db\Query::from()

yii\db\Query::from() 方法指定了 SQL 语句当中的 FROM 子句。例如:

// SELECT * FROM `user`
$query->from('user');

你可以通过字符串或者数组的形式来定义被查询的表名称。就像你写原生的 SQL 语句一样, 表名称里面可包含数据库前缀,以及/或者表别名。例如:

$query->from(['public.user u', 'public.post p']);

// 等同于:

$query->from('public.user u, public.post p');

如果你使用的是数组的格式,那么你同样可以用数组的键值来定义表别名,如下所示:

$query->from(['u' => 'public.user', 'p' => 'public.post']);

除了表名以外,你还可以从子查询中再次查询,这里的子查询是由 yii\db\Query 创建的对象。 例如:

$subQuery = (new Query())->select('id')->from('user')->where('status=1');

// SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u 
$query->from(['u' => $subQuery]);

yii\db\Query::where()

yii\db\Query::where() 方法定义了 SQL 语句当中的 WHERE 子句。 你可以使用如下三种格式来定义 WHERE 条件:

  • 字符串格式,例如:'status=1'
  • 哈希格式,例如: ['status' => 1, 'type' => 2]
  • 操作符格式,例如:['like', 'name', 'test']

字符串格式

在定义非常简单的查询条件的时候,字符串格式是最合适的。它看起来和原生 SQL 语句差不多。例如:

$query->where('status=1');

// 或者使用参数绑定来绑定动态参数值
$query->where('status=:status', [':status' => $status]);

千万不要像如下的例子一样直接在条件语句当中嵌入变量,特别是当这些变量来源于终端用户输入的时候, 因为这样我们的软件将很容易受到 SQL 注入的攻击。

// 危险!千万别这样干,除非你非常的确定 $status 是一个整型数值。
$query->where("status=$status");

当使用参数绑定的时候,你可以调用 yii\db\Query::params() 或者 yii\db\Query::addParams() 方法 来分别绑定不同的参数。

$query->where('status=:status')
    ->addParams([':status' => $status]);

哈希格式

哈希格式最适合用来指定多个 AND 串联起来的简单的”等于断言”子条件。 它是以数组的形式来书写的,数组的键表示字段的名称,而数组的值则表示 这个字段需要匹配的值。例如:

// ...WHERE (`status` = 10) AND (`type` IS NULL) AND (`id` IN (4, 8, 15))
$query->where([
    'status' => 10,
    'type' => null,
    'id' => [4, 8, 15],
]);

就像你所看到的一样,查询构建器非常的智能,能恰当地处理数值当中的空值和数组。

你也可以像下面那样在子查询当中使用哈希格式:

$userQuery = (new Query())->select('id')->from('user');

// ...WHERE `id` IN (SELECT `id` FROM `user`)
$query->where(['id' => $userQuery]);

操作符格式

操作符格式允许你指定类程序风格的任意条件语句,如下所示:

[操作符, 操作数1, 操作数2, ...]

其中每个操作数可以是字符串格式、哈希格式或者嵌套的操作符格式,而操作符可以是如下列表中的一个:

  • and: 操作数会被 AND 关键字串联起来。例如,['and', 'id=1', 'id=2'] 将会生成 id=1 AND id=2。如果操作数是一个数组,它也会按上述规则转换成 字符串。例如,['and', 'type=1', ['or', 'id=1', 'id=2']] 将会生成 type=1 AND (id=1 OR id=2)。 这个方法不会自动加引号或者转义。
  • or: 用法和 and 操作符类似,这里就不再赘述。
  • between: 第一个操作数为字段名称,第二个和第三个操作数代表的是这个字段 的取值范围。例如,['between', 'id', 1, 10] 将会生成 id BETWEEN 1 AND 10
  • not between: 用法和 BETWEEN 操作符类似,这里就不再赘述。
  • in: 第一个操作数应为字段名称或者 DB 表达式。第二个操作符既可以是一个数组, 也可以是一个 Query 对象。它会转换成IN条件语句。如果第二个操作数是一个 数组,那么它代表的是字段或 DB 表达式的取值范围。如果第二个操作数是 Query 对象,那么这个子查询的结果集将会作为第一个操作符的字段或者 DB 表达式的取值范围。 例如, ['in', 'id', [1, 2, 3]] 将生成 id IN (1, 2, 3)。 该方法将正确地为字段名加引号以及为取值范围转义。in 操作符还支持组合字段,此时, 操作数1应该是一个字段名数组,而操作数2应该是一个数组或者 Query 对象, 代表这些字段的取值范围。
  • not in: 用法和 in 操作符类似,这里就不再赘述。
  • like: 第一个操作数应为一个字段名称或 DB 表达式,第二个操作数可以使字符串或数组, 代表第一个操作数需要模糊查询的值。比如,['like', 'name', 'tester'] 会生成 name LIKE '%tester%'。 如果范围值是一个数组,那么将会生成用 AND 串联起来的 多个 like 语句。例如,['like', 'name', ['test', 'sample']] 将会生成 name LIKE '%test%' AND name LIKE '%sample%'。 你也可以提供第三个可选的操作数来指定应该如何转义数值当中的特殊字符。 该操作数是一个从需要被转义的特殊字符到转义副本的数组映射。 如果没有提供这个操作数,将会使用默认的转义映射。如果需要禁用转义的功能, 只需要将参数设置为 false 或者传入一个空数组即可。需要注意的是, 当使用转义映射(又或者没有提供第三个操作数的时候),第二个操作数的值的前后 将会被加上百分号。

> 注意:当使用 PostgreSQL 的时候你还可以使用 ilike, > 该方法对大小写不敏感。

  • or like: 用法和 like 操作符类似,区别在于当第二个操作数为数组时, 会使用 OR 来串联多个 LIKE 条件语句。
  • not like: 用法和 like 操作符类似,区别在于会使用 NOT LIKE 来生成条件语句。
  • or not like: 用法和 not like 操作符类似,区别在于会使用 OR 来串联多个 NOT LIKE 条件语句。
  • exists: 需要一个操作数,该操作数必须是代表子查询 yii\db\Query 的一个实例, 它将会构建一个 EXISTS (sub-query) 表达式。
  • not exists: 用法和 exists 操作符类似,它将创建一个 NOT EXISTS (sub-query) 表达式。
  • >, <=, 或者其他包含两个操作数的合法 DB 操作符: 第一个操作数必须为字段的名称, 而第二个操作数则应为一个值。例如,['>', 'age', 10] 将会生成 age>10

附加条件

你可以使用 yii\db\Query::andWhere() 或者 yii\db\Query::orWhere() 在原有条件的基础上 附加额外的条件。你可以多次调用这些方法来分别追加不同的条件。 例如,

$status = 10;
$search = 'yii';

$query->where(['status' => $status]);

if (!empty($search)) {
    $query->andWhere(['like', 'title', $search]);
}

如果 $search 不为空,那么将会生成如下 SQL 语句:

... WHERE (`status` = 10) AND (`title` LIKE '%yii%')

过滤条件

WHERE 条件来自于用户的输入时,你通常需要忽略用户输入的空值。 例如,在一个可以通过用户名或者邮箱搜索的表单当中,用户名或者邮箱 输入框没有输入任何东西,这种情况下你想要忽略掉对应的搜索条件, 那么你就可以使用 yii\db\Query::filterWhere() 方法来实现这个目的:

// $username 和 $email 来自于用户的输入
$query->filterWhere([
    'username' => $username,
    'email' => $email,		
]);

yii\db\Query::filterWhere() 和 yii\db\Query::where() 唯一的不同就在于,前者 将忽略在条件当中的hash format的空值。所以如果 $email 为空而 $username 不为空,那么上面的代码最终将生产如下 SQL ...WHERE username=:username

提示:当一个值为 null、空数组、空字符串或者一个只包含空白字符时,那么它将被判定为空值。

类似于 [yii\db\Query::andWhere()|andWhere()]] 和 yii\db\Query::orWhere(), 你可以使用 yii\db\Query::andFilterWhere() 和 yii\db\Query::orFilterWhere() 方法 来追加额外的过滤条件。

yii\db\Query::orderBy()

yii\db\Query::orderBy() 方法是用来指定 SQL 语句当中的 ORDER BY 子句的。例如,

// ... ORDER BY `id` ASC, `name` DESC
$query->orderBy([
    'id' => SORT_ASC,
    'name' => SORT_DESC,
]);

如上所示,数组当中的键指代的是字段名称,而数组当中的值则表示的是排序的方式。 PHP 的常量 SORT_ASC 指的是升序排列,SORT_DESC 指的则是降序排列。

如果 ORDER BY 仅仅包含简单的字段名称,你可以使用字符串来声明它, 就像写原生的 SQL 语句一样。例如,

$query->orderBy('id ASC, name DESC');

注意:当 ORDER BY 语句包含一些 DB 表达式的时候,你应该使用数组的格式。

你可以调用 [yii\db\Query::addOrderBy()|addOrderBy()]] 来为 ORDER BY 片断添加额外的子句。 例如,

$query->orderBy('id ASC')
    ->addOrderBy('name DESC');

yii\db\Query::groupBy()

yii\db\Query::groupBy() 方法是用来指定 SQL 语句当中的 GROUP BY 片断的。例如,

// ... GROUP BY `id`, `status`
$query->groupBy(['id', 'status']);

如果 GROUP BY 仅仅包含简单的字段名称,你可以使用字符串来声明它, 就像写原生的 SQL 语句一样。例如,

$query->groupBy('id, status');

注意:当 GROUP BY 语句包含一些 DB 表达式的时候,你应该使用数组的格式。

你可以调用 [yii\db\Query::addOrderBy()|addOrderBy()]] 来为 GROUP BY 子句添加额外的字段。例如,

$query->groupBy(['id', 'status'])
    ->addGroupBy('age');

yii\db\Query::having()

yii\db\Query::having() 方法是用来指定 SQL 语句当中的 HAVING 子句。它带有一个条件, 和 where() 中指定条件的方法一样。例如,

// ... HAVING `status` = 1
$query->having(['status' => 1]);

请查阅 where() 的文档来获取更多有关于如何指定一个条件的细节。

你可以调用 yii\db\Query::andHaving() 或者 yii\db\Query::orHaving() 方法来为 HAVING 子句追加额外的条件,例如,

// ... HAVING (`status` = 1) AND (`age` > 30)
$query->having(['status' => 1])
    ->andHaving(['>', 'age', 30]);

yii\db\Query::limit() 和 yii\db\Query::offset()

yii\db\Query::limit() 和 yii\db\Query::offset() 是用来指定 SQL 语句当中 的 LIMITOFFSET 子句的。例如,

// ... LIMIT 10 OFFSET 20
$query->limit(10)->offset(20);

如果你指定了一个无效的 limit 或者 offset(例如,一个负数),那么它将会被忽略掉。

提示:在不支持 LIMITOFFSET 的 DBMS 中(例如,MSSQL), 查询构建器将生成一条模拟 LIMIT/OFFSET 行为的 SQL 语句。

yii\db\Query::join()

[yii\db\Query::join()|join()]] 是用来指定 SQL 语句当中的 JOIN 子句的。例如,
`php
// … LEFT JOIN post ON post.user_id = user.id
$query->join(‘LEFT JOIN’, ‘post’, ‘post.user_id = user.id’);
`

yii\db\Query::join() 带有四个参数:

  • $type: 连接类型,例如:'INNER JOIN', 'LEFT JOIN'
  • $table: 将要连接的表名称。
  • $on: 可选参数,连接条件,即 ON 子句。请查阅 where() 获取更多有关于条件定义的细节。
  • $params: 可选参数,与连接条件绑定的参数。

你可以分别调用如下的快捷方法来指定 INNER JOIN, LEFT JOINRIGHT JOIN

  • yii\db\Query::innerJoin()
  • yii\db\Query::leftJoin()
  • yii\db\Query::rightJoin()

例如,

$query->leftJoin('post', 'post.user_id = user.id');

可以通过多次调用如上所述的连接方法来连接多张表,每连接一张表调用一次。

除了连接表以外,你还可以连接子查询。方法如下,将需要被连接的子查询指定 为一个 yii\db\Query 对象,例如,

$subQuery = (new \yii\db\Query())->from('post');
$query->leftJoin(['u' => $subQuery], 'u.id = author_id');

在这个例子当中,你应该将子查询放到一个数组当中,而数组当中的键,则为这个子查询的别名。

yii\db\Query::union()

yii\db\Query::union() 方法是用来指定 SQL 语句当中的 UNION 子句的。例如,

$query1 = (new \yii\db\Query())
    ->select("id, category_id AS type, name")
    ->from('post')
    ->limit(10);

$query2 = (new \yii\db\Query())
    ->select('id, type, name')
    ->from('user')
    ->limit(10);

$query1->union($query2);

你可以通过多次调用 yii\db\Query::union() 方法来追加更多的 UNION 子句。

查询方法

yii\db\Query 提供了一整套的用于不同查询目的的方法。

  • yii\db\Query::all(): 将返回一个由行组成的数组,每一行是一个由名称和值构成的关联数组(译者注:省略键的数组称为索引数组)。
  • yii\db\Query::one(): 返回结果集的第一行。
  • yii\db\Query::column(): 返回结果集的第一列。
  • yii\db\Query::scalar(): 返回结果集的第一行第一列的标量值。
  • yii\db\Query::exists(): 返回一个表示该查询是否包结果集的值。
  • yii\db\Query::count(): 返回 COUNT 查询的结果。
  • 其它集合查询方法: 包括 yii\db\Query::sum(), yii\db\Query::average(), yii\db\Query::max(), yii\db\Query::min() 等. $q 是一个必选参数, 既可以是一个字段名称,又可以是一个 DB 表达式。

例如,

// SELECT `id`, `email` FROM `user`
$rows = (new \yii\db\Query())
    ->select(['id', 'email'])
    ->from('user')
    ->all();
    
// SELECT * FROM `user` WHERE `username` LIKE `%test%`
$row = (new \yii\db\Query())
    ->from('user')
    ->where(['like', 'username', 'test'])
    ->one();

注意:yii\db\Query::one() 方法只返回查询结果当中的第一条数据, 条件语句中不会加上 LIMIT 1 条件。如果你清楚的知道查询将会只返回一行或几行数据 (例如, 如果你是通过某些主键来查询的),这很好也提倡这样做。但是,如果查询结果 有机会返回大量的数据时,那么你应该显示调用 limit(1) 方法,以改善性能。 例如, (new \yii\db\Query())->from('user')->limit(1)->one()

所有的这些查询方法都有一个可选的参数 $db, 该参数指代的是 yii\db\Connection, 执行一个 DB 查询时会用到。如果你省略了这个参数,那么 db application component 将会被用作 默认的 DB 连接。 如下是另外一个使用 count() 查询的例子:

// 执行 SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name
$count = (new \yii\db\Query())
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->count();

当你调用 yii\db\Query 当中的一个查询方法的时候,实际上内在的运作机制如下:

  • 在当前 yii\db\Query 的构造基础之上,调用 yii\db\QueryBuilder 来生成一条 SQL 语句;
  • 利用生成的 SQL 语句创建一个 yii\db\Command 对象;
  • 调用 yii\db\Command 的查询方法(例如,queryAll())来执行这条 SQL 语句,并检索数据。

有时候,你也许想要测试或者使用一个由 yii\db\Query 对象创建的 SQL 语句。 你可以使用以下的代码来达到目的:

$command = (new \yii\db\Query())
    ->select(['id', 'email'])
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->limit(10)
    ->createCommand();
    
// 打印 SQL 语句
echo $command->sql;
// 打印被绑定的参数
print_r($command->params);

// 返回查询结果的所有行
$rows = $command->queryAll();

索引查询结果

当你在调用 yii\db\Query::all() 方法时,它将返回一个以连续的整型数值为索引的数组。 而有时候你可能希望使用一个特定的字段或者表达式的值来作为索引结果集数组。那么你可以在调用 yii\db\Query::all() 之前使用 yii\db\Query::indexBy() 方法来达到这个目的。 例如,

// 返回 [100 => ['id' => 100, 'username' => '...', ...], 101 => [...], 103 => [...], ...]
$query = (new \yii\db\Query())
    ->from('user')
    ->limit(10)
    ->indexBy('id')
    ->all();

如需使用表达式的值做为索引,那么只需要传递一个匿名函数给 yii\db\Query::indexBy() 方法即可:

$query = (new \yii\db\Query())
    ->from('user')
    ->indexBy(function ($row) {
        return $row['id'] . $row['username'];
    })->all();

该匿名函数将带有一个包含了当前行的数据的 $row 参数,并且返回用作当前行索引的 标量值(译者注:就是简单的数值或者字符串,而不是其他复杂结构,例如数组)。

批处理查询

当需要处理大数据的时候,像 yii\db\Query::all() 这样的方法就不太合适了, 因为它们会把所有数据都读取到内存上。为了保持较低的内存需求, Yii 提供了一个 所谓的批处理查询的支持。批处理查询会利用数据游标将数据以批为单位取出来。

批处理查询的用法如下:

use yii\db\Query;

$query = (new Query())
    ->from('user')
    ->orderBy('id');

foreach ($query->batch() as $users) {
    // $users 是一个包含100条或小于100条用户表数据的数组
}

// or if you want to iterate the row one by one
foreach ($query->each() as $user) {
    // $user 指代的是用户表当中的其中一行数据
}

yii\db\Query::batch() 和 yii\db\Query::each() 方法将会返回一个实现了Iterator 接口 yii\db\BatchQueryResult 的对象,可以用在 foreach 结构当中使用。在第一次迭代取数据的时候, 数据库会执行一次 SQL 查询,然后在剩下的迭代中,将直接从结果集中批量获取数据。默认情况下, 一批的大小为 100,也就意味着一批获取的数据是 100 行。你可以通过给 batch() 或者 each() 方法的第一个参数传值来改变每批行数的大小。

相对于 yii\db\Query::all() 方法,批处理查询每次只读取 100 行的数据到内存。 如果你在处理完这些数据后及时丢弃这些数据,那么批处理查询可以很好的帮助降低内存的占用率。

如果你通过 yii\db\Query::indexBy() 方法为查询结果指定了索引字段,那么批处理查询将仍然保持相对应的索引方案,例如,

$query = (new \yii\db\Query())
    ->from('user')
    ->indexBy('username');

foreach ($query->batch() as $users) {
    // $users 的 “username” 字段将会成为索引
}

foreach ($query->each() as $username => $user) {
}

Yii2-ueditor-widget

百度UEditor

GitHub地址 https://github.com/BigKuCha/yii2-ueditor-widget

安装

Either run

$ php composer.phar require kucha/ueditor "*"

or add

"kucha/ueditor": "*"

to the require section of your composer.json file.

应用

controller:

public function actions(){
return [
'upload' => [
'class' => 'kucha\ueditor\UEditorAction',
        ]
    ];
}

view:

echo \kucha\ueditor\UEditor::widget([]);

或者:

echo $form->field($model,'colum')->widget('kucha\ueditor\UEditor',[]);

说明

ueditor只支持2种语言,en-uszh-cn,默认跟随系统语言 Yii::$app->language,可以通过2种方式设置,1.修改系统语言,在main.php(高级版) 或者web.php(基础版)添加'language' => 'zh-CN',。2.实例化的时候配置语言选项,见下边配置

配置相关

编辑器相关配置,请在view 中配置,参数为clientOptions,比如定制菜单,编辑器大小等等,具体参数请查看UEditor官网文档

简单实例:

use \kucha\ueditor\UEditor;
echo UEditor::widget([
'clientOptions' => [        //编辑区域大小
        'initialFrameHeight' => '200',        //设置语言
        'lang' =>'en', //中文为 zh-cn
        //定制菜单
        'toolbars' => [
            [
'fullscreen', 
'source',
 'undo', 
'redo', 
'|',
'fontsize',
'bold',
 'italic', 
'underline', 
'fontborder', 
'strikethrough',
 'removeformat',
'formatmatch',
 'autotypeset',
 'blockquote', 
'pasteplain', '|',
'forecolor', 'backcolor', '|',
'lineheight', '|','indent', '|'
            ],
        ]
]);
文件上传相关配置,请在controller中配置,参数为config,例如文件上传路径等;更多参数请参照 config.php (跟UEditor提供的config.json一样)

简单实例:

public function actions(){
return [
'upload' => [
'class' => 'kucha\ueditor\UEditorAction',
 'config' => [ "imageUrlPrefix"  => "http://www.baidu.com",//图片访问路径前缀
                "imagePathFormat" => "/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}" //上传保存路径
            ],
        ]
    ];
}