深入理解 yii2的Active Record
yii2 中的 $model->attribute() , $model->attributes , $model->attributes= [...], model->fields(), $model->toArray();
以下依次进行剖析:
1.
执行的是model的attributes()方法,返回的数据库的字段数组, yii\db\ActiveRecord 代码如下:
- public function attributes()
- {
- return array_keys(static::getTableSchema()->columns);
- }
执行返回结果示例:
- array (size=14)
- 0 => string 'id' (length=2)
- 1 => string 'username' (length=8)
- 2 => string 'password_hash' (length=13)
- 3 => string 'password_reset_token' (length=20)
- 4 => string 'email' (length=5)
- 5 => string 'auth_key' (length=8)
- 6 => string 'status' (length=6)
- 7 => string 'created_at' (length=10)
- 8 => string 'updated_at' (length=10)
- 9 => string 'password' (length=8)
- 10 => string 'role' (length=4)
- 11 => string 'access_token' (length=12)
- 12 => string 'allowance' (length=9)
- 13 => string 'allowance_updated_at' (length=20)
2.
var_dump($this->attributes) 执行的是函数: \yii\base\model->getAttributes(),该函数代码如下
- public function getAttributes($names = null, $except = [])
- {
- $values = [];
- if ($names === null) {
- $names = $this->attributes();
- }
- foreach ($names as $name) {
- $values[$name] = $this->$name;
- }
- foreach ($except as $name) {
- unset($values[$name]);
- }
-
- return $values;
- }
也就是说,先通过 $this->attributes()方法,得到数据库表的字段数组
然后 依次遍历,查看各个属性,$this->$name获取各个字段对应的值,而这个访问的是对象的属性,是通过魔术方法__get 从private属性 \yii\db\BaseActiveRecord::$_attributes 获取的,也就是说数组的结构是先用数据库字段作为数组的key, value是从数组 \yii\db\BaseActiveRecord::$_attributes中取值
然后拼起来的数组,也就是只有数据库里面的部分,对于model中定义的成员变量和其他的属性,这里是不做输出的。仅仅是数据库的字段部分。
如果您想得到一些定义的成员变量,但是又不想定义fields那么麻烦,您可以通过
- $table_attr = $this->attributes();
- $public_x = [ 'password_repeat'];
- $arr = array_merge($table_attr,$public_x );
- $model->getAttributes($arr);
返回您想访问的所有的属性和成员变量(注意:我说的属性,指的是不是成员变量的属性。)
魔术方法从 \yii\db\BaseActiveRecord::$_attributes 中取值,如果不存在,返回null
魔兽方法如下: \yii\db\BaseActiveRecord::__get __sete
- public function __get($name)
- {
- if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
- return $this->_attributes[$name];
- } elseif ($this->hasAttribute($name)) {
- return null;
- } else {
- if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
- return $this->_related[$name];
- }
- $value = parent::__get($name);
- if ($value instanceof ActiveQueryInterface) {
- return $this->_related[$name] = $value->findFor($name, $this);
- } else {
- return $value;
- }
- }
- }
- public function __set($name, $value)
- {
- if ($this->hasAttribute($name)) {
- $this->_attributes[$name] = $value;
- } else {
- parent::__set($name, $value);
- }
- }
因此,在$_attributes不存在的值,就会被赋值null。
3.
$this->attributes = array();
这个执行的是
yii\base\model->setAttributes($values, $safeOnly = true);
代码如下:
- public function setAttributes($values, $safeOnly = true)
- {
- if (is_array($values)) {
- $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
- foreach ($values as $name => $value) {
- if (isset($attributes[$name])) {
- $this->$name = $value;
- } elseif ($safeOnly) {
- $this->onUnsafeAttribute($name, $value);
- }
- }
- }
- }
也就是块赋值,根据场景定义的字段,而且是只有这些字段赋值。
原理就是 将
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
函数如下
- public function fields()
- {
- $fields = array_keys($this->_attributes);
-
-
- return array_combine($fields, $fields);
- }
也就是说:通过private $_attributes 数组的key
5.
$model->toArray()
这个函数的输出,是将 fields 返回的数组输出,从第4部分,可以看到,fields默认是由 yii\db\BaseActiveRecord 的private属性$_attributes 的key得到
所以,toArray默认是将$_attributes的值得出,如果想在这里添加类的成员变量,可以fields函数中添加:
- public function fields()
- {
- $fields = parent::fields();
- $fields['password_repeat'] = 'password_repeat';
- //$fields['rememberMe'] = function ($model) {
- // return $model->rememberMe . ' ' . $model->password_repeat;
- // };
-
- return $fields;
- }
$fields数组的key 是属性或成员变量,值也是key 通过$model->password_repeat得到对应的值
如果想自定义,可以通过函数的方式获取。
6.实践
当findOne得到一个AR实例后
$_attributes 里面保存的是所有的属性
$_oldAttributes里面保存的也是所有的数据库查询出来的属性,和$_attributes一样
当对这个对象重新赋值,实际上是吧值写入到了$_attributes ,而 $_oldAttributes的值是不变的
在最终的save的时候,查看两个数组的差异,进行update对应的字段,
在这里也就看出,实际上AR读取的字段值,不是AR类的成员变量,而是通过__get __set方法得到的对应的值
而这个值,就是从$_attributes这个private属性中取到的值。
BaseActiveRecord函数:
- public function __set($name, $value)
- {
- if ($this->hasAttribute($name)) {
- $this->_attributes[$name] = $value;
- } else {
- parent::__set($name, $value);
- }
- }
-
-
-
-
- public function __get($name)
- {
- if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
- return $this->_attributes[$name];
- } elseif ($this->hasAttribute($name)) {
- return null;
- } else {
- if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
- return $this->_related[$name];
- }
- $value = parent::__get($name);
- if ($value instanceof ActiveQueryInterface) {
- return $this->_related[$name] = $value->findFor($name, $this);
- } else {
- return $value;
- }
- }
- }
所以,通过
$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';
称为:
- public function fields()
- {
- $fields = parent::fields();
- $fields['password_repeat'] = 'password_repeat';
- //$fields['rememberMe'] = function ($model) {
- // return $model->rememberMe . ' ' . $model->password_repeat;
- // };
-
- return $fields;
- }
通过asArray就输出了 password_repeat字段了
对于new的新的AR对象
- $model = new Useradmin();
- $model->attributes = $this->getValues1();
- public function getValues1(){
- return [
- 'username' => 'terry',
- 'password' => 'passfdafds',
- //'password' => 'passfdafds',
- 'password_repeat' => 'passfdafds',
- 'email' => 'email',
- 'created_at' => '2014-09-09 11:11:11',
- 'access_token' => null,
- 'rememberMe' => 44,
- ];
- }
$model->attributes = $this->getValues1(); 这个是块赋值,执行的是setAttributes方法,先通 场景 scrnarios函数 验证是否是安全属性,
如果是,则把数据插入到 private $_attributes中
var_dump($model->attributes());
var_dump($model->toArray());
var_dump($model->attributes);
依次输出
- array (size=14)
- 0 => string 'id' (length=2)
- 1 => string 'username' (length=8)
- 2 => string 'password_hash' (length=13)
- 3 => string 'password_reset_token' (length=20)
- 4 => string 'email' (length=5)
- 5 => string 'auth_key' (length=8)
- 6 => string 'status' (length=6)
- 7 => string 'created_at' (length=10)
- 8 => string 'updated_at' (length=10)
- 9 => string 'password' (length=8)
- 10 => string 'role' (length=4)
- 11 => string 'access_token' (length=12)
- 12 => string 'allowance' (length=9)
- 13 => string 'allowance_updated_at' (length=20)
- array (size=6)
- 'username' => string 'terry' (length=5)
- 'password' => string 'passfdafds' (length=10)
- 'email' => string 'email' (length=5)
- 'created_at' => string '2014-09-09 11:11:11' (length=19)
- 'access_token' => null
- 'password_repeat' => string 'passfdafds' (length=10)
- array (size=14)
- 'id' => null
- 'username' => string 'terry' (length=5)
- 'password_hash' => null
- 'password_reset_token' => null
- 'email' => string 'email' (length=5)
- 'auth_key' => null
- 'status' => null
- 'created_at' => string '2014-09-09 11:11:11' (length=19)
- 'updated_at' => null
- 'password' => string 'passfdafds' (length=10)
- 'role' => null
- 'access_token' => null
- 'allowance' => null
- 'allowance_updated_at' => null
因为rememberMe不是安全属性,所以块赋值失败
$model->attributes() 返回的是数据库中所有的字段数组
$model->toArray()返回的是 $_attributes 属性数组
$model->attributes 返回的是 key为 $model->attributes() 返回的key ,值为 $_attributes 属性数组的对应值,不存在则为null
7
对于model的foreach
- foreach($model as $k=>$v){
- echo $k."=>".$v."<br/>";
- }
输出的是属性 $model->attributes 也就是 $model->getAttributes()函数返回的结果。
对应 model的其他的成员变量是不输出的。需要通过
$model->xxxx调用