load方法小探
前面的话
Yii有一系列语法糖可供使用,其中就包含Model的load方法。
前一家公司用的是Laravel,刚接触Yii load方法的时候觉得蛮二的,就这么轻松的把提交上来的数据加载进Model了,不过用习惯了确实省事。感觉上这么做太偷懒了,没有Laravel封的那么紧。但尊重框架的东西吧。
开始正文
平时做页面的时候前端用Yii的Form生成的表单,controller直接load进Model也就存起来了,这次是跟外部有一个同步对接的活,把外部的一些数据导入过来,于是也就想当然用load()了,结果对面报失败,查日志调试跟踪,啊咧,竟然返回false?!
习惯性的抓一下$model->getErrors(),诶?竟然没用数据。
看代码,调试跟进。
对方传过来的数据结构是这样的:
1 | [ |
1、第一阶段,以为自己这么做就对了,呵呵哒,你太naive了
首先分析load方法:
1 | public function load($data, $formName = null) |
当时load的时候,第一个参数$data = Yii::$app->request->post('data');
由于$formName === null
,所以$scope = $this->formName()
,那么$scope是多少。
1 | public function formName() |
formName()方法调用反射$reflector->getShortName()
获取当前类的短名,不含命名空间的类名。
好,回到代码,$scope
就是实际Model的类名,假设$scope = 'Merchant'
1 | public function load($data, $formName = null) |
由于$scope不为空,所以逻辑就走到了elseif中
elseif中的判断,elseif (isset($data[$scope]))
,由于$data
直接就是数据库的字段和值的对应,所以也不满足,于是就走到了最后的else,返回了false
好,那就改吧,把原先的数据从$post['data']
变成$post['data']['Merchant']
。
2、第二阶段,继续深究
发现改了之后还是false,那么进$this->setAttributes($data[$scope]);
看看
1 | public function setAttributes($values, $safeOnly = true) |
由于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的。