load方法小探

转载: Yii load方法小探&&感受思想

前面的话

Yii有一系列语法糖可供使用,其中就包含Model的load方法。
前一家公司用的是Laravel,刚接触Yii load方法的时候觉得蛮二的,就这么轻松的把提交上来的数据加载进Model了,不过用习惯了确实省事。感觉上这么做太偷懒了,没有Laravel封的那么紧。但尊重框架的东西吧。

开始正文

平时做页面的时候前端用Yii的Form生成的表单,controller直接load进Model也就存起来了,这次是跟外部有一个同步对接的活,把外部的一些数据导入过来,于是也就想当然用load()了,结果对面报失败,查日志调试跟踪,啊咧,竟然返回false?!

习惯性的抓一下$model->getErrors(),诶?竟然没用数据。
看代码,调试跟进。

对方传过来的数据结构是这样的:

1
2
3
4
5
6
7
8
9
10
[
[sign] => 'string_sign'
[timestamp] => 1488338067
[data] => [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'key4' => 'value4',
]
]

1、第一阶段,以为自己这么做就对了,呵呵哒,你太naive了

首先分析load方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function load($data, $formName = null)
{
$scope = $formName === null ? $this->formName() : $formName;
if ($scope === '' && !empty($data)) {
$this->setAttributes($data);

return true;
} elseif (isset($data[$scope])) {
$this->setAttributes($data[$scope]);

return true;
} else {
return false;
}
}

当时load的时候,第一个参数$data = Yii::$app->request->post('data');

由于$formName === null,所以$scope = $this->formName(),那么$scope是多少。

1
2
3
4
5
public function formName()
{
$reflector = new ReflectionClass($this);
return $reflector->getShortName();
}

formName()方法调用反射$reflector->getShortName()获取当前类的短名,不含命名空间的类名。

好,回到代码,$scope就是实际Model的类名,假设$scope = 'Merchant'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function load($data, $formName = null)
{
$scope = $formName === null ? $this->formName() : $formName;
// $scope == 'Merchant'
if ($scope === '' && !empty($data)) {
$this->setAttributes($data);

return true;
} elseif (isset($data[$scope])) {
$this->setAttributes($data[$scope]);

return true;
} else {
return false;
}
}

由于$scope不为空,所以逻辑就走到了elseif中
elseif中的判断,elseif (isset($data[$scope])),由于$data直接就是数据库的字段和值的对应,所以也不满足,于是就走到了最后的else,返回了false

好,那就改吧,把原先的数据从$post['data']变成$post['data']['Merchant']

2、第二阶段,继续深究

发现改了之后还是false,那么进$this->setAttributes($data[$scope]);看看

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}
}
}

由于load的时候只传了第一个参数,所以第二个参数$safeOnly == true,所以代码中
$attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
的实际操作是
$attributes = array_flip($this->safeAttributes());
重点来了,这里实际调用的是safeAttributes,好,到这就明白了,对方传过来的某些参数没在model中rule配置的属于safeAttributes的地方。

解决方案:把传来的参数中没在safe的字段加入safe配置。
好了,完美。

事后一根烟

所以问题就很明显了,一共有两个问题点:
1、使用load的时候,键值对要被包含在数组key为类名的里面;
2、想用load,必须要保证字段都被配置在了Model的rule里面的属于safe的字段。

实际上当时看到if (isset($data[$scope]))的时候就想到了,使用Yii自己创建的Form提交数据的时候,value的name并不直接是字段名,而是lei_ming[ziduan_ming]

另外,其实Yii设置load只是为了让Yii Form用,因此那些字段都是“safe”的,并不是为了让你偷懒而用load的。