[ PHP 内核与扩展开发系列] 类与面向对象:访问对象实例的属性和方法
上一章节里,我们看了一下如何在 PHP 扩展里定义类与接口,这一章节我们将学习一下如何在 PHP 扩展中操作类的实例 —— 对象。PHP语言中的面向对象其实是分为三个部分来实现的:class、object、refrence。class 就是我们所说的类,可以直观的理解为前面章节中所描述的 zend_class_entry。object 就是类的实际对象。每一个 zval 并不直接包含具体的object,而是通过一个索引(refrence)与其联系。也就是说,每个 class 都有很多个 object 实例,并把他们统一放在一个数组里,每个 zval 只要记住自己相应的 key 就行了。如此一来,我们在传递 zval 时,实际上传递的是一个索引,而不是内存中具体的对象数据。
调用对象方法
为了操作一个对象,我们需要先获取这个对象的实例,而这肯定会涉及调用对象的构造方法。有关如何在扩展中调用 PHP 的函数与对象的方法这里不展开描述了。首先我们先了解下一个 object 在 PHP 内核中到底是如何实现的:typedef struct _zend_object_value {
zend_object_handle handle;
zend_object_handlers *handlers;
} zend_object_value;
// 这里我们再回顾一下 zval 的值 value 的结构。
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
如果我们有一个 zval *tmp
,那么可以通过 tmp->value.obj
来访问到最终保存对象实例的 zend_object_value 结构体,它包含两个成员:
-
zend_object_handle handle
:最终实现是一个unsigned int
值,Zend 会把每个对象放进数组里,这个handle
就是此实例的索引。所以我们在把对象当作参数传递时,只不过传递的是handle
罢了,这样对性能有利,同时也是对象的引用机制的原理。 -
zend_object_handlers *handlers
:这个里面是一组函数指针,我们可以通过它来对对象进行一些操作,比如:添加引用、获取属性等。此结构体在Zend/zend_object_handlers.h
里定义。
<?php
class baby
{
public function __construct()
{
echo "a new baby!\n";
}
public function hello()
{
echo "hello world!\n";
}
}
function test_call()
{
$obj = new baby();
$obj->hello();
}
然后我们在扩展中实现以上 test_call
函数:
zend_class_entry *baby_ce;
int academy_call_user_function(zval** retval, zval* obj, char* function_name, char* paras, ...){ //用于接收参数
short int paras_count = 0;
zval*** parameters = NULL;
long long_tmp;
char *string_tmp;
zval *zval_tmp;
double dou_tmp;
int i; //仅与调用有关的变量
int fun_re, retval_is_null = 0;
HashTable *function_table; //接收参数
paras_count = strlen(paras);
if(paras_count > 0) {
parameters = (zval***)emalloc(sizeof(zval**) * paras_count);
va_list ap;
va_start(ap,paras);
for(i=0; i<paras_count; i++) {
parameters[i] = (zval**)emalloc(sizeof(zval*));
switch(paras[i]) {
case 's':
MAKE_STD_ZVAL(*parameters[i]);
string_tmp = va_arg(ap, char*);
long_tmp = va_arg(ap, long);
ZVAL_STRINGL(*parameters[i], string_tmp, long_tmp, 1);
break;
case 'l':
MAKE_STD_ZVAL(*parameters[i]);
long_tmp = va_arg(ap, long);
ZVAL_LONG(*parameters[i], long_tmp);
break;
case 'd':
MAKE_STD_ZVAL(*parameters[i]);
dou_tmp = va_arg(ap, double);
ZVAL_DOUBLE(*parameters[i], dou_tmp);
break;
case 'n':
MAKE_STD_ZVAL(*parameters[i]);
ZVAL_NULL(*parameters[i]);
break;
case 'z':
zval_tmp = va_arg(ap, zval*);
*parameters[i] = zval_tmp;
break;
case 'b':
MAKE_STD_ZVAL(*parameters[i]);
ZVAL_BOOL(*parameters[i], (int)va_arg(ap, int));
break;
default:
zend_error(E_ERROR, "Unsupported type:%c in walu_call_user_function", paras[i]);
return 0;
}
}
va_end(ap);
} //构造参数执行call_user_function_ex
zval *_function_name;
MAKE_STD_ZVAL(_function_name);
ZVAL_STRINGL(_function_name, function_name, strlen(function_name), 1);
if (retval == NULL) {
retval_is_null = 1;
retval = (zval**)emalloc(sizeof(zval*));
} //开始函数调用
if (obj) {
function_table = &Z_OBJCE_P(obj)->function_table;
} else {
function_table = (CG(function_table));
}
zend_fcall_info fci;
fci.size = sizeof(fci);
fci.function_table = function_table;
fci.object_ptr = obj ? obj : NULL;
fci.function_name = _function_name;
fci.retval_ptr_ptr = retval;
fci.param_count = paras_count;
fci.params = parameters;
fci.no_separation = 1;
fci.symbol_table = NULL;
fun_re = zend_call_function(&fci, NULL TSRMLS_CC); //函数调用结束
if (retval_is_null == 1) {
zval_ptr_dtor(retval);
efree(retval);
}
//free掉parameter及其里面的每个元素zval**,及每个元素zval**对应的zval*
//对于传进来的zval,不进行free,由参数调用者自行free
zval_ptr_dtor(&_function_name);
if(paras_count > 0) {
for (i = 0; i < paras_count; i++) {
if (paras[i] != 'z') {
zval_ptr_dtor(parameters[i]);
}
efree(parameters[i]);
}
efree(parameters);
}
return fun_re;
}
ZEND_FUNCTION(test_call)
{
zval *obj;
MAKE_STD_ZVAL(obj);
object_init_ex(obj, baby_ce);
//如果确认此类没有构造函数就不用调用了。
academy_call_user_function(NULL, obj, "__construct", "");
academy_call_user_function(NULL, obj, "hello", "");
zval_ptr_dtor(&obj);
return;
}
ZEND_METHOD(baby, __construct)
{
printf("a new baby!\n");
}
ZEND_METHOD(baby, hello)
{
printf("hello world!\n");
}
static zend_function_entry baby_method[]={
ZEND_ME(baby, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
ZEND_ME(baby, hello, NULL, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
ZEND_MINIT_FUNCTION(test)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "baby", baby_method);
baby_ce = zend_register_internal_class(&ce TSRMLS_CC);
return SUCCESS;
}
重新编译,执行命令查看是否生效:
php -r "test_call();"
访问对象属性
我们已经看了下如何操作一个对象的方法,下面主要描述与对象属性有关的东西。有关如何对它进行定义的操作我们已经在前面章节描述过了,这里不再叙述,只讲对其的操作。读取对象的属性
ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, char *name, int name_length, zend_bool silent TSRMLS_DC);
ZEND_API zval *zend_read_static_property(zend_class_entry *scope, char *name, int name_length, zend_bool silent TSRMLS_DC);
zend_read_property
函数用于读取对象的属性,而 zend_read_static_property
则用于读取静态属性。可以看出,静态属性是直接保存在类上的,与具体的对象无关。silent
参数等于 0
表示如果属性不存在,则抛出一个 notice 错误;等于 1
表示如果属性不存在不报错。
如果所查的属性不存在,那么此函数将返回 IS_NULL
类型的 zval。
更新对象的属性
ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC);
ZEND_API int zend_update_static_property(zend_class_entry *scope, char *name, int name_length, zval *value TSRMLS_DC);
zend_update_property
用来更新对象的属性,zend_update_static_property
用来更新类的静态属性。如果对象或者类中没有相关的属性,函数将自动的添加上。
读写对象与类属性的实例
我们先看个 PHP 实现的例子:class baby
{
public $age;
public static $area;
public function __construct($age, $area)
{
$this->age = $age;
self::$area = $area;
var_dump($this->age, self::$area);
}
}
对应在 PHP 扩展中的实现:
ZEND_METHOD(baby, __construct)
{
zval *age, *area;
zend_class_entry *ce;
ce = Z_OBJCE_P(getThis());
if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &age, &area) == FAILURE )
{
printf("Error\n");
RETURN_NULL();
}
zend_update_property(ce, getThis(), "age", sizeof("age")-1, age TSRMLS_CC);
zend_update_static_property(ce, "area", sizeof("area")-1, area TSRMLS_CC);
age = NULL;
area = NULL;
age = zend_read_property(ce, getThis(), "age", sizeof("age")-1, 0 TSRMLS_DC);
php_var_dump(&age, 1 TSRMLS_CC);
area = zend_read_static_property(ce, "area", sizeof("area")-1, 0 TSRMLS_DC);
php_var_dump(&area, 1 TSRMLS_CC);
}
一些其它的快捷函数
更新对象与类的属性函数大全:ZEND_API void zend_update_property_null(zend_class_entry *scope, zval *object, char *name, int name_length TSRMLS_DC);
ZEND_API void zend_update_property_bool(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC);
ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC);
ZEND_API void zend_update_property_double(zend_class_entry *scope, zval *object, char *name, int name_length, double value TSRMLS_DC);
ZEND_API void zend_update_property_string(zend_class_entry *scope, zval *object, char *name, int name_length, const char *value TSRMLS_DC);
ZEND_API void zend_update_property_stringl(zend_class_entry *scope, zval *object, char *name, int name_length, const char *value, int value_length TSRMLS_DC);
ZEND_API int zend_update_static_property_null(zend_class_entry *scope, char *name, int name_length TSRMLS_DC);
ZEND_API int zend_update_static_property_bool(zend_class_entry *scope, char *name, int name_length, long value TSRMLS_DC);
ZEND_API int zend_update_static_property_long(zend_class_entry *scope, char *name, int name_length, long value TSRMLS_DC);
ZEND_API int zend_update_static_property_double(zend_class_entry *scope, char *name, int name_length, double value TSRMLS_DC);
ZEND_API int zend_update_static_property_string(zend_class_entry *scope, char *name, int name_length, const char *value TSRMLS_DC);
ZEND_API int zend_update_static_property_stringl(zend_class_entry *scope, char *name, int name_length, const char *value, int value_length TSRMLS_DC);
No Comments