被忽略的魔法——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);
}


 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值