近期我在研究 Symfony2,这是一个全新的、个人认为很有前途的 php 框架。和 Symfony 1.x 相比基本是推倒重来的一个版本,官方号称这是这是最快的 php 框架之一。具体可以自己上官方网站看看。
然而 Symfony2 要求 php 5.3.2+ 以上版本,还需要 php intl 库,而 Mac OS X 10.6 默认自带的 php 没有 intl 扩展,我只好自己安装。php 扩展嘛,最方便的安装方法莫过于用 pecl 安装了,然而:
$ sudo pecl install intl
downloading intl-1.1.2.tgz ...
...
/usr/temp/intl/collator/collator_class.c:92: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:96: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:101: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:107: error: duplicate ‘static’
make: *** [collator/collator_class.lo] Error 1
ERROR: `make' failed
Google 了一番,没什么结果,好多人在问这个问题,但就是没有一个的解决方法。
那就自力更生吧,经过一番“研究”,发现原来是 php 5.3 的使用了新的内存回收方式,zval 结构体也和 php 5.2 不一样了,一些宏也改变了,ZEND_BEGIN_ARG_INFO_EX 这个宏在 php 5.2 是这样的:
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \
zend_arg_info name[] = { \
{ NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },
在 php 5.3 下是这样的:
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },
而 intl 1.1.2 中很多地方都用到了这个宏,比如上面出错的 collator/collator_class.c 文件:
static
ZEND_BEGIN_ARG_INFO_EX( collator_0_args, 0, 0, 0 )
ZEND_END_ARG_INFO()
看见没有,php 5.3 中 ZEND_BEGIN_ARG_INFO_EX 的定义中已经那个包含了一个 static 关键字,而 intl 1.1.2 中用到这个宏的地方还是按照 php 5.2 的方式使用,这个宏经过预处理后,导致出现 static 关键字重复,编译出错。 知道了问题原因,就试着修改下吧。由于 intl 1.1.2 库中是用这个宏的地方很多,不好一个一个该,我就用了一个邪恶的方法,在 intl_common.h 中重新定义这个宏:
// HACK!!!
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \
const zend_arg_info name[] = { \
{ NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },
保存再编译,这次上面的那个错误没有了,但出现了别的错误:
/Users/liang/apps/intl-1.1.2/resourcebundle/resourcebundle_class.c:215: error:‘zval’ has no member named ‘refcount’;
make: *** [resourcebundle/resourcebundle_class.lo] Error 1
这个就是上面说的 zval 结构体的改变了。php 5.3 使用了新的 GC 方式,zval 结构体中的 refcount 成员改名为 refcount__gc,is_ref 改名为 is_ref__gc,而且也有配套的管理这个新成员的宏。经过研究,intl 1.1.2 中只有 3 个地方使用 php 5.2 的方式直接访问了 refcount 成员:
$ grep -r refcount *
msgformat/msgformat_format.c:#define Z_ADDREF_P(z) ((z)->refcount++)
msgformat/msgformat_format.c: /* TODO: needs refcount increase here? */
resourcebundle/resourcebundle_class.c: retval->refcount--;
resourcebundle/resourcebundle_iterator.c: object->refcount--;
resourcebundle/resourcebundle_iterator.c: object->refcount++;
其中 msgformat/msgformat_format.c 文件已经和 php 5.3 兼容了,所以只需要修把后面的三个地方修改为 php 5.3 的方式,那这个错误应该就修改好了。 修改方法很简单,分别打开上面的几个 resourcebundle/resourcebundle_class.c 文件和 resourcebundle/resourcebundle_iterator.c 文件,按照下面的方式修改:
retval->refcount--; => Z_DELREF_P(retval);
bject->refcount--; => Z_DELREF_P(object);
object->refcount++; => Z_ADDREF_P(object);
完成后重新编译,OH YEAH~~~~能通过编译了。不过别高兴得太早,可能还有问题呢。先修改一下 php.ini 中的 intl 配置为:
[intl]
intl.default_locale = zh-CN
然后运行一下测试自带的测试用例,发现有很多测试用例没通过。。。
$ make test
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped : 0
Exts tested : 55
---------------------------------------------------------------------
Number of tests : 78 78
Tests skipped : 0 ( 0.0%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 17 ( 21.8%) ( 21.8%)
Expected fail : 0 ( 0.0%) ( 0.0%)
Tests passed : 61 ( 78.2%) ( 78.2%)
---------------------------------------------------------------------
Time taken : 10 seconds
=====================================================================
=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
get_locale() [tests/collator_get_locale.phpt]
collator_get_sort_key() [tests/collator_get_sort_key.phpt]
datefmt_get_pattern_code and datefmt_set_pattern_code() [tests/dateformat_get_set_pattern.phpt]
datefmt_localtime_code() [tests/dateformat_localtime.phpt]
datefmt_parse_code() [tests/dateformat_parse.phpt]
datefmt_parse_localtime() with parse pos [tests/dateformat_parse_localtime_parsepos.phpt]
datefmt_parse_timestamp_code() with parse pos [tests/dateformat_parse_timestamp_parsepos.phpt]
datefmt_set_timezone_id_code() [tests/dateformat_set_timezone_id.phpt]
numfmt_format() [tests/formatter_format.phpt]
numfmt_format_currency() [tests/formatter_format_currency.phpt]
grapheme() [tests/grapheme.phpt]
locale_get_display_name() [tests/locale_get_display_name.phpt]
locale_get_display_region() [tests/locale_get_display_region.phpt]
locale_get_display_script() [tests/locale_get_display_script.phpt]
locale_get_display_variant() [tests/locale_get_display_variant.phpt]
locale_get_region() [tests/locale_get_region.phpt]
locale_parse_locale() [tests/locale_parse_locale.phpt]
=====================================================================
因为这些测试用例本身也是 php 程序,我就自己在命令行下运行了一遍检查为什么会导致测试失败。发现大部分输出都是正确的,但有一些情况下输出和测试用例中的断言不匹配。为什么会这样呢?我没有深入研究,也不知道为什么。哪位朋友如果熟悉这方面请赐教。
我最后的结论: 我使用了这个自己编译的版本用了一段时间,没发现有什么不正常的地方,所以这种 patch 方式应该是可行的。但是,由于存在部分测试用例没有通过,可能会隐藏着一些意想不到的bug,所以不建议在生产环境下使用。
懒人有福了,patch 后的版本我放到了 github 上,大家可以直接 clone 一份下来编译,不用自己手动修改代码了,链接在此: https://github.com/liangzhenjing/php53-intl