之前吐槽过PHP为什么没__compare
魔术方法《PHP __compare?》,可能开发组觉得没有必要吧,毕竟对象默认的比较一般情况已经够用了。 于是乎怀着no zuo no die心情尝试去实现一下,发现难度比预想要小。但由于拖延症的原因这篇文拖到现在才写,还有一方面就是修改的地方比较多和杂乱。
先看看效果吧!
可以看出,$o1, $o2的比较行为已经被__compare
改变
先看对象比较的实现吧,这里假设我们是有__compare
这个魔术方法的。当两个对象进行比较时会调用zend_std_compare_objects
这个函数,然后让函数检测对象是否注册了__compare
,如果有就优先调用,很简单吧。
这里要注意的是zend_std_compare_objects
是谁触发的,也就是说o1
到底是谁。在这里o1
上面PHP代码的$o2
,o2
才是对应PHP的$o1
。记得Python的object.__cmp__
调用的是第一个对象,PHP有点不一样。因为觉得别扭所以在调用zend_std_call_compare(o2, o1 TSRMLS_CC)
的时候我有意把参数对调了一下,当然清楚这点以后你也可以不必这么做。
还有一点就是zend_std_compare_objects
的返回值, 大于 -> -1, 等于 -> 1, 小于 -> 0 应该没记错吧。zend_std_call_compare
函数返回值是一个 zval *
,return 的时候需要稍微处理一下 return Z_LVAL_P(rv) ? -1 : (Z_LVAL_P(rv) == 0 ? 1 : 0);
zend_std_call_compare
就没什么好说了,直接zend_call_method_with_1_params
调用已注册的__compare
就可以了。以上就完成了对象对比的逻辑。
扯点题外话,有人可能会问我怎么知道调用了zend_std_compare_objects
函数,我的思路是这样的。先了解PHP运行过程,引用鸟哥博客
1.Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)
2.Parsing, 将Tokens转换成简单而有意义的表达式
3.Compilation, 将表达式编译成Opocdes
4.Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
既然是对象,那就从new
关键字开始,Zend/zend_language_scanner.l
得到T_NEW
token,Zend/zend_language_parser.y
观察猜测调用了zend_do_begin_new_object
,以此为关键字在Zend
目录搜索,Zend/zend_compile.c +5287
发现opcode是ZEND_NEW
,继续以此搜索,Zend/zend_vm_def.h +3349
调用(以下文件名就不展开了,lxr跟进就好)object_init_ex -> _object_init_ex -> _object_and_properties_init -> zend_objects_new -> 关键的一步retval.handlers = &std_object_handlers;
, 看过上篇的应该知道retval.handlers
是zend_object_handlers
结构类常用操作的方法集合,而std_object_handlers
就是默认的方法集,zend_std_compare_objects
就包含在里面。
添加__compare
魔术方法的过程比较杂乱枯燥,入手点就是仿照已有的魔术方法,比如你可以在lxr搜索__isset
。这是我当时添加代码时的笔记,以防下面讲漏,先贴一下。http://note.youdao.com/share/?id=12a38d65c426d8ca35bbaa7ff7aff99f&type=note
####zend.h
先往_zend_class_entry
结构添加__compare
这样我们的类就具有这个方法了
####zend_compile.h
这里定义ZEND_COMPARE_FUNC_NAME
宏
对__compare
方法前缀判断(接口部分)
对__compare
方法前缀判断(类部分)并保存
(user class??)
如果当前没实现__compare
方法将继承父类
保存__compare
方法(internal class??)
这里是nullify_handlers
处理,嗯,一段是unticked_class_declaration_statement:
调用的,我也没看懂,unticked user class初始化的时候把方法集设置为NULL
??? unticked_class_declaration_statement 是什么,囧,总之先把代码加上就对了。
####zend_API.h
这个宏好像是只提供给zend_disable_class
使用,暂且设置为NULL
吧,对我们用户层代码也不会有影响
####zend_API.C
添加__compare
声明
赋值__compare
,这也是一个internal才会调用的方法,粗略看了下闭包什么的会用到
对前缀的判断
####编译
以上代码都添加完毕以后到你的PHP源码目录 ./configure
&& make
千万别make install除非你打算替换你系统的PHP。如果没有意外编译完成以后在 your_php_src_path/sapi/sli
会生成一个php
可执行文件,测试可以通过your_php_src_path/sapi/sli/php -f phpfile.php