关于 yii2的Active Record 的那些事

深入理解 yii2的Active Record

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

以下依次进行剖析:

1.

$this->attributes()   

执行的是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     __set

 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.如果存在成员变量,那么首先进行成员变量的赋值。

3.如果成员变量不存在,则会使用到__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调用

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值