[ 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 语言实现:

<?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);

学院君 has written 703 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>