前言
对于 php 扩展开发,很多人可能已经不那么陌生了,zend 引擎为了们提供了非常丰富了函数和 macro,来帮助我们很快速的创建一个标准的 php 类,然而,当我们在使用自定义的数据结构(struct), 并想把我们自己定义的数据结构封装成 php 的类的时候可能就会有些困惑,因为我们都知道 php 中的所有变量都是通过 zval 来存储的,而我们自定义的数据结构要怎样才能和 zval 实现完美的对接呢? 以前我通常采用的一种方式就是使用 zend 引擎提供的资源类型,因为资源类型的封装中包含了通用的数据类型,而且有很丰富的函数来操作资源,所以使用起来很简单也很方便。然而,强大的 zend 引擎真的没有其他方式扩展数据结构了吗?当然不是!下面就来介绍一个更加优雅的方式。要弄明白,首要要搞清 php 内核是如何创建对象的。
如何创建一个对象
我们首先来探讨下如何创建一个 PHP 对象。为此我们将会用到object_and_properties_init
之类的一些宏。
// 创建一个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
宏函数:
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 中):
/* 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 中):
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 中):
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 中)
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
属性中。
下面是一个很简单的示例:
#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 的示例:
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
函数的最后一个参数:
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 结构:
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 中):
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 看起来简单许多:
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()
。你将会频繁使用。下面给出一个简单的示例:
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);
}
下面给出完整的示例代码:
/*
+----------------------------------------------------------------------+
| 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
测试并运行:
$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)
评论