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小伙伴们都健健康康,事业有成。
END
Yii2框架那些折磨人的坑的更多相关文章
- [PHP]Yii2框架的坑
[PHP]Yii2框架的坑.md-/Users/zjh/Documents/我的文章/[PHP]Yii2框架的坑 html{font-family: sans-serif;-ms-text-size- ...
- Yii2框架与MongoDB拓展、Redis拓展的安装流程
@author 周煦辰 2016-03-21 这段时间新上了一个项目,使用的是Yii2框架.这里记录一下Yii2框架.Yii2-Mongo拓展.Yii2-Redis拓展等的安装流程.因为使用的系统是W ...
- Yii2 框架整体结构
Yii2框架是一个非常庞大但是并不臃肿的 php 框架.使用 Yii2 框架,可以极大的提升开发效率. 秉持着要知其然也要知其所以然的思想,花了一周的时间,看了 linuor 的 <深入理解Yi ...
- Yii2框架实现计数器功能
思路: 因为yii2框架没有像tp框架那样的setinc这样的累加函数,所以只能在每次累加之前查出当前的这个字段的值,然后写算法累加入库 例如:
- 关于yii2框架活动记录activeRecord添加默认字段的问题
平时使用sql的时候可以如下添加默认字段flag: "select a.*,0 as flag from user_info a", 对于yii2框架则需要这样: $query = ...
- Yii2框架RESTful API教程(二) - 格式化响应,授权认证和速率限制
之前写过一篇Yii2框架RESTful API教程(一) - 快速入门,今天接着来探究一下Yii2 RESTful的格式化响应,授权认证和速率限制三个部分 一.目录结构 先列出需要改动的文件.目录如下 ...
- Yii2框架RESTful API教程(一) - 快速入门
前不久做一个项目,是用Yii2框架写一套RESTful风格的API,就去查了下<Yii 2.0 权威指南 >,发现上面写得比较简略.所以就在这里写一篇教程贴,希望帮助刚接触Yii2框架RE ...
- yii2框架安装
注意:先把php.ini里面的php_openssl.dll扩展打开 1.下载yii2框架的文件包yii-advanced-app-2.0.7 2.打开路径为advanced下面的init.bat ...
- nginx完美支持yii2框架
nginx完美支持yii2框架 server {listen 80;server_name www.peita.net peita.net;# default_server;access_log /d ...
随机推荐
- 面试总问的jvm调优到底是要干什么?
1. 压力测试的理解,xxx的性能10w/s,对你有意义么? 没有那家卖瓜的会说自己家的不甜,同样,没有哪个开源项目愿意告诉你在对它条件最苛刻的时候压力情况是多少,一般官网号称给你看的性能指标都是在最 ...
- npm报错:无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本
npm报错 在 windows终端输入 vue init webpack app, 创建一个名为 app 的 Vue 项目时报错如下: 无法加载文件 D:\nodejs\node_global\web ...
- ubuntu 16.04源码编译OpenCV教程 | compile opencv on ubuntu 16.04
本文首发于个人博客https://kezunlin.me/post/15f5c3e8/,欢迎阅读! compile opencv on ubuntu 16.04 Series Part 1: comp ...
- 【Linux系列】配置Centos 7的软件源(二)
目的 本文主要介绍以下内容: 设置centos的国内软件源,默认源都是国外的下载软件超级麻烦. ssh登录 下载一个shell或者cmder ssh root@192.168.10.18 #上篇设置的 ...
- 如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
缓存雪崩 数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 比如一个雪崩的简单过程: 1.redis集群大面积故障 2.缓存失 ...
- 【Luogu P1164】小A点菜
题目原链接: Luogu 小A点菜 [解题思路] 常规的0-1背包,不过是求装满整个背包的方案数,只要把0-1背包的状态转移方程稍微改一下就行.因为要求方案数,那么把方程中的max换成sum就行. [ ...
- android 活动监听是否点击某个view
前述(写给做过web前端的人) 在web H5,如果如果判断当前是否点击某个元素,一般会这样写. window.addEventListener("touchstart",func ...
- Java基础面试题及答案(五)
Java Web 64. jsp 和 servlet 有什么区别? jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器 ...
- 【数据结构】之栈(Java语言描述)
在前面的[这篇文章]中,我简单介绍了栈这种数据结构的操作功能,并使用C语言对其进行了代码的编写. Java的JDK中默认为我们提供了栈这种数据结构的API—— Stack . Java中的Stack类 ...
- node_export 安装
目录 安装部署 环境准备 下载安装 启动测试 安装部署 环境准备 主机名 角色 IP 系统版本 内核版本 es01.k8s.com node01 10.0.20.11 CentOS 7.5 5.1.4 ...