DDR爱好者之家 Design By 杰米

说点闲话

距离上次写博客,已经有一年了。在动手写之前,总是带着深深的罪恶感。被它折磨许久,终于,还是,动手了。

值得庆祝的一件事:最近开始健身了。每天动感单车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();    //保存,会发现此行的其它字段都被写成默认值了。

总结问题

这个例子的问题在于:

  1. 我从数据库中取出了一行,也就是代码中的$room,但是只取出了id字段,而其他字段自然就是默认值。
  2. 当我$room->save()的时候,那些是默认值的字段也被保存到数据库里去了。what!"color: #ff0000">你的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();
    }

    问题总结

    这段代码的问题在于:

    1. 大家知道$transaction的存在意义是保证整段数据库存储代码要么全成功,要么全失败。
    2. 显然,在这个例子中,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) "color: #ff0000">总结

    1、从开发速度方面,借助于gii脚手架,可以快速生成代码,也就是说搭建一个可以增删改查的系统可能一行代码都不用写,而且集成了jquery和bootstrap,特效和样式基本也不需要写了,这对于设计和审美能力普遍较差的后端程序员来说简直是一大福利。不过在前后端完全的分离的趋势下,Yii2前后端的耦合的还是有些重了。

    2、从代码的可读性方面,Yii不会为了刻板地遵照某种设计模式而对代码进行过度的设计。基本上类在IDE里不借助第三方组件是可以跳转阅读源码的。这点上Yii要比Laravel略胜一筹。

    3、从开源生态圈方面,Yii因为人少,稍微偏门一点的资料就很少,需要强大的谷歌能力和阅读英文文档的能力。

    不可否认,Yii是一个优秀的开发框架,值得PHP开发者上手学习,踩坑的过程也是一种成长与积累。最后祝愿PHP小伙伴们都健健康康,事业有成。

DDR爱好者之家 Design By 杰米
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
DDR爱好者之家 Design By 杰米