Yii2框架那些折磨人的坑
说点闲话距离上次写博客,已经有一年了。在动手写之前,总是带着深深的罪恶感。被它折磨许久,终于,还是,动手了。 值得庆祝的一件事:最近开始健身了。每天动感单车45分钟,游泳45分钟,真的是(生)爽(不)到(如)爆(死)。 好了,扯淡完毕,步入正题。 ? ActiveRecord被莫名写入?准备知识
代码现场/** * @property integer $id * @property string $name * @property string $detail * @property double $price * @property integer $area **/ class OcRoom extends ActivieRecord { ... } $room = OcRoom::find() //先取出一个对象。 ->select(['id']) 只取出'id'列 ->where(['id'=>20]) ->one(); $room->save(); 保存,会发现此行的其它字段都被写成默认值了。 总结问题这个例子的问题在于:
解决方法然而,我们有什么解决办法呢?提供几种思路:
? 你的Transaction生效了吗?代码现场* * @property integer $id * @property string $name * ActiveRecord { public function rules() { return [['name','string','min'=>2,'max'=>10]]; } ... } class OcHouse $a = new OcRoom(); $a->name = ''; name为空字符串,不满足rules()条件。 $b = OcHouse(); $b->name = '我的房间'; name合法,可以保存。 $transaction = Yii::$app->db->beginTransaction(); try{ $a->save(); name字段不合法,无法验证通过,在validate()阶段已经返回false,不会进行数据库存储的步骤,所以也不会抛出异常。 $b->save(); name字段合法,可以正常保存。 $transaction->commit(); 提交后,发现$a保存失败,而$b保存成功。 } catch (Exception $e) { Yii::error($e->getTraceAsString(),__METHOD__); $transaction->rollBack(); } 问题总结这段代码的问题在于:
解决方法在 ? 'Y-m-d'不被识别?代码现场OcRenterBill return [ ['start_time','date','format'=>'Y-m-d'], ]; } } OcRenterBill(); $a = '2015-09-12'; $a->save(); 会报错,说格式不对 问题总结如果一开始,Yii框架就报错,这个还不算坑。坑的是我在Mac上开发时,这个可以完全正常的工作,而发布到线上环境(Ubuntu)后,就弹出“属性start_time格式无效”的错误。而参考官方文档,发现这种格式是允许的官方文档。 啊啊啊。各种试错,最后发现如果改成 ? 内存泄露代码现场static actionTest() { $total = 10; var_dump('开始内存'.memory_get_usage()); while($total){ $ret=User::findOne(['id'=>910002]); var_dump('end内存'.memory_get_usage()); unset($ret); $total--; } } 上面代码的内存一直在增长, 按照原本想法来看, 变量被释放了,内存就算增长也不会一直增长。因为每循环一次内存都会被释放。 分析问题 上面这段代码涉及到了数据库的操作,而我们知道,数据库的很多地方都能引起内存泄漏。 所以先屏蔽数据库相关操作, 我手写了一个原生的数据库查询操作, 发现内存正常,没有问题。 $dsn = "mysql:dbname=test;host=localhost"$db_user = 'root'$db_pass = 'admin'; 查询 $sql = "select * from buyer"$res = $pdo->query($sql); foreach($res as $row) { echo $row['username'].'<br/>'; } 这时候答案呼之欲出--- 是yii2框架搞了鬼 定位问题 既然知道了是yii2 框架的问题那就可以进一步缩小问题。 $ret= User(); ; } } 内存还是一直增长。 这时候我测试了一个其他的yii2类 发觉内存不增长了。 这就可以联想到是在new 对象的时候yii2内部自己执行了什么操作,然后导致内存泄漏。 什么方法是new 的时候就执行的呢。。。 对的 构造方法 __construct 。 然后 我一步一步的从model 查到object 发觉都没有能引起泄漏的地方。 这个时候我们不妨换个思路, 既然是yii2框架下出现的泄漏, 那肯定就是yii2独有的功能, 那什么功能是yii2独有的,又是在new 对象的时候就会执行的呢? 行为(Behavior) 发觉我的模型类里面果然有用了行为 behaviors() { [ TimestampBehavior::class,1)"> ]; } 最普通不过的代码。 我们知道 行为最后调用的地方是 yiibaseComponent->attachBehaviors 最后定位到 private function attachBehaviorInternal($name,$behavior) { if (!( instanceof Behavior)) { $behavior = Yii::createObject(); } if (is_int($name)) { $behavior->attach($this$this->_behaviors[] = ; } else { isset($this->_behaviors[])) { $name]->detach(); } $name] = ; } return ; } 我们观察这段代码,发觉他把自己传进去了$behavior->attach($this); 最后调用的是 yiibaseBehavior->attach function attach($owner) { $this->owner = foreach ($this->events() $event => $handler) { $owner->on($event,1)">is_string($handler) ? [$this,1)">$handler] : ); } } 问题总结这个时候答案已经呼之欲出, Yii2为了实现行为这一功能, 把自身this传进去,以便能注册事件、触发事件、解除事件。 这就导致了一个循环引用的问题。 所以导致对象refcount一直不为0 一直回收不了。 接下来就好办了。将查询换成原始的连接试试。果然,内存上升的非常慢了,可以说这才是正常现象。现在的内存也就是50m左右,cpu也稳定在7%左右。? 代码优化后,再跑脚本,1分钟左右吧,脚本就跑完了。重点是不会再报出内存错误了。所以,以后考虑问题还是要深入。敢于质疑。以后如果遇到这种内存错误,一定要先检查自己的代码是不是有内存泄漏的地方。不要想着先设置php的内存。这样只会治标不治本。 总结1、从开发速度方面,借助于gii脚手架,可以快速生成代码,也就是说搭建一个可以增删改查的系统可能一行代码都不用写,而且集成了jquery和bootstrap,特效和样式基本也不需要写了,这对于设计和审美能力普遍较差的后端程序员来说简直是一大福利。不过在前后端完全的分离的趋势下,Yii2前后端的耦合的还是有些重了。 2、从代码的可读性方面,Yii不会为了刻板地遵照某种设计模式而对代码进行过度的设计。基本上类在IDE里不借助第三方组件是可以跳转阅读源码的。这点上Yii要比Laravel略胜一筹。 3、从开源生态圈方面,Yii因为人少,稍微偏门一点的资料就很少,需要强大的谷歌能力和阅读英文文档的能力。 不可否认,Yii是一个优秀的开发框架,值得PHP开发者上手学习,踩坑的过程也是一种成长与积累。最后祝愿PHP小伙伴们都健健康康,事业有成。 ? END(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |