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