[翻译][php扩展开发和嵌入式]第11章-php5对象
内容导读
收集整理的这篇技术教程文章主要介绍了[翻译][php扩展开发和嵌入式]第11章-php5对象,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含22366字,纯文字阅读大概需要32分钟。
内容图文
php5对象
将php5的对象和它的先辈php4对象进行比较实在有些不公平, 不过php5对象使用的API函数还是遵循php4的API构建的. 如果你已经阅读了第10章"php4对象", 你将会对本章内容多少有些熟悉. 在开始本章之前, 可以像第10章开始时一样, 重命名扩展为sample3并清理多余的代码, 只保留扩展的骨架代码.
进化史
在php5对象变量中有两个关键的组件. 第一个是一个数值的标识, 它和第9章"资源数据类型"中介绍的数值资源ID非常相似, 扮演了一个用来在对应表中查找对象实例的key的角色. 在这个实例表中的元素包含了到zend_class_entry的引用以及内部的属性表.
第二个元素是对象变量的句柄表, 使用它可以自定义Zend引擎对实例的处理方式. 在本章后面你将看到这个句柄表.
zend_class_entry
类条目是你在用户空间定义的类的内部表示. 正如你在前一章所见, 这个结构通过调用INIT_CLASS_ENTRY()初始化, 参数为类名和它的函数表. 接着在MINIT阶段使用zend_register_internal_class()注册.
zend_class_entry *php_sample3_sc_entry;#define PHP_SAMPLE3_SC_NAME "Sample3_SecondClass"static function_entry php_sample3_sc_functions[] = { { NULL, NULL, NULL }};PHP_MINIT_FUNCTION(sample3){ zend_class_entry ce; INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME, php_sample3_sc_functions); php_sample3_sc_entry = zend_register_internal_class(&ce TSRMLS_CC); return SUCCESS;}
方法
如果你已经阅读了上一章, 你可能就会想"到现在为止看起来几乎一样啊?", 到现在为止, 你是对的. 现在我们开始定义一些对象方法. 你将开始看到一些非常确定的并且大受欢迎的不同.
PHP_METHOD(Sample3_SecondClass, helloWorld){ php_printf("Hello Worldn");}
在Zend引擎2中引入了PHP_METHOD()宏, 它是对PHP_FUNCTION()宏的封装, 将类名和方法名联合起来, 不用像php4中手动定义方法名了. 通过使用这个宏, 在扩展中你的代码和其他维护者的代码的名字空间解析规范就保持一致了.
定义
定义一个方法的实现, 和其他函数一样, 只不过是将它连接到类的函数表中. 除了用于实现的PHP_METHOD()宏, 还有一些新的宏可以用在函数列表的定义中.
PHP_ME(classname, methodname, arg_info, flags)
PHP_ME()相比于第5章"你的第一个扩展"中介绍的PHP_FE()宏, 增加了一个classname参数, 以及末尾的一个flags参数(用来提供public, protected, private, static等访问控制, 以及abstract和其他一些选项). 比如要定义helloWorld方法, 就可以如下定义:
PHP_ME(Sample3_SecondClass,helloWorld,NULL,ZEND_ACC_PUBLIC)
PHP_MALIAS(classname, name, alias, arg_info, flags)
和PHP_FALIAS()宏很像, 这个宏允许你给alias参数描述的方法(同一个类中的)实现提供一个name指定的新名字. 例如, 要复制你的helloWorld方法则可以如下定义
PHP_MALIAS(Sample3_SecondClass, sayHi, helloWorld, NULL, ZEND_ACC_PUBLIC)
PHP_ABSTRACT_ME(classname, methodname, arg_info)
内部类中的抽象方法很像用户空间的抽象方法. 在父类中它只是一个占位符, 期望它的子类提供真正的实现. 你将在接口一节中使用这个宏, 接口是一种特殊的class_entry.
PHP_ME_MAPPING(methodname, functionname, arg_info)
最后一种方法定义的宏是针对同时暴露OOP和非OOP接口的扩展(比如mysqli既有过程化的mysqli_query(), 也有面向对象的MySQLite::query(), 它们都使用了相同的实现.)的. 假定你已经有了一个过程化函数, 比如第5章写的sample_hello_world(), 你就可以使用这个宏以下面的方式将它附加为一个类的方法(要注意, 映射的方法总是public, 非static, 非final的):
PHP_ME_MAPPING(hello, sample_hello_world, NULL)
现在为止, 你看到的方法定义都使用了ZEND_ACC_PUBLIC作为它的flags参数. 实际上, 这个值可以是下面两张表的任意值的位域运算组合, 并且它还可以和本章后面"特殊方法"一节中要介绍的一个特殊方法标记使用位域运算组合.
类型标记 | 含义 |
ZEND_ACC_STATIC | 方法可以静态调用.实际上,这就表示,方法如果通过实例调用, $this或者更确切的说this_ptr,并不会被设置到实例作用域中 |
ZEND_ACC_ABSTRACT | 方法并不是真正的实现.当前方法应该在被直接调用之前被子类覆写. |
ZEND_ACC_FINAL | 方法不能被子类覆写 |
可见性标记 | 含义 |
ZEND_ACC_PUBLIC | 可以在对象外任何作用域调用.这和php4方法的可见性是一样的 |
ZEND_ACC_PROTECTED | 只能在类中或者它的子类中调用 |
ZEND_ACC_PRIVATE | 只能在类中调用 |
比如, 由于你前面定义的Sample3_SecondClass::helloWorld()方法不需要对象实例, 你就可以将它的定义从简单的ZEND_ACC_PUBLIC修改为ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, 这样引擎知道了就不会去提供(实例)了.
魔术方法
除了ZE1的魔术方法外, ZE2新增了很多魔术方法, 如下表(或者可以在http://www.gxlcms.com/中找到)
方法 | 用法 |
__construct(...) | 可选的自动调用的对象构造器(之前定义的是和类名一致的方法).如果__construct()和classname()两种实现都存在,在实例化的过程中,将优先调用__construct() |
__destruct() | 当实例离开作用域,或者请求整个终止,都将导致隐式的调用实例的__destruct()方法去处理一些清理工作,比如关闭文件或网络句柄. |
__clone() | 默认情况下,所有的实例都是真正的引用传值.在php5中,要想真正的拷贝一个对象实例,就要使用clone关键字.当在一个对象实例上调用clone关键字时, __clone()方法就会隐含的被执行,它允许对象去复制一些需要的内部资源数据. |
__toString() | 在用文本表示一个对象时,比如当直接在对象上使用echo或print语句时, __toString()方法将自动的被引擎调用.类如果实现这个魔术方法,应该返回一个包含描述对象的当前状态的字符串. |
__get($var) | 如果脚本中请求一个对象不可见的属性(不存在或者由于访问控制导致不可见)时, __get()魔术方法将被调用,唯一的参数是所请求的属性名.实现可以使用它自己的内部逻辑去确定最合理的返回值返回. |
__set($var, $value) | 和__get()很像, __set()提供了与之相反的能力,它用来处理赋值给对象的不可见属性时的逻辑.__set()的实现可以选择隐式的在标准属性表中创建这些变量,以其他存储机制设置值,或者直接抛出错误并丢弃值. |
__call($fname, $args) | 调用对象的未定义方法时可以通过使用__call()魔术方法实现漂亮的处理.这个方法接受两个参数:被调用的方法名,包含调用时传递的所有实参的数值索引的数组. |
__isset($varname) | php5.1.0之后, isset($obj->prop)的调用不仅是检查$obj中是否有prop这个属性,它还会调用$obj中定义的__isset()方法,动态的评估尝试使用动态的__get()和__set()方法是否能成功读写属性 |
__unset($varname) | 类似于__isset(), php 5.1.0为unset()函数引入了一个简单的OOP接口,它可以用于对象属性,虽然这个属性可能在对象的标准属性表中并不存在,但它可能对于__get()和__set()的动态属性空间是有意义的,因此引入__unset()来解决这个问题. |
还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如ArrayAccess接口以及一些SPL接口.
在一个内部对象的实现中, 每个这样的"魔术方法"都可以和其他方法一样实现, 只要在对象的方法列表中正确的定义PHP_ME()以及PUBLIC访问修饰符即可.对于 __get(), __set(), __call(), __isset()以及__unset(), 它们要求传递参数, 你必须定义恰当的arg_info结构来指出方法需要一个或两个参数. 下面的代码片段展示了这些木梳函数的arg_info和它们对应的PHP_ME()条目:
static ZEND_BEGIN_ARG_INFO_EX(php_sample3_one_arg, 0, 0, 1) ZEND_END_ARG_INFO()static ZEND_BEGIN_ARG_INFO_EX(php_sample3_two_args, 0, 0, 2) ZEND_END_ARG_INFO()static function_entry php_sample3_sc_functions[] = { PHP_ME(Sample3_SecondClass, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(Sample3_SecondClass, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) PHP_ME(Sample3_SecondClass, __clone, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CLONE) PHP_ME(Sample3_SecondClass, __toString, NULL, ZEND_ACC_PUBLIC) PHP_ME(Sample3_SecondClass, __get, php_sample3_one_arg, ZEND_ACC_PUBLIC) PHP_ME(Sample3_SecondClass, __set, php_sample3_two_args, ZEND_ACC_PUBLIC) PHP_ME(Sample3_SecondClass, __call, php_sample3_two_args, ZEND_ACC_PUBLIC) PHP_ME(Sample3_SecondClass, __isset, php_sample3_one_arg, ZEND_ACC_PUBLIC) PHP_ME(Sample3_SecondClass, __unset, php_sample3_one_arg, ZEND_ACC_PUBLIC) { NULL, NULL, NULL }};
要注意__construct, __destruct, __clone使用位域运算符增加了额外的常量. 这三个访问修饰符对于方法而言是特殊的, 它们不能被用于其他地方.
属性
php5中对象属性的访问控制与方法的可见性有所不同. 在标准属性表中定义一个公开属性时, 就像你通常期望的, 你可以使用zend_hash_add()或add_property_*()族函数.
对于受保护的和私有的属性, 则需要使用新的ZEND_API函数:
void zend_mangle_property_name(char **dest, int *dest_length, char *class, int class_length, char *prop, int prop_length, int persistent)
这个函数会分配一块新的内存, 构造一个"