php 延迟返回数据,被忽略的魔法——php引用之推迟赋值(后期数据延迟绑定)...

被忽略的魔法——php引用之延迟赋值(后期数据延迟绑定)

看到这个主题大家知道我今天要说的是php的变量引用特性,但是延迟赋值又是怎么回事呢?这个主要是我近期优化一些功能时的一个想法,我觉得还算不错,就打算记录下来。看一下下面的伪代码:

// 这段代码有人会说为啥不用联表,因为有些业务需求不用联表的效率是联表的3到20倍

// 我的项目里基本都是此类写法,比之前联表效率提升很多

$a = DB::query("select id from a");

$aid = "";

foreach($a as $v){

$aid .= $v['id'].',';

}

$aid = substr($aid, 0 , -1);

if($aid){

$b = DB::query("select * from b where aid in ({$aid})");

// 此处省略

}

之所以用这段代码举例,因为类似这样的代码很多,大家比较容易理解,但不一定适合用后期延迟赋值,因为这样更容理解,且效率差不多,不过可以和后期延迟赋值形成鲜明对比,让大家更容理解下面的实现的方式。

看完上面的例子我们再看一个复杂的需求,要求数据是获取一人的最近10篇文章列表,且读取每篇文章5条评论,并包含文章发起者和评论人的id,姓名,把是数据打包成指定格式的json返回给客户端,看下面的代码:

// 这种需求用联表获取用户信息远没有搜集用户id做in查询效率高

$data = array();

$article = DB::query("select id,uid,title,content from article where uid={$_GET['uid']} order by id desc limit 10");

foreach($article as $v){

$uid = $v['uid'];

$comment = DB::query("select id,uid,content from comment where aid={$v['id']} order by id asc limit 5");

foreach($comment as $value){

$uid .= ','.$value['uid'];

}

// 这里第二个参数我们要求DB类返回的数组以uid为索引

$member = DB::query("select uid,username from user where uid in({$uid})", 'uid');

$commentList = array();

$data[] = array(

'id' => $v['id'],

'title' => $v['title'],

'content' => $v['content'],

'uid' => $v['uid'],

'username' => $member[$v['uid']]['username'],

'comment' => &$commentList

);

foreach($comment as $value){

$commentList[] = array(

'id' => $value['id'],

'content' => $value['content'],

'uid' => $value['uid'],

'username' => $member[$value['uid']]['username']

)

}

}

echo json_encode($data);

exit;

细心看这段代码就会发现其中$data[]['comment']的值最开始就引用了变量$commentList,之后在后面更改了$commentList的值,同时导致$data[]['comment']值跟着一起发生了改变,这里也是后期延迟赋值,但是比较简单,可以据此了解一下这个实现原理。

我相信大多数人都写过类似的代码,也很少有人觉得这段代码会有问题。我来分析一下这块的逻辑,评论信息因为要每篇文章获取5条,这个没法用简单的sql合并成一条,由于需求只是获取10篇文章,写复杂的sql处理反而不如循环查询的效率(如果你内网延迟较低的情况下),为啥说复杂sql的处理效率慢,如果你考虑的是几千条数据都一样没啥需要注意,但是如果处理的是千万级别的数据量,复杂sql很多情况下不如简单的sql效率高,那么这里就采用循环查询没法继续优化。但是我们看用户信息也在循环里进行查询,这个很不好了,我的项目组里很不希望见到这样的代码的,10篇帖子都是一个人发起的,近50条评论会有很多活跃用户的数据,这样每次循环查询其实查询到重复用户信息的概率非常高,在一个业务逻辑里最好不要从数据库获取重复信息而是复用。如何能让让用户信息达到复用呢?可以在组装最终数据前循环获取文章的评论信息,之后收集用户id,然后再获取用户信息,之后组装最终的数据。这是一种比较简单的解决方案,不是我们今天的重点,下面看一种优雅的处理方式——延迟赋值。

// 这种需求用联表获取用户信息远没有搜集用户id做in查询效率高

$data = array();

$article = DB::query("select id,uid,title,content from article where uid={$_GET['uid']} order by id desc limit 10");

$member = array();

foreach($article as $v){

$comment = DB::query("select id,uid,content from comment where aid={$v['id']} order by id asc limit 5");

$commentList = array();

$data[] = array(

'id' => $v['id'],

'title' => $v['title'],

'content' => $v['content'],

'uid' => $v['uid'],

'username' => &$member[$v['uid']]['username'],

'comment' => &$commentList

);

foreach($comment as $value){

$commentList[] = array(

'id' => $value['id'],

'content' => $value['content'],

'uid' => $value['uid'],

'username' => &$member[$value['uid']]['username']

)

}

}

$uid = array_keys($member);

if($uid){

$uid = implode(',', $uid);

$user = DB::query("select uid,username from user where uid in({$uid})", 'uid');

foreach($member as $uid => $value){

$member[$uid]['username'] = $user[$uid]['username'];

}

unset($member,$user);

}

echo json_encode($data);

exit;

这段代码和之前不太一样了,最明显的就是最下面多了一段代码,而且暂时还不知道究竟是干嘛,好像没啥用,我们一步步的看看,首先在循环文章数据前初始化了一个变量$member = array();之后在循环里少了$uid的赋值,以及循环收集评论人的id,并且查询用户数据的sql也不见了,好像到了最下面那段看不懂的代码地方。仔细找了找还发现&$member[$v['uid']]['username']和&$member[$value['uid']]['username']地方多了&引用符号,这就是为啥循环里少了写代码的奥秘。回想一下之前发现$commentList被引用之后在后面进行赋值的,并且改变了$data[]['comment']。这的道理是一样的,先不查询用户信息,只进行一个空的引用,在引用一个不存在的变量时php会先创建这个变量,例如&$member[$v['uid']]['username'],php检测$member是一个数组已经声明,但是$member[$v['uid']]['username']不存在就在内存创建并且值为null。

当循环完文章数据后打印会发现username的信息都是null,当然之前并没用用户信息,php在引用赋值的时候帮我们给了一个null值。之后通过$uid = array_keys($member);获取所有用户的id信息,

为什么array_keys能获取用户id,因为php在引用的时候帮我们创建了$member数组呀,注意一下这里的uid是不重复的哟,之后我们去user表用in检索用户信息,一定注意这里不能把返回的数据赋值给$member因为之前的数据都是引用$member里的数据,如果这里覆盖了$member,内存里两个变量的地址就不一样了,相当于重新创建了一个数组,我们这里赋值给$user,下面的循环是干什么的,当然是修改之前被引用数据的赋值了,我们循环$member变量把$user[$uid]['username']赋值给$member[$uid]['username'],从而改变引用变量的值。在我们把数据绑定到引用变量后千万不要忽略用uset把$member删除了,主要是防止之后的代码里出现操作$member变量的代码,不小心就会把之前绑定好的数据覆盖掉。为啥删除$member之后绑定的数据没有丢失,主要是引用的特性,当多个变量引用一个内存地址时,删除其中一个变量不影响其它变量,除非把所有变量都删除,才会真的删除内存里的数据。php手册是这么解释的“当unset一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了”。unset($user);只是因为$user是一个临时变量,使用完可以直接从内存释放了。

关于这种编程方式我命名为后期数据延迟绑定,之所以标题是延迟变量赋值主要是让大家便于理解。Php中引用的作用非常广泛,本文所举的例子也只局部的一种使用方法,用来解决编程中遇到类似业务需求的一种处理方式,当然后期数据延迟绑定的编程方法也有很广的使用,希望大家不要局限在本文例子的场景上。针对本文例子是我们编程中最常用的一种问题,我编写了一个函数用来处理数据延迟绑定,减少每个地方都要编写数据绑定的逻辑。

/**

* 数据延迟绑定通用方法

*

* @access public

* @param array $bindingVar待绑定的变量

* @param array$data待绑定的数据

* @param mixed$default默认值

* @return void

*/

function bindingData(&$bindingVar, $data, $default=''){

foreach($bindingVar as $key => $tmp){

foreach($tmp as $k => $v){

$bindingVar[$key][$k] = isset($data[$key][$k]) ? $data[$key][$k] : $default;

}

}

unset($bindingVar);

}

采用这个函数我们能把之前处理数据绑定的代码部分改成下面这样:

$uid = array_keys($member);

if($uid){

$user = DB::query("select uid,username from user where uid in({$uid})", 'uid');

bindingData($member, $user);

unset($member,$user);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值