Yii2学习笔记

控制器

控制器周期

beforeAction

1
2
3
4
5
6
7
8
9
10
11
12
/**
* action 运行的前置操作
*
* 比较适合统一化的数据验证,请求数据的接受等
* @param \yii\base\Action $action action 类
* @return bool 返回ture 控制器周期才会走下去
*/
public function beforeAction($action)
{
var_dump($action->id, $action->controller->id);
return parent::beforeAction($action); // TODO: Change the autogenerated stub
}

afterAction

1
2
3
4
5
6
7
8
9
10
11
12
/**
* action 后置操作
*
* 比较适合返回最后的日志搜集
* @param \yii\base\Action $action action 类
* @param mixed $result
* @return mixed
*/
public function afterAction($action, $result)
{
return parent::afterAction($action, $result); // TODO: Change the autogenerated stub
}

模型

AR 模型 常用方法

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
// 查询单条
$one = Post::find()->where(['id' => 1])->one();
// 或者
$one = Post::finOne(1);

// 查询多条
$posts = Post::find()->where(['status' => 1])->all();
// 或者
$posts = Post::findAll(['status' => 1]);

// 通过sql查询
$posts = Post::findBySql('SELECT * FROM `posts` WHERE `status` = :status', [':status' => 1])->all();

查询条件的设定

where 查询支持的参数

  • 字符串格式 例如: ‘status=1’

    1
    2
    3
    4
    // 对应的SQL: WHERE status = 1
    $query->where('status = 1');
    // 使用字符串方式,可以绑定参数
    $query->where('status = :status', [':status' => 1])
  • 哈希格式 例如: [‘status’ => 1, ‘type’ => 1]

哈希格式最适合用来指定多个 AND 串联起来的简单的”等于断言”子条件。

1
2
3
4
// 对应的SQL: WHERE (`status`=1) AND (`type`=1)
$query->where(['status' => 1, 'type' => 1])
// 如果值为一个数组会转为IN 对应SQL: WHERE (`status`=1) AND (`type` IN (1, 2, 3))
$query->where(['status' => 1, 'type' => [1, 2, 3]])

  • 操作符格式 例如: [‘=’, ‘status’, 1]

可以实现比较复杂的查询条件

  1. 格式

    1
    ['操作符', 操作数1, 操作数2, ...];
  2. 演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 简单单个查询 对应的SQL: WHERE `status` >= 1
    $query->where(['>=', 'status', 1])

    // 多个条件 AND 连接 对应的SQL: WHERE (`status` >= 1) AND (`type` <= 3)
    $query->where(['and', ['>=', 'status', 1], ['<=', 'type', 3]])

    // 多个条件 OR 连接 对应SQL: WHERE (`status` IN (1, 3)) OR (`type` >= 2)
    $query->where('or', ['in', 'status', [1, 3], ['>=', 'type', 2]])

    // 比较复杂组合的SQL 对应SQL: WHERE (`create_type` = 6) AND (`status` = 0) AND (`is_fail` = 0) AND (((`last_view_time` = 0) AND (`create_time` <= 1503562511)) OR ((`last_view_time` > 0) AND (`last_view_time` <= 1503562511)))
    $query->where([
    'and',
    ['=', 'create_type', 6],
    ['=', 'status', 0],
    ['=', 'is_fail', 0],
    ['or',
    ['and', ['=', 'last_view_time', 0], ['<=', 'create_time', time()]],
    ['and', ['>', 'last_view_time', 0], ['<=', 'last_view_time', time()]]
    ]
    ]);
  3. 操作符说明

    • and 和 or 确定多个条件通过什么方式连接
    • between 和 not between

      1
      2
      // 数组需要四个元素 对应SQL: WHERE `id` BETWEEN 1 AND 10
      ['between', 'id', 1, 10]
    • in 和 not in

      1
      2
      // 对应SQL: WHERE `status` IN (1, 3)
      ['in', 'status', [1, 3]]
    • like

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 默认 对应SQL: WHERE `name` LIKE '%username%'
      ['like', 'name', 'username']

      /**
      * 第二个范围值是一个数组,那么将会生成用 AND 串联起来的 多个 like 语句
      * 对应SQL: WHERE `name` LIKE '%user%' AND `name` LIKE '%name%'
      */
      ['like', 'name', ['user', 'name']]

      // 使用前缀查询 对应SQL: WHERE `name` LIKE 'user%'
      ['like', 'name', 'user%', false]
    • or like 与like 相同,只是第二个范围值是一个数组,那么将会生成用 OR 串联起来的 多个 like 语句

      1
      2
      3
      4
      5
      // 对应的SQL: WHERE `name` LIKE '%user%' OR `name` LIKE '%name%'
      ['or like', 'name', ['user', 'name']]

      // 自己定义前缀查询还是后缀查询 对应SQL: WHERE `name` LIKE 'user%' OR `name` LIKE '%name'
      ['or like', 'name', ['user%', '%name'], false]
    • not like 和 or not like 和 like 与 or like 用法相同

    • exists 和 not exists 需要一个操作数,该操作数必须是代表子查询 yii\db\Query 的一个实例
      1
      2
      3
      4
      // 生成的SQL: WHERE EXISTS (SELECT * FROM `user` WHERE `status` = 1)
      $queryOne = new Query();
      $queryOne->from('user')->where(['=', 'status', 1])->one();
      $query->where(['exists', $queryOne])
- \>, >=, =, <, <=
  • andWhere 和 orWhere 在原有条件的基础上 附加额外的条件。你可以多次调用这些方法来分别追加不同的条件

    1
    2
    3
    4
    5
    6
    7
    // 对应SQL: WHERE (`status` = 1) AND (`type` >= 1)
    $query->where(['=', 'status', 1]);
    $query->andWhere(['>=', 'type', 1]);

    // 对应的SQL: WHERE ((`status`=1) AND (`type`=1)) OR (`name`='username')
    $query->where(['status' => 1, 'type' => 1]);
    $query->orWhere(['name' => 'username']);
  • filterWhere 过滤查询(在用户输入查询的时候比较好用) 会忽略空值(null, ‘’, ‘ ‘, [])

    1
    2
    3
    4
    5
    6
    7
    // 不会添加查询条件
    $status = ''; // null, ' ', [] 都不会添加查询条件
    $query->filterWhere(['status' => $status]);

    // 添加查询条件: WHERE `status` = 0
    $status = 0;
    $query->filterWhere(['status' => $status]);

建议查询时候直接使用where() 方法中传递数组的方式定义where条件; andWhere() 和 orWhere() 会增加判断

查询对象转数组

asArray() 方法

在对查询数据没有其他操作的情况下,建议都转为数组(能够节省内存)

1
2
// 查询出来是一个数组
$posts = Post::find()->where(['status' => 1])->asArray()->all();

指定数组的键

indexBy() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$users = User::find()->where(['status' => 1])->asArray()->indexBy('id')->all();

// 查询出来的数组
[
'1' => [
'id' => 1,
'status' => 1,
'username' => 'username'
],
'3' => [
'id' => 3,
'status' => 1,
'username' => 'my name'
],
];

// 这样查询出来的数组,判断指定ID的用户是否存在比较方法(特别是循环中)
if (isset($user[1])) {
....
}

使用场景

现在后台程序需要显示文章信息,并且需要显示文章的作者信息

两个表的结构

文章表 article
字段名 | 字段说明
—|—
id | 文章ID
user_id | 文章作者ID
title | 文章标题

用户表 user

字段名 字段说明
id 用户ID
name 用户姓名
  • 方案一 使用表关联

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // Article 模型类中定义关联关系
    class Article extends ActiveRecord
    {
    public function getUser()
    {
    return $this->hasOne(User::className(), ['id' => 'user_id']);
    }
    }

    /**
    * 使用
    *
    * 说明这里也是只使用了两次查询
    * 第一次: SELECT * FROM `article` LIMIT 10
    * 第二次: SELECT * FROM `user` WHERE `id` IN (1, 2, ...)
    */
    $arrArticles = Article::find()->with('user')->limit(10)->asArray()->all();
    if ($arrArticles) {
    foreach ($arrArticles as $article) {
    if (isset($article['user'])) {
    // 使用作者名称
    $article['user']['name'];
    }
    }
    }
  • 方案二 使用indexBy()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 第一步查询出文章信息
    $arrArticles = Article::find()->limit(10)->asArray()->all();

    // 第二步获取到用户信息
    if ($arrArticles) {
    // 获取到用户ID
    $arrUserIds = array_column($arrArticles, 'user_id');
    // 查询到客户信息
    $arrUsers = User::find()->where(['id' => $arrUserIds])->indexBy('id')->asArray()->all();
    // 三 将用户名称追加到文章信息中
    foreach ($arrArticles as &$value) {
    $value['user_name'] = '';
    if (isset($arrUsers[$value['user_id']])) {
    $value['user_name'] = $arrUsers[$value['user_id']]['name'];
    }
    }
    }

批处理查询

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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() 方法的第一个参数传值来改变每批行数的大小。

查看执行语句

1
2
3
4
5
6
7
8
// 可以通过yii 自带的 debug 工具查看

// 通过model 类
// 查看执行SQL: SELECT * FROM `user` WHERE `status` IN (1, 2)
var_dump(User::find()->where(['status' => [1, 2, 3]])->createCommand()->getRawSql());

// 查看预处理的SQL: SELECT * FROM `user` WHERE `status` IN (:qp0, :qp1)
var_dump(User::find()->where(['status' => [1, 2, 3]])->createCommand()->getSql());

验证

验证规则定义

定义格式

1
2
3
4
5
6
7
8
// 第一个字段可以为字符串,也可以是数组
[[验证字段], 验证规则, 其他配置项...]

// 为字符串的时候必须为单个字段
['username', 'required', 'message' => '用户名不能为空']

// 多个字段验证
[['username', 'password'], 'required']

例子:

1
2
3
4
5
6
7
8
9
10
// 在模型的 rules 方法中定义
public function rules()
{
return [
// 不能为空
[['username', 'passwrod'], 'required'],
// 必须唯一
[['username'], 'unique'],
];
}

验证规则类型

说明
一般提示信息可以不用填写,会自动匹配 attributeLabels() 方法返回的字段说明信息

  • required: 必须值验证

    1
    2
    // requiredValue, message 可以不用填写
    [['字段名'], 'required', 'requiredValue' => '必填值', 'message' => '提示信息']
  • email 邮箱格式验证

    1
    ['email', 'email']
  • match 正则表达事验证

    1
    2
    3
    [['字段名'], 'match', 'pattern' => '正则表达式']
    // 正则取反
    [['字段名'], 'match', 'pattern' => '正则表达式', 'not' => true]
  • url 网址信息验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 验证https 或者 http 的网址
    ['字段名', 'url']

    /**
    * 输入字段可以不带 http 前缀,默认帮你添加
    * 输入的值可以是 www.baidu.com 也可以是 http://www.baidu.com
    * 输入 www.baidu.com 会帮你加上 http:// 最终为 http://www.baidu.com
    */
    [['字段名'], 'url', 'defaultScheme' => 'http']
  • captcha 验证码

    1
    ['验证码字段', 'captcha']
  • compare 比较

    1
    2
    3
    4
    5
    // compareValue 比较值 operator 比较类型
    ['字段', 'compare', 'compareValue' => 30, 'operator' => '>=']

    // 比较两个字段的值(确认密码需要与密码一致)
    [['confirm_password'], 'compare', 'compareAttribute' => 'password']
  • default 默认值 当字段为空那么赋默认值

    1
    2
    // value 定义默认值
    ['字段', 'default', 'value' => 30]
  • exist 验证存在

    1
    2
    3
    4
    5
    6
    7
    8
    // 验证数据username 在表中必须存在
    ['username', 'exist']

    // 定义查询的model, 用户名在admin 表中是否存在
    ['username', 'exist', 'targetClass' => Admin::className()]

    // 定义数据的字段
    ['username', 'exist', 'targetAttribute' => 'name']
  • unique 唯一性验证

    1
    2
    3
    4
    5
    /**
    * targetClass 可以配置查询那个表
    * targetAttribute 可以定义对应的字段信息
    */
    ['username', 'unique']
  • file 文件验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * extension 允许上传的文件类型
    * 其他配置项
    * checkExtensionByMimeType 是否使用mime类型检查文件类型(扩展名)
    * mimeTypes 允许的mimeTypes 类型
    * minSize 文件最小大小
    * maxSize 文件最大大小
    * 其他配置...
    */
    [['file'], 'file', 'extensions' => ['png', 'jpg', 'gif', 'jpeg']]
  • image 图片验证 继承于 File

    1
    2
    3
    4
    5
    6
    7
    /**
    * image 验证继承于 file 验证
    * file 定义的验证配置都可以使用
    * 其他配置
    * minWidth, minHeight, maxWidth, maxLength, ...
    */
    ['image', 'image', 'minWidth' => 100]
  • filter 滤镜[trim]

    1
    2
    3
    4
    5
    /**
    * filter 制定过滤的函数 可以使用匿名函数
    * skipOnArray 是否跳过数组过滤
    */
    ['username', 'filter', 'filter' => 'trim', 'skipOnArray' => true]
  • trim 处理

    1
    [['username', 'password'], 'trim']
  • in 范围值验证

    1
    2
    // range 定义范围值
    ['status', 'in', 'range' => [1, 2]]
  • integer 整数

    1
    ['age', 'integer']
  • number 数字

    1
    ['money', 'number']
  • double 浮点数

    1
    ['money', 'double']
  • string 字符串验证

    1
    2
    3
    4
    5
    /**
    * length 定义字符串最小长度和最大长度
    * min, max 单独定义指定最小,最大长度
    */
    ['title', 'string', 'length' => [1, 2]]
  • boolean 布尔值验证

    1
    2
    3
    4
    /**
    * strict 定义是否严格验证
    */
    ['字段名', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true]
  • date 日期验证[data, time, datetime]

    1
    ['start_time', 'date']
  • ip 验证

    1
    [['user_ip'], 'ip']

自定义验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class User extends Model
{
public function rules()
{
return [
['username', 'validateUser', 'params' => [1, 2, 3]]
];
}

/**
* 自定义验证处理
*
* @desc 函数写的时候可以不接受参数
* @param string $attribute 字段名称
* @param mixed $params rules 中定义的params 参数信息
* @param \yii\validators\InlineValidator $validator 验证类
*/
public function validateUser($attribute, $params, $validator)
{
if ($this->user == 2) {
$this->addError($attribute, '不能等于二');
}
}
}

验证规则生效条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可以定义指定验证的生效条件
[
['passowrd'],
'required',

// 指定服务器验证什么时候生效(当状态为1的时候密码不能为空)
'when' => function ($model) {
return $model->status == 1;
},

// 前端js 验证什么时候生效
'whenClient' => "function(attribute, value) {
return $('#status').val() == 1;
}",
],

验证场景

model 可能在不同的场景下使用,不同场景对应不同的业务逻辑,需要验证的字段和方式也就不一样,这个时候就需要用到验证场景

定义验证场景

model 中定义验证场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User extends Model
{
public function scenarios()
{
// 默认验证场景 default 的验证字段
$scenarios = parent::scenarios();

// 登录时候,验证用户名和密码
$scenarios['login'] = ['username', 'password'];

// 注册的时候要验证用户名、密码、确认密码
$scenarios['register'] = ['username', 'password', 'confirm_password'];
return $scenarios;
}

}

为规则指定验证场景

还是上面那个model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 定义验证规则
*/
public function rules
{
return [
// 一样定义通用验证
[['username', 'password'], 'trim'],

// on 定义使用的验证场景 可以是字符串,也可以是数组
[['username', 'password'], 'required', 'on' => ['login', 'register']],
['confirm_password', 'required', 'on' => 'register'],
['confirm_password', 'compare', 'compareAttribute' => 'password', 'on' => 'register'],
];
}

使用验证场景

一般在控制器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public function actionLogin()
{
// 使用验证场景
$user = new User();
$user->scenario = 'login';

// 或者在实例化指定验证场景
$user = new User(['scenario' => 'login']);

// 对model的属性进行赋值
$user->username = '123';
...

// 显示的调用验证
if ($user->validate()) {
// 通过验证
} else {
// 获取到错误信息
var_dump($user->getErrors());
}

/**
* 执行save()的时候会调用validate() 方法
* 除非save() 调用时指定不走验证
* $user->save(false)
*/
if ($user->save()) {
...
} else {
var_dump($user->getErrors());
}
}

验证规则使用注意

执行验证是通过rules 中的规则,从上往下验证的,一旦验证不通过,不会向下验证, 建议关于数据库的验证,尽量放到最后

数据的操作

块赋值

块赋值只用一行代码将用户所有输入填充到一个模型

1
2
3
4
5
6
7
8
$user = new User();
$user->attributes = Yii::$app->request->post('User', []);

// 等同于
$user = new User();
$data = Yii::$app->request->post('User', []);
$user->username = isset($data['username']) ? $data['username'] : null;
$user->password = isset($data['password']) ? $data['password'] : null;

  • 使用load() 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $user = new User();

    /**
    * 默认使用Yii 表单
    * 提交的表单数组应该为
    * 表单元素 <input type="text" name="User[username]" />
    * [
    * 'User' => ['username' => 'username', 'password' => 'password']
    * ]
    */
    $user->load(Yii::$app->request->post());

    /**
    * 没有使用yii表单
    * <input type="text" name="username" >
    * 对应提交的值
    * ['username' => 'username', 'password' => '']
    */
    $user->load(Yii::$app->request->post(), '');
  • 使用属性 attributes

    1
    2
    3
    4
    5
    /**
    * attributes 需要接收一个数组
    * [属性字段 => 对应值]
    */
    $user->attributes = Yii::$app->request->post('User', []);
  • 使用 setAttributes() 方法(前面两种最终都是调用的 setAttributes() 方法)

    1
    2
    3
    4
    5
    6
    7
    $user = new User();

    /**
    * 需要接收一个数组
    * [属性字段 => 对应值]
    */
    $user->setAttributes(Yii::$app->request->post());

块赋值注意

  1. 需要定义为安全的字段才可以赋值成功
  2. 在没有定义场景的情况下,定义了规则的字段、便会认为是安全的,可以块赋值成功
  3. 定义了验证场景,只有场景定义的字段可以批量赋值

新增数据

1
2
3
4
5
6
7
$user = new User();
$user->username = 'username';
$user->password = 'password';
$user->insert();

// 或者使用
$user->save();

修改数据

  • 查询对象修改

    1
    2
    3
    4
    5
    6
    7
    $user = User::findOne(1);
    $user->username = 'username';
    $user->password = 'password';
    $user->update();

    // 或者使用
    $user->save();
  • 修改指定数据

    1
    2
    3
    4
    5
    // 将ID 等于 2的用户的状态改为 1, 第二个参数同 where() 方法中的参数
    $intNumber = User::updateAll(['status' => 1], ['id' => 2]]);

    // 使用字符的绑定参数查询修改
    User::updateAll(['status' => 2], 'id=:id', [':id' => 1]);
  • 属性值实现累加

    1
    2
    3
    4
    5
    6
    7
    /**
    * updateAllCounters
    * 第一个参数: 字段对应累加 正数值、累减的值 负数值
    * 第二个参数: 查询条件 同where() 方法,可以字符串,数组
    * 第三个参数: 查询绑定的参数
    */
    User::updateAllCounters(['status' => -2], 'id=:id', [':id' => 1]);

删除数据

  • 使用查询对象删除

    1
    2
    $user = User::findOne(1);
    $user->delete()
  • 删除指定数据

    1
    User::deleteAll(['status' => 0]);

AR的生命周期

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

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

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

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

constructor

  1. yii\db\ActiveRecord::init(): 会触发一个 yii\db\ActiveRecord::EVENT_INIT 事件
  2. 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. yii\db\ActiveRecord::afterSave(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_INSERT 或 yii\db\ActiveRecord::EVENT_AFTER_UPDATE 事件
  • 最后,当调用 delete() 删除数据时, 我们将获得如下生命周期:
  1. yii\db\ActiveRecord::beforeDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_DELETE 事件
    执行实际的数据删除
  2. yii\db\ActiveRecord::afterDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_DELETE 事件

参考文档