前言
对于 php 扩展开发,很多人可能已经不那么陌生了,zend 引擎为了们提供了非常丰富了函数和 macro,来帮助我们很快速的创建一个标准的 php 类,然而,当我们在使用自定义的数据结构(struct), 并想把我们自己定义的数据结构封装成 php 的类的时候可能就会有些困惑,因为我们都知道 php 中的所有变量都是通过 zval 来存储的,而我们自定义的数据结构要怎样才能和 zval 实现完美的对接呢? 以前我通常采用的一种方式就是使用 zend 引擎提供的资源类型,因为资源类型的封装中包含了通用的数据类型,而且有很丰富的函数来操作资源,所以使用起来很简单也很方便。然而,强大的 zend 引擎真的没有其他方式扩展数据结构了吗?当然不是!下面就来介绍一个更加优雅的方式。要弄明白,首要要搞清 php 内核是如何创建对象的。
如何创建一个对象
我们首先来探讨下如何创建一个 PHP 对象。为此我们将会用到object_and_properties_init
之类的一些宏。
1// 创建一个SomeClass类型的对象,并且把properties_hashtable中的变量作为其属性值
2zval *obj;
3MAKE_STD_ZVAL(obj);
4object_and_properties_init(obj, class_entry_of_SomeClass, properties_hashtable);
5
6// 创建一个没有属性的对象
7zval *obj;
8MAKE_STD_ZVAL(obj);
9object_init_ex(obj, class_entry_of_SomeClass);
10// = object_and_properties_init(obj, class_entry_of_SomeClass, NULL)
11
12// 创建一个stdClass的对象
13zval *obj;
14MAKE_STD_ZVAL(obj);
15object_init(obj);
16// = object_init_ex(obj, NULL) = object_and_properties_init(obj, NULL, NULL);
在上面的例子中,最后一种情况下,当你创建一个stdClass
的对象后,通常将会给它添加属性。这时如果使用
zend_update_property
之类的函数,是不起作用的,取而代之的是add_property
宏函数:
1add_property_long(obj, "id", id);
2add_property_string(obj, "name", name, 1); // 1 表示使用字符串的副本
3add_property_bool(obj, "isAdmin", is_admin);
4// 同样有_null(), _double(), _stringl(), _resource()和_zval()
那么当一个对象被创建的时候到底发生了什么呢?想知道真相,就需要去阅读_object_and_properties_init
函数的源码(位于 phpsrc/Zend/zend_API.c 中):
1/* This function requires 'properties' to contain all props declared in the
2 * class and all props being public. If only a subset is given or the class
3 * has protected members then you need to merge the properties separately by
4 * calling zend_merge_properties(). */
5ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties ZEND_FILE_LINE_DC TSRMLS_DC)
6{
7 zend_object *object;
8
9 if (class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
10 char *what = (class_type->ce_flags & ZEND_ACC_INTERFACE) ? "interface"
11 :((class_type->ce_flags & ZEND_ACC_TRAIT) == ZEND_ACC_TRAIT) ? "trait"
12 : "abstract class";
13 zend_error(E_ERROR, "Cannot instantiate %s %s", what, class_type->name);
14 }
15
16 zend_update_class_constants(class_type TSRMLS_CC);
17
18 Z_TYPE_P(arg) = IS_OBJECT;
19 if (class_type->create_object == NULL) {
20 Z_OBJVAL_P(arg) = zend_objects_new(&object, class_type TSRMLS_CC);
21 if (properties) {
22 object->properties = properties;
23 object->properties_table = NULL;
24 } else {
25 object_properties_init(object, class_type);
26 }
27 } else {
28 Z_OBJVAL_P(arg) = class_type->create_object(class_type TSRMLS_CC);
29 }
30 return SUCCESS;
31}
这个函数基本上只做了三件事情:首先校验其是否能够被实例化,接着会注册 class 常量,最后也是最重要的一步操作就是
是否该类有create_object
的处理函数,如果有,将会被调用,如果没有,会调用一个默认的实现zend_objects_new
。
下面是zend_objects_new
的源码(位于 phpsrc/Zend/zend_objects.c 中):
1ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC)
2{
3 zend_object_value retval;
4
5 *object = emalloc(sizeof(zend_object));
6 (*object)->ce = class_type;
7 (*object)->properties = NULL;
8 (*object)->properties_table = NULL;
9 (*object)->guards = NULL;
10 retval.handle = zend_objects_store_put(*object, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_sto↷
11 retval.handlers = &std_object_handlers;
12 return retval;
13}
上面的代码中包含了三个很有趣的东西。一个是zend_object
的结构,它的定义如下(位于 phpsrc/Zend/zend.h 中):
1typedef struct _zend_object {
2 zend_class_entry *ce;
3 HashTable *properties;
4 zval **properties_table;
5 HashTable *guards; /* protects from __get/__set ... recursion */
6} zend_object;
这就是一个标准的对象的结构。zend_objects_new
函数声明了一个标准的对象结构并对其进行初始化操作。然后它调用了zend_objects_store_put
函数
来把对象数据存放到一个对象存储器中。对象存存储器仅仅是一个能够动态改变大小的存放zend_object_store_buckets
类型数据的数组。
下面是zend_object_store_bucket
的数据结构(位于 phpsrc/Zend/zend_objects_API.h 中)
1typedef struct _zend_object_store_bucket {
2 zend_bool destructor_called;
3 zend_bool valid;
4 zend_uchar apply_count;
5 union _store_bucket {
6 struct _store_object {
7 void *object;
8 zend_objects_store_dtor_t dtor;
9 zend_objects_free_object_storage_t free_storage;
10 zend_objects_store_clone_t clone;
11 const zend_object_handlers *handlers;
12 zend_uint refcount;
13 gc_root_buffer *buffered;
14 } obj;
15 struct {
16 int next;
17 } free_list;
18 } bucket;
19} zend_object_store_bucket;
这里的主要部分是_store_object
结构,它通过void *object
成员来存储对象,紧跟其后的成员分别用于是它的销毁,释放和拷贝。
接着是一些额外的成员,例如它有自己的refcount
属性,因为一个存储在对象存储器中的对象可以被很多zval
同时引用,php 需要
知道有多少引用以便于它是否能够被释放。此外,handlers
属性和一个 GC root buffer 也被额外保存下来。
我们回到前面的zend_objects_new
函数,它的最后一部操作就是将对象的handlers
设置成默认的std_object_handlers
。
重写 create_object
当你想使用自定义的对象存储时,你至少需要重复上述的三个操作:首先你需要申明并初始化你的对象,其中它需要包含一个标准的
对象作为它结构的一部分。接着你需要将它以及它的一些处理函数一起放入对象存储器中。最后你需要将你自定义的对象处理结构物
赋值给zend_object_value
的handlers
属性中。
下面是一个很简单的示例:
1#ifdef HAVE_CONFIG_H
2#include "config.h"
3#endif
4
5#include "php.h"
6#include "php_ini.h"
7#include "ext/standard/info.h"
8#include "php_test.h"
9
10static int le_test;
11
12zend_class_entry *test_ce;
13
14static zend_object_handlers test_object_handlers;
15
16typedef struct _test_object {
17 zend_object std;
18 long additional_property;
19} test_object;
20
21
22static void test_free_object_storage_handler(test_object *intern TSRMLS_DC)
23{
24 zend_object_std_dtor(&intern->std TSRMLS_CC);
25 efree(intern);
26}
27
28zend_object_value test_create_object_handler(zend_class_entry *class_type TSRMLS_DC)
29{
30 zend_object_value retval;
31 test_object *intern = emalloc(sizeof(test_object));
32 memset(intern, 0, sizeof(test_object));
33
34 zend_object_std_init(&intern->std, class_type TSRMLS_CC);
35 object_properties_init(&intern->std, class_type);
36
37 retval.handle = zend_objects_store_put(
38 intern,
39 (zend_objects_store_dtor_t) zend_objects_destroy_object,
40 (zend_objects_free_object_storage_t) test_free_object_storage_handler,
41 NULL TSRMLS_CC
42 );
43
44 /* Assign the customized object handlers */
45 retval.handlers = &test_object_handlers;
46 return retval;
47}
48
49const zend_function_entry test_methods[] = {
50 PHP_FE_END
51};
52
53
54/* PHP_MINIT_FUNCTION
55 */
56PHP_MINIT_FUNCTION(test)
57{
58 zend_class_entry tmp_ce;
59 INIT_CLASS_ENTRY(tmp_ce, "Test", test_methods);
60 test_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC);
61 test_ce->create_object = test_create_object_handler;
62 memcpy(&test_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
63
64 return SUCCESS;
65}
66
67/* PHP_MSHUTDOWN_FUNCTION
68 */
69PHP_MSHUTDOWN_FUNCTION(test)
70{
71 return SUCCESS;
72}
73
74/* PHP_RINIT_FUNCTION
75 */
76PHP_RINIT_FUNCTION(test)
77{
78 return SUCCESS;
79}
80
81/* PHP_RSHUTDOWN_FUNCTION
82 */
83PHP_RSHUTDOWN_FUNCTION(test)
84{
85 return SUCCESS;
86}
87
88/* PHP_MINFO_FUNCTION
89 */
90PHP_MINFO_FUNCTION(test)
91{
92 php_info_print_table_start();
93 php_info_print_table_header(2, "test support", "enabled");
94 php_info_print_table_end();
95
96}
97
98/* test_functions[]
99 *
100 */
101const zend_function_entry test_functions[] = {
102 PHP_FE_END
103};
104
105/* test_module_entry
106 */
107zend_module_entry test_module_entry = {
108 STANDARD_MODULE_HEADER,
109 "test",
110 NULL,
111 PHP_MINIT(test),
112 PHP_MSHUTDOWN(test),
113 PHP_RINIT(test), /* Replace with NULL if there's nothing to do at request start */
114 PHP_RSHUTDOWN(test), /* Replace with NULL if there's nothing to do at request end */
115 PHP_MINFO(test),
116 PHP_TEST_VERSION,
117 STANDARD_MODULE_PROPERTIES
118};
119
120#ifdef COMPILE_DL_TEST
121ZEND_GET_MODULE(test)
122#endif
Object store handlers
正如前面已经提到的,总共有三个 object storage handlers: 一个是用来做销毁操作,一个是用来处理释放操作,还有一个是用来处理克隆。
你一开始是不是感到有一点点的疑惑,为什么会同时存在dtor
和free
操作,它们看上去像是做的相同的事情。然而实际上是 php 有两个阶段
的对象销毁系统,首先会调用析构函数,然后才会释放空间。两个阶段相互分开,彼此独立。
特别地,在脚本执行完毕,而且所有的对象还存在于内存中的时候,php 会先调用析构函数(之后会执行 RSHUTDOWN 函数),接下来会及时释放 空间,作为关闭执行器的一部分操作。销毁和释放操作独立开是很有必要的,这样能够保证在关闭操作执行的时候不会有析构函数执行,换句 话说,你可能会遇到这种情况,你的代码在一种半关闭的状态下执行。
析构函数的另一个特性就是它不是一定会被执行,例如当执行die
语句后,析构函数就会被跳过。
因此dtor
和free
最本质的区别就是dtor
可以执行用户端的代码,但是并不是必须执行的,而free
操作却总是会被执行,而且free
不必
执行任何 php 代码。这就是为什么大多数情况下,你只需要定义一个自定义的释放函数,然后使用zend_objects_destroy_object
作为析构函数的原因。
而zend_objects_destroy_object
只提供了一个默认的行为,那就是调用__destruct
方法,如果存在的话。另外,即使你用不到__destruct
方法,
你也需要给该dtor
赋值,否则它的派生类也不能使用__destruct
方法。
至此,只剩下 clone handler 了,但是对于它的功能,从名字上我们很快就能理解到。但是它的用法却有一点点的复杂。下面是一个 clone handler 的示例:
1static void test_clone_object_storage_handler(test_object *object, test_object **object_clone_target TSRMLS_DC)
2{
3 /* Create a new object */
4 test_object *object_clone = emalloc(sizeof(test_object));
5 zend_object_std_init(&object_clone->std, object->std.ce TSRMLS_CC);
6 object_properties_init(&object_clone->std, object->std.ce);
7
8 object_clone->additional_property = object->additional_property;
9 *object_clone_target = object_clone;
10}
然后把 clone handler 传递给zend_objects_store_put
函数的最后一个参数:
1retval.handle = zend_object_store_put(
2 intern,
3 (zend_objects_store_dtor_t) zend_objects_destroy_object,
4 (zend_objects_free_object_storage_t) test_free_object_storage_handler,
5 (zend_objects_store_clone_t) test_clone_object_storage_handler
6 TSRMLS_CC
7 );
但是这样做并不能使 clone handler 生效,默认情况下,clone handler 会被忽略。为了使他生效,你需要通过zend_objects_store_clone_obj
用
默认的 clone handler 替换到自己定义的 handlers 结构:
1memcpy(&test_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
2test_object_handler.clone_obj = zend_objects_store_clone_obj;
由于覆盖标准的 clone handler(zend_objects_clone_obj
)会引发一系列的问题:由于对象的属性不会被拷贝,因此
__clone
方法也不会被调用。这就是为什么大多数内部类都是直接指定自己的对象拷贝函数,而不是绕额外的弯子去
使用对象存储器的对象克隆函数。这个方法看起来有点过于样板化,它的源码如下(位于 phpsrc/Zend/zend_objects.c 中):
1ZEND_API zend_object_value zend_objects_clone_obj(zval *zobject TSRMLS_DC)
2{
3 zend_object_value new_obj_val;
4 zend_object *old_object;
5 zend_object *new_object;
6 zend_object_handle handle = Z_OBJ_HANDLE_P(zobject);
7
8 /* assume that create isn't overwritten, so when clone depends on the
9 * overwritten one then it must itself be overwritten */
10 old_object = zend_objects_get_address(zobject TSRMLS_CC);
11 new_obj_val = zend_objects_new(&new_object, old_object->ce TSRMLS_CC);
12
13 zend_objects_clone_members(new_object, new_obj_val, old_object, handle TSRMLS_CC);
14
15 return new_obj_val;
16}
这个函数首先使用zend_objects_get_address
从对象存储器中获取zend_object*
,然后创建一个拥有相同class_entry
的对象(通过zend_objects_new
),
然后调用zend_objects_clone_members
操作来克隆对象的属性,而且会调用__clone
方法,如果存在的话。
使用我们自定义的create_object
handler 来取代zend_objects_new
函数,将会使得我们自定义的 object clone handler 看起来简单许多:
1static zend_object_value test_clone_handler(zval *object TSRMLS_DC)
2{
3 test_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
4 zend_object_value new_object_val = test_create_object_handler(Z_OBJCE_P(object) TSRMLS_CC);
5 test_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
6 /* Clone properties and call __clone */
7 zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
8
9 new_object->additional_property = old_object->additional_property;
10 return new_object_val;
11}
12
13/* ... */
14//MINIT
15test_object_handler.clone_obj = test_clone_handler;
与 object store 的交互操作
通过上面的示例,已经展示过一些与 object store 交互的函数,例如:zend_objects_store_put
,这是一个向 object store 中插入一个元素的操作。
同时也有三个函数用来从 object store 中获取元素。
zend_object_store_get_object_by_handle()
,顾名思义,通过给定一个对象的 handle 来获取该对象。当你已经获取到了一个对象的 handle,却没有访问到它的 zval 时,可以调用此函数。
但是在大多数情况下,你会使用zend_object_store_get_object()
函数,通过给定对象的 zval 来获取它。
第三个函数是zend_objects_get_address()
,该函数类似于zend_object_store_get_object()
函数,但是它返回的类型是zend_object *
而不是void *
。所以这个函数一般很少使用,因为
c 语言允许隐式的类型转换,从void *
类型转换成其他指针类型。
以上三个函数中最重要的一个就是zend_object_store_get_object()
。你将会频繁使用。下面给出一个简单的示例:
1PHP_METHOD(Test, get)
2{
3 zval *object;
4 test_object *intern;
5 if (zend_parse_parameters_none() == FAILURE) {
6 return;
7 }
8 object = getThis();
9 intern = zend_object_store_get_object(object TSRMLS_CC);
10 RETURN_LONG(intern->additional_property);
11}
下面给出完整的示例代码:
1/*
2 +----------------------------------------------------------------------+
3 | Test |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2016 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: liubang <it.liubang@gmail.com> |
16 +----------------------------------------------------------------------+
17*/
18
19/* $Id$ */
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include "php.h"
26#include "php_ini.h"
27#include "ext/standard/info.h"
28#include "php_test.h"
29
30static int le_test;
31
32zend_class_entry *test_ce;
33
34static zend_object_handlers test_object_handlers;
35
36typedef struct _test_object {
37 zend_object std;
38 long additional_property;
39} test_object;
40
41static void test_free_object_storage_handler(test_object *intern TSRMLS_DC)
42{
43 zend_object_std_dtor(&intern->std TSRMLS_CC);
44 efree(intern);
45}
46
47
48static void test_clone_object_storage_handler(test_object *object, test_object **object_clone_target TSRMLS_DC)
49{
50 /* Create a new object */
51 test_object *object_clone = emalloc(sizeof(test_object));
52 zend_object_std_init(&object_clone->std, object->std.ce TSRMLS_CC);
53 object_properties_init(&object_clone->std, object->std.ce);
54
55 object_clone->additional_property = object->additional_property;
56 *object_clone_target = object_clone;
57}
58
59zend_object_value test_create_object_handler(zend_class_entry *class_type TSRMLS_DC)
60{
61 zend_object_value retval;
62 test_object *intern = emalloc(sizeof(test_object));
63 memset(intern, 0, sizeof(test_object));
64
65 zend_object_std_init(&intern->std, class_type TSRMLS_CC);
66 object_properties_init(&intern->std, class_type);
67
68 retval.handle = zend_objects_store_put(
69 intern,
70 (zend_objects_store_dtor_t) zend_objects_destroy_object,
71 (zend_objects_free_object_storage_t) test_free_object_storage_handler,
72 (zend_objects_store_clone_t) test_clone_object_storage_handler
73 TSRMLS_CC
74 );
75
76 /* Assign the customized object handlers */
77 retval.handlers = &test_object_handlers;
78 return retval;
79}
80
81static zend_object_value test_clone_handler(zval *object TSRMLS_DC)
82{
83 test_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
84 zend_object_value new_object_val = test_create_object_handler(Z_OBJCE_P(object) TSRMLS_CC);
85 test_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
86 /* Clone properties and call __clone */
87 zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
88
89 new_object->additional_property = old_object->additional_property;
90 return new_object_val;
91}
92
93PHP_METHOD(Test, set)
94{
95 zval *object;
96 long *val;
97 test_object *intern;
98 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &val) == FAILURE) {
99 RETURN_FALSE;
100 }
101 object = getThis();
102 intern = zend_object_store_get_object(object TSRMLS_CC);
103 intern->additional_property = val;
104 RETURN_TRUE;
105}
106
107PHP_METHOD(Test, get)
108{
109 zval *object;
110 test_object *intern;
111 if (zend_parse_parameters_none() == FAILURE) {
112 return;
113 }
114 object = getThis();
115 intern = zend_object_store_get_object(object TSRMLS_CC);
116 RETURN_LONG(intern->additional_property);
117}
118
119const zend_function_entry test_methods[] = {
120 PHP_ME(Test, set, NULL, ZEND_ACC_PUBLIC)
121 PHP_ME(Test, get, NULL, ZEND_ACC_PUBLIC)
122 PHP_FE_END
123};
124
125
126/* PHP_MINIT_FUNCTION
127 */
128PHP_MINIT_FUNCTION(test)
129{
130 zend_class_entry tmp_ce;
131 INIT_CLASS_ENTRY(tmp_ce, "Test", test_methods);
132 test_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC);
133 test_ce->create_object = test_create_object_handler;
134 memcpy(&test_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
135 test_object_handlers.clone_obj = test_clone_handler;
136
137 return SUCCESS;
138}
139
140/* PHP_MSHUTDOWN_FUNCTION
141 */
142PHP_MSHUTDOWN_FUNCTION(test)
143{
144 return SUCCESS;
145}
146
147/* PHP_RINIT_FUNCTION
148 */
149PHP_RINIT_FUNCTION(test)
150{
151 return SUCCESS;
152}
153
154/* PHP_RSHUTDOWN_FUNCTION
155 */
156PHP_RSHUTDOWN_FUNCTION(test)
157{
158 return SUCCESS;
159}
160
161/* PHP_MINFO_FUNCTION
162 */
163PHP_MINFO_FUNCTION(test)
164{
165 php_info_print_table_start();
166 php_info_print_table_header(2, "test support", "enabled");
167 php_info_print_table_end();
168
169}
170
171/* test_functions[]
172 *
173 */
174const zend_function_entry test_functions[] = {
175 PHP_FE_END
176};
177
178/* test_module_entry
179 */
180zend_module_entry test_module_entry = {
181 STANDARD_MODULE_HEADER,
182 "test",
183 NULL,
184 PHP_MINIT(test),
185 PHP_MSHUTDOWN(test),
186 PHP_RINIT(test), /* Replace with NULL if there's nothing to do at request start */
187 PHP_RSHUTDOWN(test), /* Replace with NULL if there's nothing to do at request end */
188 PHP_MINFO(test),
189 PHP_TEST_VERSION,
190 STANDARD_MODULE_PROPERTIES
191};
192
193#ifdef COMPILE_DL_TEST
194ZEND_GET_MODULE(test)
195#endif
测试并运行:
评论