Archive for the 'PHP' Category

PHP模拟BigPipe

前不久新浪微博改版,前端引入了BigPipe(最早是Facebook提出的),页面载入速度嗖嗖嗖地上去了。。那末为神马BigPipe能这么快呢?
与AJAX相似,BigPipe使页面可以按区块渲染。但与AJAX不同的是,BigPipe不需要在页面载入后再通过XMLHttpRequest向服务器发起异步请求,而是(通过使用缓冲区)在页面载入过程中先输出页面布局,并在HTML中预留各个页面区块(Facebook称为Pagelet),在页面底部才输出Javascript代码,DOM操作,从而对区块进行内容填充,也就是把内容赋给innerHTML。
这么做的好处是,在页面加载的过程中,把一些响应时间相对较长的输出放到了页面底部,而页面整体轮廓先显示出来,减少用户的等待时间;此外,与Ajax相比,BigPipe不需要进行多次请求就能完全显示内容,降低了服务器的负担。
下面用PHP模拟一下BigPipe,代码里用sleep(1)模拟一些运算的时间损耗(要先关闭Apache的GZip):

<!doctype>
<html>
<head>
	<meta charset="utf-8" />
	<title>PHP模拟BigPipe</title>
</head>
<style>
.section{height:200px;}
#con1{background:#ef8090;}
#con2{background:#fef000;}
#con3{background:#19c000;}
</style>
<body>
	<div class="wrapper">
		<div class="section" id="con1">页头内容,正在加载……</div>
		<div class="section" id="con2">正文内容,正在加载……</div>
		<div class="section" id="con3">页尾内容,正在加载……</div>
	</div>
	<?php
	/*
	 * 输出缓存区
	 */
	function flush_now(){
		ob_flush();
		flush();
	}
	flush_now();
	?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con1").innerHTML = "<?php echo str_repeat(\'内容1\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con2").innerHTML = "<?php echo str_repeat(\'内容2\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con3").innerHTML = "<?php echo str_repeat(\'内容3\', 200) ?>";
	</script>
	<?php flush_now()?>
</body>
</html>

这样,打开页面后,页头、正文和页尾的内容一开始显示的是“正在加载……”,但随后内容会逐一显示出来,肿么样,很神奇吧。
但话说回来,这只是模拟的BigPipe,因为每个区块的内容输出之间是阻塞的,需要等待前一个区块处理完了,才能接着处理后一个。而要真正实现BigPipe就得使用多线程,可以通过CURL的异步模式或者pctnl。

关于一些编程语言的运算符“优先级”

Tips:这个问题是铁军师兄发现并提出的,我写篇博客梳理一下。
当我们在学习一门编程语言的时候,我们经常会看到类似这样的一句话:运算符优先级是一套规则。该规则在计算表达式时控制运算符执行的顺序。具有较高优先级的运算符先于较低优先级的运算符执行。
那么事实是不是果真如此呢,下面让我们用Javascript等几门语言为例来验证一下。
首先,在官方的ECMA-262/Core JavaScript手册以及权威的MDN Docs中,分别是这样介绍Javascript运算符优先级的:

All operators have a pre-defined execution priority or precedence. Within a given complex expression, operations will be performed in order of priority.

Operator precedence determines the order in which operators are evaluated. Operators with higher precedence are evaluated first.

两段介绍的意思大致是一样的,都是说运算符优先级决定了运算符执行的先后顺序。运算符优先级高的运算符会被先执行(计算)。我们以往的编程经验似乎也在告诉我们这是真理,然而请先看下面一段代码:

var a=1;
function fn(){
    console.log(a);
}
var b = a++ + fn();

按照Javascript官方手册上对运算符优先级的定义以及的运算符优先级表(如图所示或参考http://rx4ajax-jscore.com/ecmacore/operator/predence.html),我们可以推算运行结果:在最后一行代码中,由于函数调用的括号运算符“()”优先级比自增运算符“++”和算术运算符“+”都要高,所以会先被执行。所以在控制台中被log的a的值应该是1。而事实上,结果是2。这说明了什么?——虽然“()”运算符的优先级比“++”要来得高,但a++却先于fn()被执行。
javascript优先级
图节选自Javascript运算符优先级表(http://rx4ajax-jscore.com/ecmacore/operator/predence.html)
那么在Javascript中是这种状况,在其他语言中又会是什么情况呢?随后,我们分别测试了PHP、C/C++、Java以及C#版本的代码(Python和Ruby中没有自增运算符)。猜猜结果各是什么?
PHP版:

$a=1;
function fn($a1){
    echo $a1;
}
$b = $a++ + fn($a);

C++版:

#include "stdafx.h"
#include <iostream>
using namespace std;
int fn(int a1)
{
	cout<<a1;
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	int a = 1;
	int b = a++ + fn(a);
	int i;
	cin>>i;
	return 0;
}

揭晓答案:
只有C/C++输出了1,其他语言都输出了2。
这样看来似乎最传统的编译型语言C/C++能得出与其运算符优先级定义相符合的结果。而事实上关于运算符优先级,PHP手册是这样定义的:

The precedence of an operator specifies how “tightly” it binds two expressions together.

意思是运算符优先级决定了两个表达式之间“紧密程度”——也就是说代码的组合方式。这么一来PHP为什么输出2就能解释了。而C#和Java的代码,如果这么解释也能解释得清楚。不过在Java的运算符中,并没有函数调用的“()”运算符。C#当中却是有的。更有趣的是,在C/C++中,前自增/减运算符“++/–”的优先级居然排在了函数调用运算符“()”的前面,仅次于“::”运算符。
结论:混乱啊。。

PHP Fatal Error重定向跳转

在前端生产环境下,我们需要error_reporting(0)来禁止对用户输出任何错误信息;与此同时,如果能在不可预见地发生Fatal errors和Parse errors的时候跳转到其他页面,而不出现空白,那就能在一定程度上保证用户体验。
我们发现,PHP的error_handlers不能用来处理Fatal errors和Parse errors,于是就有了下面这个方法。
在php.ini文件中可以找到:

; String to output before an error message.
;error_prepend_string = "html code"
; String to output after an error message.
;error_append_string = "html code"

那么我们可以把error_prepend_string或者error_append_string前面的分号去掉,把它的值设置为”<script>window.location.href=’another_page.php’;</script>”,在出错的时候使用javascript的redirect到另一个页面。
但是这种做法似乎不太优雅…正在寻找通过服务器302临时重定向的方法。

PHP SPL File Browser

俗话说不想偷懒的程序员不是好程序员。
所以为了向“好程序员”靠拢,我们要自觉地在工作中偷懒…当然不是要你偷工减料,而是要用程序员应该有的方式来“偷懒”,提高工作效率。
我所在的公司使用的PHP demo环境,出于安全方面的考虑,设置了禁止访问网站目录(列出目录文件列表),造成了我没有办法通过浏览器直接访问目录,查看到目录下的文件列表。上传了个php探针到demo服务器上,发现PHP的系列文件操作函数都被禁用了。这样我也没有办法写一段php脚本通过opendir、chdir、scandir等函数来遍历目录下的文件,输出文件列表到浏览器。
好在经过测试,发现并没有禁用PHP的SPL(SPL是Standard PHP Library的缩写)。这样我们就可以通过SPL中的DirectoryIterator来遍历目录了。
于是就有了下面这个小工具,我想可以把它叫做PHP SPL File Browser或者PHP SPL Directory Browser。它强大得放在任意子目录下都可以浏览整站目录。代码写得很乱,就不献丑了,有需要的童鞋可以发Email给我。