PHP:call_user_func_array 导致的诡异事件

最近,在升级原创榜时,发现了Doggy中Dt模版的一个诡异现象。当访问某些页面时,不定期出现Gateway timeout.
PHP没有报任何错误,也没有任何错误日志或者信息输出。我一度认为是模版的Tag有问题,因为当尝试
删除某些Tag后会解决这个问题。好景不长,更多的页面也冒出此类错误,然而当我把模版内部解析缓存关闭后,则不会出现此类错误。 于是转头跟踪Dt模版的内部调用。Dt模版的设计中,当模版第一次被解析后,将模版转换成Doggy_Dt_NodeList对象,并将它缓存到内部cache中。当模版下次被调用时,如果模版没有变动,则直接调用NodeList的render方法,这样可以减少模版解析的时间。然后,当从缓存中被反序列的Doggy_Dt_NodeList,却无法正确执行filter。看下面的一段代码:

if (isset(Doggy_Dt::$filters[$name])) {

foreach ($args as $i => $argument) {
# name args
if (is_array($argument)) {
foreach ($argument as $n => $arg) {
$args[$i][$n] = $this->resolve($arg);
}
}
else {
# resolve argument values
$args[$i] = $this->resolve($argument);
}
}
array_unshift($args, $object);
$object = call_user_func_array(Doggy_Dt::$filters[$name], $args);
}

罪魁祸首是:call_user_func_array(Doggy_Dt::$filters[$name], $args)。

此处调用的call_user_func_array导致无法autoload class。于是php就悄无声息的挂了。

说是诡异,就是因为,如果不是反序列出来的,则能够正常的调用。
此外,在官方手册中,对于call_user_func_array还有一段注释:

Note: Callbacks registered with functions such as call_user_func() and call_user_func_array() will not be called if there is an uncaught exception thrown in a previous callback.

也就是说,如果之前call_user_func_array()导致了未知的异常,那么后续的call_user_func_array则不会被调用。

后来检查,的确模版中也有错误的filter tag。 但,奇怪的是只有是反序列(unserialize)回来的才会重复此类
现象。 因此,未必是完全如注释所说的原因。
我猜测,有可能是call_user_func_array的错误导致了反序列后的对象无法正确再次调用call_user_func_array,至少在call_user_func_array中无法正确autoload class。

解决方法,其实也是修复上面代码的一个bug,在call_user_func_array前增加is_callable判断。
if (isset(Doggy_Dt::$filters[$name]) && is_callable(Doggy_Dt::$filters[$name])) {

}
else {
Doggy_Log_Helper::warn(’unkown filter:’.$name);
}

于是,世界就太平了。

PHP编写命令行脚本和后台运行程序的注意事项

在一些场合(如开发,测试), 可能需要使用PHP编写一些命令行的处理脚本,或者是长时间
后台运行的任务, 需要注意以下准则:

准则1. 尽量避免使用PHP编写后台运行程序, 尤其是类似while(true){….} 这种循环的处理脚本.

比如,有时候我们需要定期检查数据库,然后有数据进行处理,没有数据等待.
我强烈建议不要使用PHP编写这样的类似service的脚本. php的gc并不稳定, 当运行到一定时候,
会异常退出. 另外, PHP也不擅长做命令行脚本. 看看Phing, 号称PHP port的Ant, 但是性能
糟糕的一塌糊涂, 远不如使用几个unix工具+shell来的快捷. (更别提windows,那基本是不可用).

使用Perl,Python来完成相应的任务吧, 那会让你很愉快, 或者,Ruby也不错.

准则2 编译特殊版本的php
如果一定要沉迷于PHP解决, 那么首先, 重新编译PHP,去掉所有不实用的模块. 增加PHP的稳定性.

准则3 不要直接使用fork 或者 nohup
不要在PHP中直接使用fork来进入后台运行, 也不要直接使用nohup来运行直接运行php 循环脚本.

准则4 使用 shell guard 来完成PHP的后台循环运行
php的脚本只需要处理一次数据后马上退出,不要使用任何while(true) 这样的脚本来挂起等待.
相反的, 可以使用以下的shell gurad 来完成上述工作:
night9.cn# cat guard.sh
DIR=`pwd`
while true; do
echo “start php script ..”
php $DIR/thumbnail_worker.php
echo “respawn the worker…”

上面这种脚本我称之为shell guard.这样的好处是你的后台脚本永远可以可靠的运行,一旦因为php自身出现问题(如内存溢出),那么马上就可以立即重新执行.
对于检查,执行,休眠模式,可以使用和以下类似的:
while true; do
php ./realtime_data_worker.php
echo “paused 10s”
sleep 10
done

上述shell guard的另一个好处就是一旦你更新了Php文件, 下次运行时就是新的了. 而无须kill掉在重启.
特别适合频繁变更的情况,减少你的工作量.

准则5 可以在你PHP脚本中监控执行的情况, 当执行了一定次数或者内存消耗到一定,则exit, 释放占用的内存,
防止内存泄露.
这条一定要和shell guard来配合.

如果你用windows, 抱歉, 这不在我考虑的范围之内.

准则6 如果以上仍然无法解决一些问题, 那么请参考第一条,使用Perl/Python重写. 立刻会药到病除 ;-)

PS: 某些copy & paste的人儿, 转载我的笔记麻烦给个出处. 我现在知道某些人为何使用我不称我,而使用类似night9.cn认为这样的第三人称说法, 都是某些热衷把转载当自己原创使用的人害得,但每次我看到这样的第三人称总会稀稀拉拉掉一地鸡皮疙瘩.

我写的所有笔记和心得都是自己实践, 主要是为自己备忘使用,都是原创,无须声明.
有时候很纳闷, 转载有必要么?
google可以告诉你一切. 减少点碳排放吧.

技巧:用ETag产生更为有效的304 Not Modified

对于大流量的网站, 尽可能使用304 Not Modified, 这将大大节约带宽的使用,也节省你的资金投入.
通常,对于静态文件, 一般都无须配置, 多数的http server能够产生有效的last-modifed-time和Expires header,
同时下次请求的时候自动判断并输出304.
但是对于动态输出的页面, 依赖时间戳并不是一个有效的方式. 很多时候, 我们需要频繁更新文件,但是
文件内容并没有改变. 这是就要用到ETag了.

关于ETag即Entity Tag是W3C在HTTP 1.1中定义的. 对于动态输出,我们可以使用md5值作为etag的值, 这样
可以当内容没有变更,那么就可以直接返回304了.

结合MongoDB的GridFS,ETag更加有用. 当文件被存储到db中的时候, 计算文件的md5值,保存. 当输出gridfs中的文件时候,
同时输出日期和etag.
以下是一个简单的PHP类, 用于检查是否刷新, 这个类首先检查过期时间, 同时检查etag, 并且etag的优先级高于日期检查:

/**
* Cache Validator
*
* A helper class to mainipulate/validate cache.
*
* The code is ported from my oooold project(eps2004),but works!
* @author night
*/
class CZone_Core_Util_HttpCacheValidator {
/**
* Validate client headers and check wheather to send 304 header.
*
* @param int $lastModified timestamp to validate
* @param string $tag ETag to validate
*/
public static function is_expired($lastModified,$tag,$headers){
// $now = time();
// $headers = getallheaders();
$refresh=TRUE;
if(isset($headers["If-Modified-Since"])) {
$arraySince = explode(";", $headers["If-Modified-Since"]);
$since = strtotime($arraySince[0]);
if($since >= $lastModified) $refresh=FALSE;
}
/**
* Check Entity Tag(ETag)
*
* Entity tags are used for comparing two or more entities from the same requested resource.
* HTTP/1.1 uses entity tags in the ETag (section 14.19), If-Match (section 14.24),
* If-None-Match (section 14.26), and If-Range (section 14.27) header
* fields. The definition of how they are used and compared as cache
* validators is in section 13.3.3. An entity tag consists of an opaque
* quoted string, possibly prefixed by a weakness indicator.
*
* entity-tag = [ weak ]
* opaque-tag weak = “W/”
* opaque-tag =quoted-string
*
* A “strong entity tag” MAY be shared by two entities of
* a resource only if they are equivalent by octet equality. A “weak
* entity tag,” indicated by the “W/” prefix, MAY be shared by two
* entities of a resource only if the entities are equivalent and could
* be substituted for each other with no significant change in
* semantics. A weak entity tag can only be used for weak comparison.An
* entity tag MUST be unique across all versions of all entities
* associated with a particular resource. A given entity tag value MAY
* be used for entities obtained by requests on different URIs. The use
* of the same entity tag value in conjunction with entities obtained by
* requests on different URIs does not imply the equivalence of those
* entities.
*
* See HTTP/1.1(W3C)
*
*/
if(isset($headers["If-None-Match"])) { // check ETag
if(strcmp($headers["If-None-Match"], $tag) == 0 ){
$refresh=FALSE;
}
else {
$refresh=TRUE;
}

}
if(isset($headers["If-Match"])) { // check ETag
if(strcmp($headers["If-Match"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
//firefox style,resume download
if(isset($headers["If-Range"])) { // check ETag
if(strcmp($headers["If-Range"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
return $refresh;
}
?>

Patch for build gmagick on mac osx 10.6(snow leopard)

在mac osx 10.6.2(snow leopard)编译gmagick失败. 错误如下:

ld: duplicate symbol _php_gmagick_sc_entry in .libs/gmagick_methods.o and .libs/gmagick_helpers.o
collect2: ld returned 1 exit status
make: *** [gmagick.la] Error 1

感觉很奇怪,因为在centos上没问题. 检查了下gmagick_methods.c和gmagic_helpers.c 也没有重复定义啊.

$ nm .libs/gmagick_methods.o |grep _php_gmagick_sc_entry
000000000000f570 S _php_gmagick_sc_entry
$ nm .libs/gmagick_helpers.o |grep _php_gmagick_sc_entry
00000000000034b8 S _php_gmagick_sc_entry

Oh, ld没错, 的确是重复定义了,由于类型是S,那么还是php_gmagick_sc_entry的声明有问题.
再仔细查看了一下,果然. 由于php_gmagick_sc_entry是在php_gmagick.h中声明,而在gmagick_methods.c
和gmagic_helpers.c中都include了这个文件. 由于没有显示声明为exten导致了问题. 重新加入exten修饰符,
ok.

===========Patch=============
— php_gmagick.h 1970-01-01 17:13:08.000000000 +0800
+++ php_gmagick.h.ns 2009-12-03 01:17:52.000000000 +0800
@@ -18,7 +18,7 @@
*/

#ifndef HAVE_PHP_GMAGICK_H
-# define HAVE_PHP_GMAGICK_H
+#define HAVE_PHP_GMAGICK_H

/* Define Extension Properties */
#define PHP_GMAGICK_EXTNAME “gmagick”
@@ -107,12 +107,12 @@
#endif

/* Class entries */
-zend_class_entry *php_gmagick_sc_entry;
-zend_class_entry *php_gmagickdraw_sc_entry;
-zend_class_entry *php_gmagickpixel_sc_entry;
-zend_class_entry *php_gmagick_exception_class_entry;
-zend_class_entry *php_gmagickdraw_exception_class_entry;
-zend_class_entry *php_gmagickpixel_exception_class_entry;
+extern zend_class_entry *php_gmagick_sc_entry;
+extern zend_class_entry *php_gmagickdraw_sc_entry;
+extern zend_class_entry *php_gmagickpixel_sc_entry;
+extern zend_class_entry *php_gmagick_exception_class_entry;
+extern zend_class_entry *php_gmagickdraw_exception_class_entry;
+extern zend_class_entry *php_gmagickpixel_exception_class_entry;

/* Forward declarations */
PHP_METHOD(gmagick, __construct);
— gmagick.c 1970-01-01 17:13:08.000000000 +0800
+++ gmagick.c.ns 2009-12-03 01:44:20.000000000 +0800
@@ -27,6 +27,13 @@
static zend_object_handlers gmagickdraw_object_handlers;
static zend_object_handlers gmagickpixel_object_handlers;

+zend_class_entry *php_gmagick_sc_entry;
+zend_class_entry *php_gmagickdraw_sc_entry;
+zend_class_entry *php_gmagickpixel_sc_entry;
+zend_class_entry *php_gmagick_exception_class_entry;
+zend_class_entry *php_gmagickdraw_exception_class_entry;
+zend_class_entry *php_gmagickpixel_exception_class_entry;
+
/* {{{ static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
*/
static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
===========END PATCH============

UPDATE: 作者Vito回信很迅速啊, 他只使用Linux,patch已经被采纳了.

解决GraphicsMagick 和 ImageMagick冲突(PHP imagick and gmagick extension)

发现PHP imagick or magickwand无法正确加载. 经过测试发现是由于和gmagick冲突. 解决, 在编译GraphicsMagick时候加入:

–enable-symbol-prefix

重新编译后正常.

Roadsend PHP-开源的PHP代码编译器

Roadsend PHP 是一个开源的php compiler, 可以将你的PHP代码编译成原生的二进制代码, 无需分发php源码.

Roadsend 可以将你的PHP web项目编译成FastCGI的可执行文件,这样apache,nginx可以通过fastcgi方式和编译后的

程序进行通讯. 看起来,PHP 编写的程序可以和C编写的程序有同样的待遇了? 因为都可以生成原生代码么,再也不需要在服务器上安装PHP了?

等等, 没有那么美好. 的确,经Roadsend编译后, 可以不需要PHP解释器,甚至它自带了一个micro-server, 在开发环境下,连web server都不用装. 但是, Roadsend仍然有很多限制:

1. 它的编译器依赖bigloo工作,甚至可以将其看作bigloo的前端. 它是通过bigloo中间代码,然后进一步用gcc联编后生成的二进制文件. 因此, 速度上和纯C的代码仍然有差别,在某些情况,如非静态编译,需要运行时动态include php文件的时候,速度和普通php文件相差无几

2. 支持的extension有限,目前只支持 curl - gtk - mysql - pcre - posix - standard - xml 这几个. 其他如常用的memcached,gd等都不支持.

尽管原生代码编译器的名号有些缺憾,但是, 如果有兴趣的朋友还是可以试试,体验一下. 对了,最大的好处就是编译后的PHP代码是无法还原了,当你不希望别人看到你的源码的时候,倒是个不错的办法.

Roadsend PHP 目前支持Windows, Linux ,Mac OSX . 最新版也支持Linux AMD64位.

你可以从其官方站点下载:

http://www.roadsend.com/home/index.php

基于PHP Tokenizer的模板引擎设计思路

PHP5以后内置了Tokenizer库, 从而可以直接使用内置的词法分析器了. 很简单,只有2个函数:

  • token_get_all 分析给定的PHP源码,并返回分析后的token数组
  • token_name 返回字符形式的token类型

token_get_all 分析的源码必须是完整的PHP语法形式,包括”“.
返回的token数组中每个元素分为2种形式, 元素为数组,则第一个元素为token的名字或者叫类型,
第二个元素是实际的值.
第二种形式就是一个字符串标量,一般是通用的操作符,分隔符等.

使用tokenizer可以实现一些高级的功能,比如,通过分析PHP源码,进行代码重写或者实现
类似java中的XDoclet. 比较实用的例子,如自动生成测试单元的代码框架.

这些都是比较正统的应用,我则考虑利用Tokenizer实现一个更为简单的模板引擎.

我们目前使用的是Smarty,Smarty的优点就是可以作为一个独立的模板语言来使用,
在我们实践中,可以使用smarty封装很多的api从而实现二次开发.

不过,Smarty的缺点也是很明显,
首先,它依赖于复杂的正则表达式来实现模板的解析.占用内存和速度都不太快,当然,这不是主要问题.
其次,由于正则表达式的先天不足,因此对于一些表达式的支持并不高.
此外,过于复杂, 相关的概念太多. 比如block,section,foreach等,其实都可以相互替代.

设计基于Tokenizer的模板引擎可以克服以上缺点,
首先,不依赖复杂的正则表达式,只需要对词法分析器分析出的词(token)进行语法分析即可.
其次,语法分析可以通过2次编译,递归降解的形式来实现,设计可以很简单.

具体实现上,我的计划:
1. 对模板源文件替换相应的标签,使PHP能够进行分析
1. PHP Tokenizer分析tokens
2. 对tokens进行简单的处理形成内部的token数组
3. 第一次编译,将token数组翻译成Runtime库可以识别的bytecode数组
4. Runtime库对bytecode进行运行执行
5. 也可以进一步优化,可以将bytecode编译成普通的PHP文件,直接运行.

其中2,3都可以通过实现PECL扩展来加速. 由于2,3的实现逻辑比较简单,因此并不难,
重点在于4, 实现一个通用的Runtime库, 设计一些标准的bytecode,
大致可以有以下几种:
[E] 执行echo命令
[M] 对象方法调用
[F 普通函数调用
[Do] Do循环
[While] While循环
[For] For 循环
[If] If 指令
[+-/*] 操作符

Runtime可以识别这些指令,然后根据指令的参数数组进行执行相应的操作.
这样,读取bytecode数组,循环,即可实现模板的运行执行.

对于指令(bytecode),开始可以实现一些最核心的,也可为编译器留有一些接口,支持bytecode的扩展接口.
不过,我怀疑是否有用,因为其实再复杂的指令其实都可以转换为几个基本bytecode的.

Public property vs Accessor vs ArrayAccess vs Magic __set/__get/__isset

作了一个小测试, 主要是想看看不同的设计方式对于实际执行的影响.
PHP没有property,姑且把public field作为property好了.
使用Accessor替代public property有什么好处? 似乎很难有特别直接的解释,
按照我的理解, accessor最明显的用途是隔离和清洁, 避免脏数据的污染(但是实践中,很少用到).

对于PHP这种动态语言,对于纯粹的VO, 我认为一个native Array可能更好,没有必要弄一个纯粹的VO class.
所以,accessor应该至少是在DAO/Service或ActiveRecord来实现.

我倾向于Active Record模式, 基于AR的service层可以更为简洁.
缺点也很明显, 如何将AR对象剥离为纯粹的数据集是个比较麻烦的:数据的序列化不好处理,
做什么事都是有代价的,关键在于平衡.

跑题了,回到正题, 我们说的效率,有2种,一个是运行效率,一个是开发效率.
前者对于硬件,代码, 后者对于开发者,人.
我的观点是, 如果一定要取舍,应该优先开发效率,其次才是运行效率,毕竟,
硬件设备是更快更好更便宜的, 而人是最为昂贵的,从TCO来看, 减少人的投入才是关键.

不过,上述观点的前提是,运行效率是要具有一定优化的基础上进行的,
当不影响开发效率的时候,我们需要考虑运行效率, 以便选择合适的方案,
不仅代码的在本机的运行效率,还有考虑是否会影响扩展性,比如是否有利于scale out,而不是
只能scale up来进行扩展.

继续正文 :

SPL中的ArrayAccess都用过,可以以数组形式透明使用对象,看上去很美.
而Magick __set系列方法,则也可以很优雅的存取对象的attribute.
Accessor则是比较正统的方法.
Public属性是仅次于数组的值对象的构建方式.

这几个实现形式,对于实际运行的效率有和区别?

我想,多数人心里有答案,但是,是否完全正确?

测试代码:

class T1 {
public $t;
}

/**
* Test ArrayAccess
*/
class T2 implements ArrayAccess {
private $_vars = array();
//~~ArrayAccess
public function offsetExists($offset){
return isset($this->_vars[$offset]);
}
public function offsetGet($offset){
return $this->_vars[$offset];
}
public function offsetSet($offset,$value){
$this->_vars[$offset] = $value;
}
public function offsetUnset($offset){
unset($this->_vars[$offset]);
}
}

class T3 {
private $_vars=array();

public function __set($name,$value) {
$this->_vars[$name] = $value;
}

public function __unset($name) {
unset($this->_vars[$name]);
return;
}

public function __isset($name) {
return isset($this->_vars[$name]);
}
public function __get($name) {
return $this->_vars[$name];
}
}

class T4 {
private $_vars = array();
public function set($name,$value) {
$this->_vars[$name] = $value;
}
public function get($name) {
return $this->_vars[$name];
}
}

function bench($cnt) {
$obj1 = new T1();
$obj2 = new T2();
$obj3 = new T3();
$obj4 = new T4();

$start1 = microtime(true);
for ($i=0; $i < $cnt; $i++) {
$obj1->t = $i;
$obj1->t;
}
$end1 = microtime(true);

$start2 = microtime(true);
for ($i=0; $i < $cnt; $i++) {
$obj2['t'] = $i;
$obj2['t'];
}
$end2 = microtime(true);

$start3 = microtime(true);
for ($i=0; $i < $cnt; $i++) {
$obj3->t = $i;
$obj3->t;
}
$end3 = microtime(true);

$start4 = microtime(true);
for ($i=0; $i < $cnt; $i++) {
$obj4->set(’t',$i);
$obj4->get(’t');
}
$end4 = microtime(true);

$time1 = $end1-$start1;
$time2 = $end2-$start2;
$time3 = $end3-$start3;
$time4 = $end4-$start4;
echo “————— Bench result ( $cnt ) ————— \n”;
echo “Public property(Base) => $time1 \n”;
echo “ArrayAccess => $time2 \n”;
echo “Magic __set => $time3 \n”;
echo “Accessor => $time4 \n”;
echo “ArrayAccess vs Base => “.sprintf(”%+.2f”,($time2-$time1)/$time1*100).”%\n”;
echo “Magic __set vs Base => “.sprintf(”%+.2f”,($time3-$time1)/$time1*100).”%\n”;
echo “Accessor vs Base => “.sprintf(”%+.2f”,($time4-$time1)/$time1*100).”%\n”;
echo “ArrayAccess vs Accessor => “.sprintf(”%+.2f”,($time2-$time4)/$time4*100).”%\n”;
echo “Magic __set vs Accessor => “.sprintf(”%+.2f”,($time3-$time4)/$time4*100).”%\n”;
echo “Magic __set vs ArrayAccess => “.sprintf(”%+.2f”,($time3-$time2)/$time2*100).”%\n”;
}

bench(10000);
bench(50000);
bench(100000);
bench(500000);
bench(1000000);
?>

输出的结果:

Macintosh-2:doggy night$ php t.php
--------------- Bench result ( 10000 ) ---------------
Public property(Base) => 0.015399932861328
ArrayAccess => 0.082892894744873
Magic __set => 0.076795101165771
Accessor => 0.074687957763672
ArrayAccess vs Base => +438.27%
Magic __set vs Base => +398.67%
Accessor vs Base => +384.99%
ArrayAccess vs Accessor => +10.99%
Magic __set vs Accessor => +2.82%
Magic __set vs ArrayAccess => -7.36%
--------------- Bench result ( 50000 ) ---------------
Public property(Base) => 0.054196119308472
ArrayAccess => 0.3666570186615
Magic __set => 0.36188292503357
Accessor => 0.34752082824707
ArrayAccess vs Base => +576.54%
Magic __set vs Base => +567.73%
Accessor vs Base => +541.23%
ArrayAccess vs Accessor => +5.51%
Magic __set vs Accessor => +4.13%
Magic __set vs ArrayAccess => -1.30%
--------------- Bench result ( 100000 ) ---------------
Public property(Base) => 0.10039806365967
ArrayAccess => 0.729327917099
Magic __set => 0.71878385543823
Accessor => 0.68846893310547
ArrayAccess vs Base => +626.44%
Magic __set vs Base => +615.93%
Accessor vs Base => +585.74%
ArrayAccess vs Accessor => +5.93%
Magic __set vs Accessor => +4.40%
Magic __set vs ArrayAccess => -1.45%
--------------- Bench result ( 500000 ) ---------------
Public property(Base) => 0.4905378818512
ArrayAccess => 3.6530811786652
Magic __set => 3.6181490421295
Accessor => 3.3984169960022
ArrayAccess vs Base => +644.71%
Magic __set vs Base => +637.59%
Accessor vs Base => +592.79%
ArrayAccess vs Accessor => +7.49%
Magic __set vs Accessor => +6.47%
Magic __set vs ArrayAccess => -0.96%
--------------- Bench result ( 1000000 ) ---------------
Public property(Base) => 0.96968817710876
ArrayAccess => 7.2781820297241
Magic __set => 7.235552072525
Accessor => 6.9458839893341
ArrayAccess vs Base => +650.57%
Magic __set vs Base => +646.17%
Accessor vs Base => +616.30%
ArrayAccess vs Accessor => +4.78%
Magic __set vs Accessor => +4.17%
Magic __set vs ArrayAccess => -0.59%

说明:
数字是运行时间,数字越大表明运行越慢.
对比数据中, 正数是前者比后者运行速度要慢的百分比, 负数则表示前者比后者快.
分别测试了正常和一些极端情况下的比较.

和你的直觉是一样吗?

我稍微有点意外:
1. SPL的ArrayAccess比magic __set要慢
2. Accessor 和 __set 的差距没有那么想象中的那么明显

结论:
1. profiling你的代码, 如果可能,将瓶颈的accessor替换为public field,会有很大的提升. ( 我想没有必要担心不遵守约定的赋值行为,
当有人给一个object属性赋值为一个字符串, 抛出一个PHP致命错误并不是什么坏事,呵呵.)

2. 小心使用ArrayAccess, 看上去很美的伪数组和真正的数组差距还是很大的.

3. __set 可以用来充当通用的Accessor,毕竟二者差距在可忍受的范围之内. 或者, 你可以用代码生成来实现accessor.

PHPLinq - LINQ for PHP - Language Integrated Query

PHPLinq - LINQ for PHP - Language Integrated Query

LINQ(Language Integrated Query) 最早实现是在C#中,如今也有了PHP的简单实现。
创意不错,但是目前还比较初级, 个人关注一下.

doggy和mysqlnd

doggy早期版本使用adodb作为底层数据库的driver,从1.2开始,引入mysqlnd作为新的driver.从1.2.4开始,正式启用mysqlnd为首选的driver.虽然windows下的开发仍使用adodb的adapter,
但是我自己的osx开发环境和正式部署中已经不再使用adodb了.虽然,通过编译php的mysqlnd驱动可以间接
发挥一些Mysqlnd的优势,不过没有充分利用其未公开的特性,为此,我实现了一个mysqlnd的adapter,并不复杂.

经过我的自己测试,以及视觉新系统和老系统的测试,mysqlnd的稳定性和速度,效率都不错.尤其在效率,内存的使用上有明显的改善.

其实,最初使用mysqlnd是因为我自己的osx的开发环境编译mysql ext总出现莫名的问题,包括在Linux的64位上也偶尔
出现一些奇怪的问题,于是就尝试mysqlnd,这下彻底解决了libmysql的一些问题(因为我使用ICC静态编译了mysql,
导致php编译中出现一些问题).

mysqlnd已经加入php 5.3,目前也实现了pdo的driver,相信不久会成为默认的mysql的驱动了.
虽然,mysqlnd仍是beta版,不过从作者的blog的反映以及我自己的实际测试,在生产环境中(至少在doggy框架中)
应用是没有问题的.

Next Page »