背景
使用yii 2.0.10的migration,并且已经建立了User表,需要插入一个默认的Admin账号,使用了一个ActiveRecord的Model。
在save时发现如下报错,1
Exception 'yii\\db\\Exception' with message 'SQLSTATE\[HY000\]: General error: 1364 Field 'password' doesn't have a default value The SQL being executed was: INSERT INTO \`user\` (\`id\`) VALUES (DEFAULT)'
明显可以看出来是因为插入语句没有把所有数据带上,那么问题出在哪呢?
相关代码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
33
34
35
36
37
38// 在User的Model里面
public $id;
public $name;
public $password;
public $key;
public $token;
public $time;
public $email;
public function beforeSave($insert)
{
if(parent::beforeSave($insert)){
if($this->isNewRecord){
$this->key = \Yii::$app->security->generateRandomString();
$this->password = password_hash($this->password . $this->key, PASSWORD_BCRYPT);
$this->token = "";
$this->name = isset($this->name) ? $this->name : "Guest";
}
return true;
}
return false;
}
// 调用User Instance的代码中
private function createFirstAdmin() {
$admin = \app\models\User::findOne(['email'=>$this->adminEmail]);
if($admin == null){
$admin = new \app\models\User;
$admin->email = $this->adminEmail;
$admin->name = 'admin';
$admin->password = $this->adminPassword;
if($admin->save()){
//...
} else {
return false;
}
} else {
return $admin;
}
}
分析
因为是第一次用YII的数据库,所以不是很清楚,但是ActiveRecord还是有所了解的。
想法一
Model的instance相关属性没有被复制,所以在beforeSave里面var_dump了一下$this,然而是有值的。
想法二
有值的话,说明是在中间的处理过程中删掉了。那么要去看看实现方法了。
跟踪下去,找到了\\yii\\db\\ActiveRecord
的insertInternal
方法,看到了里面会调用getDirtyAttribute
,
然后想起文档里面有谈过关于Dirty Attribute的信息,但是因为快速上手,没有看直接过掉了,摘抄如下,
When you call save() to save an Active Record instance, only dirty attributes are being saved. An attribute is considered dirty if its value has been modified since it was loaded from DB or saved to DB most recently. Note that data validation will be performed regardless if the Active Record instance has dirty attributes or not. Active Record automatically maintains the list of dirty attributes. It does so by maintaining an older version of the attribute values and comparing them with the latest one. You can call yii\db\ActiveRecord::getDirtyAttributes() to get the attributes that are currently dirty. You can also call yii\db\ActiveRecord::markAttributeDirty() to explicitly mark an attribute as dirty.
所以先var_dump($this->getDirtyAttributes())
,果然是一个空数组,那么解决办法就是按照上面说,调用markAttributeDirty了。
但是为什么一个new出来的对象在直接赋值的时候没有标记成dirty呢。虽然心存疑虑,但是还是决定一试,1
2
3
4
5$this->markAttributeDirty('key');
$this->markAttributeDirty('password');
$this->markAttributeDirty('token');
$this->markAttributeDirty('name');
$this->markAttributeDirty('email');
然而,getDirtyAttribute
还是空数组,果然,并不是dirty attribute的问题。
想法三
如果不是dirty attribute的问题,难道是因为其他地方有问题,导致attribute根本就是没有的。 所以var_dump一下attributes(),然而是正常的。
想法四
所以问题应该还是在dirty attribute上,那么去看看是如何实现。然而没有什么问题,所以无奈,只能去vendor中var_dump一下了。1
2
3
4var_dump($this->_oldAttributes);
var_dump($this->_attributes);
var_dump($this->attributes());
var_dump($names);
结果居然是_attributes是空数组,而attributes()方法是正常的,一直以为这2个是一样的。
然后发现attributes()方法被ActiveRecord用来获取数据库中的attributes,而_attributes才是真的Model中的attributes,不再是用反射获取到的attribute了。
但是在getDirtyAttribute中使用的是_attributes,所以问题就是为什么_attributes没有被赋值了。
想法五
如果说不用反射,那么给_attributes赋值的方法就只有一个了,那就是用setter方法,而调用setter方法的前提是,类中没有相应的attribute,那么把User中把attribute的声明全部删掉。
果然,输出了正常的结果。
总结
对于这个结果。。。 以前因为用过其他的AR库,然而它用的是声明加注释的方式来联系数据库中的数据,于是这个先入为主的思想让自己掉坑了。
而且还在文档上的Accessing Data部分看到了这样加背景色的一段,
Note: The Active Record attributes are named after the associated table columns in a case-sensitive manner. Yii automatically defines an attribute in Active Record for every column of the associated table. You should NOT redeclare any of the attributes.
以后看文档,还是要通读一遍,至少要把花了重点的部分看一下。