Yii2框架中一些折磨人的坑
发布:smiling 来源: PHP粉丝网 添加日期:2022-01-28 10:56:01 浏览: 评论:0
这篇文章主要给大家介绍了关于Yii2框架中一些折磨人的坑,文中通过示例代码介绍的非常详细,对大家学习或者使用Yii2框架具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧。
说点闲话
距离上次写博客,已经有一年了。在动手写之前,总是带着深深的罪恶感。被它折磨许久,终于,还是,动手了。
值得庆祝的一件事:最近开始健身了。每天动感单车45分钟,游泳45分钟,真的是(生)爽(不)到(如)爆(死)。
好了,扯淡完毕,步入正题。
ActiveRecord被莫名写入?
准备知识
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(); //保存,会发现此行的其它字段都被写成默认值了。
总结问题
这个例子的问题在于:
我从数据库中取出了一行,也就是代码中的$room,但是只取出了id字段,而其他字段自然就是默认值。
当我$room->save()的时候,那些是默认值的字段也被保存到数据库里去了,what!?
也就是说,当你想节约资源,不取出所有字段的时候,一定要注意不能保存,否则,很多数据会被莫名修改为默认值。
解决方法
然而,我们有什么解决办法呢?提供几种思路:
自己时刻注意,避免未完全取出的ActiveRecord的保存。
修改或继承ActiveRecord, 使得,当此对象由find()新建,且字段没有完全取出,调用save()方法,抛出异常。
修改或继承ActiveRecord,使得,当此对象由find()新建,且字段没有完全取出,调用save()方法时,只保存取出过的字段,其他字段被忽略。
你的Transaction生效了吗?
代码现场
- /**
- * @property integer $id
- * @property string $name
- **/
- class OcRoom extends ActiveRecord
- {
- public function rules()
- {
- return [['name','string','min'=>2,'max'=>10]];
- }
- ...
- }
- class OcHouse extends ActiveRecord
- {
- public function rules()
- {
- return [['name','string','max'=>10]];
- }
- ...
- }
- $a = new OcRoom();
- $a->name = ''; //name为空字符串,不满足rules()条件。
- $b = new 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();
- }
问题总结
这段代码的问题在于:
大家知道$transaction的存在意义是保证整段数据库存储代码要么全成功,要么全失败。
显然,在这个例子中,transaction并没有达到我们想要的效果:$a因为validate()都没过,所以$transation->commit()的时候并不会报错。
解决方法
在$transation块内,所有的save()都要判断下返回值,如果为false,则直接抛出异常。
'Y-m-d'不被识别?
代码现场
- OcRenterBill extends ActiveRecord
- {
- public function rules()
- {
- return [
- ['start_time','date','format'=>'Y-m-d'],
- ];
- }
- }
- $a = new OcRenterBill();
- $a = '2015-09-12';
- $a->save(); //会报错,说格式不对
问题总结
如果一开始,Yii框架就报错,这个还不算坑。坑的是我在Mac上开发时,这个可以完全正常的工作,而发布到线上环境(Ubuntu)后,就弹出“属性start_time格式无效”的错误。而参考官方文档,发现这种格式是允许的官方文档。
啊啊啊。各种试错,最后发现如果改成php:Y-m-d,世界就清净了。所以,如果你遇到这种问题,感激我吧。
内存泄露
代码现场
- public static function 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 框架的问题那就可以进一步缩小问题。
- public static function actionTest() {
- $total = 10;
- var_dump('开始内存'.memory_get_usage());
- while($total){
- $ret= new User();
- var_dump('end内存'.memory_get_usage());
- unset($ret);
- $total--;
- }
- }
内存还是一直增长,这时候我测试了一个其他的yii2类 发觉内存不增长了,这就可以联想到是在new 对象的时候yii2内部自己执行了什么操作,然后导致内存泄漏。 什么方法是new 的时候就执行的呢。。。 对的 构造方法 __construct,然后 我一步一步的从model 查到object 发觉都没有能引起泄漏的地方。
这个时候我们不妨换个思路, 既然是yii2框架下出现的泄漏, 那肯定就是yii2独有的功能, 那什么功能是yii2独有的,又是在new 对象的时候就会执行的呢?
行为(Behavior) 发觉我的模型类里面果然有用了行为
- public function behaviors()
- {
- return [
- TimestampBehavior::class,
- ];
- }
最普通不过的代码,我们知道 行为最后调用的地方是 yii\base\Component->attachBehaviors 最后定位到
- private function attachBehaviorInternal($name, $behavior)
- {
- if (!($behavior instanceof Behavior)) {
- $behavior = Yii::createObject($behavior);
- }
- if (is_int($name)) {
- $behavior->attach($this);
- $this->_behaviors[] = $behavior;
- } else {
- if (isset($this->_behaviors[$name])) {
- $this->_behaviors[$name]->detach();
- }
- $behavior->attach($this);
- $this->_behaviors[$name] = $behavior;
- }
- return $behavior;
- }
我们观察这段代码,发觉他把自己传进去了$behavior->attach($this); 最后调用的是 yii\base\Behavior->attach
- public function attach($owner)
- {
- $this->owner = $owner;
- foreach ($this->events() as $event => $handler) {
- $owner->on($event, is_string($handler) ? [$this, $handler] : $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小伙伴们都健健康康,事业有成。
Tags: Yii2框架
- 上一篇:laravel通用化的CURD的实现
- 下一篇:最后一页
相关文章
- ·Yii2框架的csrf验证原理分析及token缓存解决方案(2020-04-05)
- ·列举PHP的Yii 2框架的开发优势(2021-06-08)
- ·yii2框架中使用下拉菜单的自动搜索yii-widget-select2实例分析(2021-07-05)
- ·Yii2框架引用bootstrap中日期插件yii2-date-picker的方法(2021-07-05)
- ·Yii2框架实现登录、退出及自动登录功能的方法详解(2021-08-16)
- ·Yii2框架类自动加载机制实例分析(2021-09-16)
- ·Yii2框架数据验证操作实例详解(2021-09-16)
- ·Yii2框架实现登陆添加验证码功能示例(2021-10-16)
- ·Yii2框架redis基本应用示例(2021-10-16)
- ·记录Yii2框架开发微信公众号遇到的问题及解决方法(2021-10-19)
- ·详解在YII2框架中使用UEditor编辑器发布文章(2021-11-01)
推荐文章
热门文章
最新评论文章
- 写给考虑创业的年轻程序员(10)
- PHP新手上路(一)(7)
- 惹恼程序员的十件事(5)
- PHP邮件发送例子,已测试成功(5)
- 致初学者:PHP比ASP优秀的七个理由(4)
- PHP会被淘汰吗?(4)
- PHP新手上路(四)(4)
- 如何去学习PHP?(2)
- 简单入门级php分页代码(2)
- php中邮箱email 电话等格式的验证(2)