gpt4 book ai didi

Yii2框架中一些折磨人的坑

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Yii2框架中一些折磨人的坑由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

说点闲话 。

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

值得庆祝的一件事:最近开始健身了。每天动感单车45分钟,游泳45分钟,真的是(生)爽(不)到(如)爆(死).

好了,扯淡完毕,步入正题.

ActiveRecord被莫名写入?

准备知识 。

ActiveRecord的基本用法。如果不理解,可参考这里.

代码现场 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
  * @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!?
  3. 也就是说,当你想节约资源,不取出所有字段的时候,一定要注意不能保存,否则,很多数据会被莫名修改为默认值。

解决方法 。

然而,我们有什么解决办法呢?提供几种思路:

  1. 自己时刻注意,避免未完全取出的ActiveRecord的保存。
  2. 修改或继承ActiveRecord, 使得,当此对象由find()新建,且字段没有完全取出,调用save()方法,抛出异常。
  3. 修改或继承ActiveRecord,使得,当此对象由find()新建,且字段没有完全取出,调用save()方法时,只保存取出过的字段,其他字段被忽略。

你的Transaction生效了吗?

代码现场 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
  * @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'不被识别?

代码现场 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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,世界就清净了。所以,如果你遇到这种问题,感激我吧.

内存泄露 。

代码现场 。

?
1
2
3
4
5
6
7
8
9
10
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 --;
   }
  }

上面代码的内存一直在增长, 按照原本想法来看, 变量被释放了,内存就算增长也不会一直增长。因为每循环一次内存都会被释放.

分析问题 上面这段代码涉及到了数据库的操作,而我们知道,数据库的很多地方都能引起内存泄漏。 所以先屏蔽数据库相关操作, 我手写了一个原生的数据库查询操作, 发现内存正常,没有问题.

?
1
2
3
4
5
6
7
8
9
$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 框架的问题那就可以进一步缩小问题.

?
1
2
3
4
5
6
7
8
9
10
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) 发觉我的模型类里面果然有用了行为 。

?
1
2
3
4
5
6
public function behaviors()
  {
   return [
    TimestampBehavior:: class ,
   ];
  }

最普通不过的代码。 我们知道 行为最后调用的地方是 yii\base\Component->attachBehaviors 最后定位到 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 。

?
1
2
3
4
5
6
7
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小伙伴们都健健康康,事业有成.

原文链接:https://www.cnblogs.com/zydj333/p/12038025.html 。

最后此篇关于Yii2框架中一些折磨人的坑的文章就讲到这里了,如果你想了解更多关于Yii2框架中一些折磨人的坑的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com