深入理解 yii2的Active Record

本文深入解析Yii2框架中ActiveRecord的使用方法,包括属性获取、批量设置、字段映射等功能,帮助开发者更好地理解和运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入理解 yii2的Active Record

yii2 中的  $model->attribute()   ,  $model->attributes  ,   $model->attributes= [...], model->fields(), $model->toArray();

以下依次进行剖析:
1.

[html]  view plain  copy
  1. $this->attributes()   

执行的是model的attributes()方法,返回的数据库的字段数组, yii\db\ActiveRecord 代码如下:
[html]  view plain  copy
  1. public function attributes()  
  2.     {  
  3.         return array_keys(static::getTableSchema()->columns);  
  4.     }  

执行返回结果示例:
[html]  view plain  copy
  1. array (size=14)  
  2.   0 => string 'id' (length=2)  
  3.   1 => string 'username' (length=8)  
  4.   2 => string 'password_hash' (length=13)  
  5.   3 => string 'password_reset_token' (length=20)  
  6.   4 => string 'email' (length=5)  
  7.   5 => string 'auth_key' (length=8)  
  8.   6 => string 'status' (length=6)  
  9.   7 => string 'created_at' (length=10)  
  10.   8 => string 'updated_at' (length=10)  
  11.   9 => string 'password' (length=8)  
  12.   10 => string 'role' (length=4)  
  13.   11 => string 'access_token' (length=12)  
  14.   12 => string 'allowance' (length=9)  
  15.   13 => string 'allowance_updated_at' (length=20)  

2.

var_dump($this->attributes) 执行的是函数: \yii\base\model->getAttributes(),该函数代码如下

[html]  view plain  copy
  1. public function getAttributes($names = null, $except = [])  
  2.    {  
  3.        $values = [];  
  4.        if ($names === null) {  
  5.            $names = $this->attributes();  
  6.        }  
  7.        foreach ($names as $name) {  
  8.            $values[$name] = $this->$name;  
  9.        }  
  10.        foreach ($except as $name) {  
  11.            unset($values[$name]);  
  12.        }  
  13.   
  14.        return $values;  
  15.    }  

也就是说,先通过  $this->attributes()方法,得到数据库表的字段数组

然后 依次遍历,查看各个属性,$this->$name获取各个字段对应的值,而这个访问的是对象的属性,是通过魔术方法__get 从private属性 \yii\db\BaseActiveRecord::$_attributes 获取的,也就是说数组的结构是先用数据库字段作为数组的key, value是从数组 \yii\db\BaseActiveRecord::$_attributes中取值

然后拼起来的数组,也就是只有数据库里面的部分,对于model中定义的成员变量和其他的属性,这里是不做输出的。仅仅是数据库的字段部分。


如果您想得到一些定义的成员变量,但是又不想定义fields那么麻烦,您可以通过

[html]  view plain  copy
  1. $table_attr = $this->attributes();  
  2. $public_x = [ 'password_repeat'];  
  3. $arr = array_merge($table_attr,$public_x );  
  4. $model->getAttributes($arr);   

返回您想访问的所有的属性和成员变量(注意:我说的属性,指的是不是成员变量的属性。)



魔术方法从 \yii\db\BaseActiveRecord::$_attributes 中取值,如果不存在,返回null

魔兽方法如下: \yii\db\BaseActiveRecord::__get     __sete

[html]  view plain  copy
  1. public function __get($name)  
  2.    {  
  3.        if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {  
  4.            return $this->_attributes[$name];  
  5.        } elseif ($this->hasAttribute($name)) {  
  6.            return null;  
  7.        } else {  
  8.            if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {  
  9.                return $this->_related[$name];  
  10.            }  
  11.            $value = parent::__get($name);  
  12.            if ($value instanceof ActiveQueryInterface) {  
  13.                return $this->_related[$name] = $value->findFor($name, $this);  
  14.            } else {  
  15.                return $value;  
  16.            }  
  17.        }  
  18.    }  

[html]  view plain  copy
  1. public function __set($name, $value)  
  2.     {  
  3.         if ($this->hasAttribute($name)) {  
  4.             $this->_attributes[$name] = $value;  
  5.         } else {  
  6.             parent::__set($name, $value);  
  7.         }  
  8.     }  

因此,在$_attributes不存在的值,就会被赋值null。


3.
$this->attributes = array();
这个执行的是

yii\base\model->setAttributes($values, $safeOnly = true);

代码如下:

[html]  view plain  copy
  1. public function setAttributes($values, $safeOnly = true)  
  2.    {  
  3.        if (is_array($values)) {  
  4.            $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());  
  5.            foreach ($values as $name => $value) {  
  6.                if (isset($attributes[$name])) {  
  7.                    $this->$name = $value;  
  8.                } elseif ($safeOnly) {  
  9.                    $this->onUnsafeAttribute($name, $value);  
  10.                }  
  11.            }  
  12.        }  
  13.    }  


也就是块赋值,根据场景定义的字段,而且是只有这些字段赋值。

原理就是 将 

1.过滤出来场景允许的安全属性,然后进行赋值

2.如果存在成员变量,那么首先进行成员变量的赋值。

  如果成员变量不存在,则会使用到__set魔兽方法,添加到   yii\db\BaseActiveRecord 的private属性$_attributes 数组中

因此,如果在数据库中有一个字段password,如果你在AR类中也定义了一个password,那么就不会保存到 private属性$_attributes 数组中

当然,这个不会影响到保存,因为$model->attributes 是通过 $this->$name 的方式读取,而不是通过 private属性$_attributes 数组读取,

通过  $this->$name 的方式读取 ,定义的成员变量也是会保存到数据库中的

造成的后果就是  private属性$_attributes 数组 没有password这个key,fields函数返回就没有password,进而toArray()返回的也没有password。




4.model->fields()
这个方法默认的值,是抽象方法 yii\db\BaseActiveRecord 的private属性$_attributes
函数如下
[html]  view plain  copy
  1. public function fields()  
  2.     {  
  3.         $fields = array_keys($this->_attributes);  
  4.   
  5.   
  6.         return array_combine($fields, $fields);  
  7.     }  

也就是说:通过private  $_attributes 数组的key


5.
$model->toArray()

这个函数的输出,是将 fields 返回的数组输出,从第4部分,可以看到,fields默认是由  yii\db\BaseActiveRecord 的private属性$_attributes 的key得到

所以,toArray默认是将$_attributes的值得出,如果想在这里添加类的成员变量,可以fields函数中添加:

[html]  view plain  copy
  1. public function fields()  
  2.     {  
  3.         $fields = parent::fields();  
  4.         $fields['password_repeat'] = 'password_repeat';  
  5.         //$fields['rememberMe'] = function ($model) {  
  6.         //     return $model->rememberMe . ' ' . $model->password_repeat;  
  7.         //  };  
  8.           
  9.         return $fields;  
  10.     }  

$fields数组的key 是属性或成员变量,值也是key  通过$model->password_repeat得到对应的值

如果想自定义,可以通过函数的方式获取。



6.实践

当findOne得到一个AR实例后

$_attributes 里面保存的是所有的属性
$_oldAttributes里面保存的也是所有的数据库查询出来的属性,和$_attributes一样
当对这个对象重新赋值,实际上是吧值写入到了$_attributes ,而 $_oldAttributes的值是不变的
在最终的save的时候,查看两个数组的差异,进行update对应的字段,
在这里也就看出,实际上AR读取的字段值,不是AR类的成员变量,而是通过__get  __set方法得到的对应的值
而这个值,就是从$_attributes这个private属性中取到的值。
BaseActiveRecord函数:
[html]  view plain  copy
  1. public function __set($name, $value)  
  2.     {  
  3.         if ($this->hasAttribute($name)) {  
  4.             $this->_attributes[$name] = $value;  
  5.         } else {  
  6.             parent::__set($name, $value);  
  7.         }  
  8.     }  
  9.   
  10.   
  11.   
  12.   
  13. public function __get($name)  
  14.     {  
  15.         if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {  
  16.             return $this->_attributes[$name];  
  17.         } elseif ($this->hasAttribute($name)) {  
  18.             return null;  
  19.         } else {  
  20.             if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {  
  21.                 return $this->_related[$name];  
  22.             }  
  23.             $value = parent::__get($name);  
  24.             if ($value instanceof ActiveQueryInterface) {  
  25.                 return $this->_related[$name] = $value->findFor($name, $this);  
  26.             } else {  
  27.                 return $value;  
  28.             }  
  29.         }  
  30.     }  


所以,通过
$user = User::findOne(1)得到的model对象,
private $_attributes 存储的是数据库里面的值,以及对其赋值


$user->fields() 得到的是 $_attributes 里面含有的字段,因此就是所有的数据表字段
因此通过
 $user->toArray()得到的是所有数据库的字段,也就是说,在默认情况下 toArray的输出的字段,是由 private  $_attributes字段决定的


当然,如果在model中进行了定义新字段,

public $password_repeat;
然后再fields中执行
public function fields() 这个函数中添加

$fields['password_repeat'] = 'password_repeat';
称为:
[html]  view plain  copy
  1. public function fields()  
  2. {  
  3. $fields = parent::fields();  
  4. $fields['password_repeat'] = 'password_repeat';  
  5. //$fields['rememberMe'] = function ($model) {  
  6.         //     return $model->rememberMe . ' ' . $model->password_repeat;  
  7.         //  };  
  8.   
  9. return $fields;  
  10. }  


通过asArray就输出了   password_repeat字段了

对于new的新的AR对象

[html]  view plain  copy
  1. $model = new Useradmin();  
  2. $model->attributes = $this->getValues1();  
  3. public function getValues1(){  
  4.         return [  
  5.             'username' => 'terry',  
  6.             'password' => 'passfdafds',  
  7.             //'password' => 'passfdafds',  
  8.             'password_repeat' => 'passfdafds',  
  9.             'email' => 'email',  
  10.             'created_at'  => '2014-09-09 11:11:11',  
  11.             'access_token' => null,  
  12.             'rememberMe'    => 44,  
  13.         ];  
  14.     }  

$model->attributes = $this->getValues1();  这个是块赋值,执行的是setAttributes方法,先通 场景 scrnarios函数  验证是否是安全属性,


如果是,则把数据插入到 private $_attributes中


var_dump($model->attributes());
var_dump($model->toArray());

var_dump($model->attributes);

依次输出

[html]  view plain  copy
  1. array (size=14)  
  2.   0 => string 'id' (length=2)  
  3.   1 => string 'username' (length=8)  
  4.   2 => string 'password_hash' (length=13)  
  5.   3 => string 'password_reset_token' (length=20)  
  6.   4 => string 'email' (length=5)  
  7.   5 => string 'auth_key' (length=8)  
  8.   6 => string 'status' (length=6)  
  9.   7 => string 'created_at' (length=10)  
  10.   8 => string 'updated_at' (length=10)  
  11.   9 => string 'password' (length=8)  
  12.   10 => string 'role' (length=4)  
  13.   11 => string 'access_token' (length=12)  
  14.   12 => string 'allowance' (length=9)  
  15.   13 => string 'allowance_updated_at' (length=20)  
  16. array (size=6)  
  17.   'username' => string 'terry' (length=5)  
  18.   'password' => string 'passfdafds' (length=10)  
  19.   'email' => string 'email' (length=5)  
  20.   'created_at' => string '2014-09-09 11:11:11' (length=19)  
  21.   'access_token' => null  
  22.   'password_repeat' => string 'passfdafds' (length=10)  
  23. array (size=14)  
  24.   'id' => null  
  25.   'username' => string 'terry' (length=5)  
  26.   'password_hash' => null  
  27.   'password_reset_token' => null  
  28.   'email' => string 'email' (length=5)  
  29.   'auth_key' => null  
  30.   'status' => null  
  31.   'created_at' => string '2014-09-09 11:11:11' (length=19)  
  32.   'updated_at' => null  
  33.   'password' => string 'passfdafds' (length=10)  
  34.   'role' => null  
  35.   'access_token' => null  
  36.   'allowance' => null  
  37.   'allowance_updated_at' => null  
因为rememberMe不是安全属性,所以块赋值失败

$model->attributes() 返回的是数据库中所有的字段数组

$model->toArray()返回的是 $_attributes 属性数组

$model->attributes 返回的是 key为 $model->attributes() 返回的key  ,值为  $_attributes 属性数组的对应值,不存在则为null


7

对于model的foreach

[html]  view plain  copy
  1. foreach($model as $k=>$v){  
  2.             echo $k."=>".$v."<br/>";  
  3.         }  

输出的是属性 $model->attributes  也就是 $model->getAttributes()函数返回的结果。



对应 model的其他的成员变量是不输出的。需要通过

$model->xxxx调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值