距离上次写博客,已经有一年了。在动手写以前,老是带着深深的罪恶感。被它折磨许久,终于,仍是,动手了。php
值得庆祝的一件事:最近开始健身了。天天动感单车45分钟,游泳45分钟,真的是(生)爽(不)到(如)爆(死)。html
好了,扯淡完毕,步入正题。mysql
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(); //保存,会发现此行的其它字段都被写成默认值了。
这个例子的问题在于:jquery
$room
,可是只取出了id
字段,而其余字段天然就是默认值。$room->save()
的时候,那些是默认值的字段也被保存到数据库里去了。what!?然而,咱们有什么解决办法呢?提供几种思路:程序员
ActiveRecord
的保存。ActiveRecord
, 使得,当此对象由find()
新建,且字段没有彻底取出,调用save()
方法,抛出异常。ActiveRecord
,使得,当此对象由find()
新建,且字段没有彻底取出,调用save()
方法时,只保存取出过的字段,其余字段被忽略。
/** * @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(); }
这段代码的问题在于:sql
$transaction
的存在乎义是保证整段数据库存储代码要么全成功,要么全失败。transaction
并无达到咱们想要的效果:$a
由于validate()
都没过,因此$transation->commit()
的时候并不会报错。在$transation
块内,全部的save()
都要判断下返回值,若是为false
,则直接抛出异常。数据库
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格式无效”的错误。而参考官方文档,发现这种格式是容许的官方文档。bootstrap
啊啊啊。各类试错,最后发现若是改为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的内存。这样只会治标不治本。
一、从开发速度方面,借助于gii脚手架,能够快速生成代码,也就是说搭建一个能够增删改查的系统可能一行代码都不用写,并且集成了jquery和bootstrap,特效和样式基本也不须要写了,这对于设计和审美能力广泛较差的后端程序员来讲简直是一大福利。不过在先后端彻底的分离的趋势下,Yii2先后端的耦合的仍是有些重了。
二、从代码的可读性方面,Yii不会为了刻板地遵守某种设计模式而对代码进行过分的设计。基本上类在IDE里不借助第三方组件是能够跳转阅读源码的。这点上Yii要比Laravel略胜一筹。
三、从开源生态圈方面,Yii由于人少,稍微偏门一点的资料就不多,须要强大的谷歌能力和阅读英文文档的能力。
不能否认,Yii是一个优秀的开发框架,值得PHP开发者上手学习,踩坑的过程也是一种成长与积累。最后祝愿PHP小伙伴们都健健康康,事业有成。