前言

对于 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_sto11    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_valuehandlers属性中。

下面是一个很简单的示例:

  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: 一个是用来做销毁操作,一个是用来处理释放操作,还有一个是用来处理克隆。 你一开始是不是感到有一点点的疑惑,为什么会同时存在dtorfree操作,它们看上去像是做的相同的事情。然而实际上是 php 有两个阶段 的对象销毁系统,首先会调用析构函数,然后才会释放空间。两个阶段相互分开,彼此独立。

特别地,在脚本执行完毕,而且所有的对象还存在于内存中的时候,php 会先调用析构函数(之后会执行 RSHUTDOWN 函数),接下来会及时释放 空间,作为关闭执行器的一部分操作。销毁和释放操作独立开是很有必要的,这样能够保证在关闭操作执行的时候不会有析构函数执行,换句 话说,你可能会遇到这种情况,你的代码在一种半关闭的状态下执行。

析构函数的另一个特性就是它不是一定会被执行,例如当执行die语句后,析构函数就会被跳过。

因此dtorfree最本质的区别就是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

测试并运行:

1$test = new Test();
2var_dump($test);
3$test->set(123);
4var_dump($test->get());
5// output
6liubang@venux:~/workspace/c/test$ php tests/test.php
7object(Test)#1 (0) {
8}
9int(123)