深入理解 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调用