PHP扩展开发(三)---类

前面已经了解了函数和参数,今天来了解一下类

例子

定义了一个 study_ext_class 类,里面只有一个 print 方法

类使用 PHP_MEPHP_METHOD 宏,与方法最大的不同的地方是类需要注册

这里我写了一个 init_class 方法,PHP_MINIT_FUNCTION中调用,主要是需要注册类

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(study)
{
    /* If you have INI entries, uncomment these lines
    REGISTER_INI_ENTRIES();
    */
    init_class();
    return SUCCESS;
}
/* }}} */

PHP_METHOD(study_ext_class,print)
{
    php_printf("你调用了study_ext_class的print方法\n");
}

/* }}} */

const zend_function_entry study_class_method[]={
    PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
    PHP_FE_END
};

zend_class_entry *study_ce;
void init_class(){
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
    study_ce = zend_register_internal_class(&ce);
}

PHP_MINIT_FUNCTION

这是我们扩展启动时会执行的一个函数,所以在这里注册类

#define ZEND_MODULE_STARTUP_N(module)       zm_startup_##module

#define INIT_FUNC_ARGS     int type, int module_number

#define ZEND_MODULE_STARTUP_D(module)      int ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
//之后
int zm_startup_study(int type, int module_number);

PHP_METHOD

这两个宏和我们前面函数哪里的 PHP_FEPHP_FUNCTION 差不多

#define ZEND_MN(name) zim_##name
#define ZEND_METHOD(classname, name)   ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))

//PHP_METHOD 最终定义成了这样
void zim_study_ext_class_print(zend_execute_data *execute_data, zval *return_value);

PHP_ME

PHP_ME 相比原来的 PHP_FE 多了几个参数,主要是方法的属性和类名

#define ZEND_ME(classname, name, arg_info, flags)  ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)

flags是方法的属性,我们可以用 | 连接它们

/* method flags (types)方法类型 */
#define ZEND_ACC_STATIC            0x01
#define ZEND_ACC_ABSTRACT      0x02
#define ZEND_ACC_FINAL         0x04
#define ZEND_ACC_IMPLEMENTED_ABSTRACT      0x08

/* method flags (visibility)访问属性 */
/* The order of those must be kept - public < protected < private */
#define ZEND_ACC_PUBLIC        0x100
#define ZEND_ACC_PROTECTED 0x200
#define ZEND_ACC_PRIVATE   0x400
#define ZEND_ACC_PPP_MASK  (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)

#define ZEND_ACC_CHANGED   0x800
#define ZEND_ACC_IMPLICIT_PUBLIC   0x1000

/* method flags (special method detection)构造函数和析构 */
#define ZEND_ACC_CTOR      0x2000
#define ZEND_ACC_DTOR      0x4000

/* method flag used by Closure::__invoke() */
#define ZEND_ACC_USER_ARG_INFO 0x80

/* method flag (bc only), any method that has this flag can be used statically and non statically. */
#define ZEND_ACC_ALLOW_STATIC  0x10000

类注册

完成了上面的,编译&安装,实例化我们的类是是会报错的,因为php不知道你有那些类

方法的话,在最后面通过 ZEND_GET_MODULE 生成了一个 get_module 的接口将方法进行了返回

zend_class_entry *study_ce;
void init_class(){
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
    study_ce = zend_register_internal_class(&ce);
}

INIT_CLASS_ENTRY

这一个宏的作用是生成这个类的结构,包括类的名称,方法,然后返回在ce这个变量中

//太长了,就只贴了一部分
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \
    INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, sizeof(class_name)-1, functions, handle_fcall, handle_propget, handle_propset, NULL, NULL)

#define INIT_CLASS_ENTRY(class_container, class_name, functions) \
    INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL)

zend_register_internal_class

显而易见的是将我们的类的信息告诉php,注册进去,还有一个 zend_register_internal_class_ex 的函数,可以指定父类,然后这个函数返回我们的这个类的指针

构造和析构

函数列表哪里标记一下 ZEND_ACC_CTOR 或者 ZEND_ACC_DTOR 就好了

PHP_METHOD(study_ext_class,__construct)
{
    php_printf("study_ext_class构造函数\n");
}
PHP_METHOD(study_ext_class,__destruct)
{
    php_printf("study_ext_class析构函数\n");
}
const zend_function_entry study_class_method[]={
    PHP_ME(study_ext_class,__construct,NULL,ZEND_ACC_CTOR)/* 构造 */
    PHP_ME(study_ext_class,__destruct,NULL,ZEND_ACC_DTOR)/* 析构 */
    PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
    PHP_FE_END
};

类属性

在初始化注册类的时候,使用 zend_declare_property_* 给我们的类添加属性,还可以给他们赋予默认值

void init_class(){
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
    study_ce = zend_register_internal_class(&ce);
    zend_declare_property_null(study_ce,"attr",sizeof("attr")-1,ZEND_ACC_PUBLIC);
    zend_declare_property_long(study_ce,"num",sizeof("num")-1,100,ZEND_ACC_STATIC|ZEND_ACC_PUBLIC);
}
$a->attr="2333s";
echo '静态属性:study_ext_class::$num:'.study_ext_class::$num."\n";
echo '属性:study_ext_class::$attr:'.$a->attr."\n";

输出

静态属性:study_ext_class::$num:100
属性:study_ext_class::$attr:2333s

类指针和属性读取

上面写了怎么去定义属性,但是如果是在类里面要怎么使用属性呢?我们需要用一个 getThis 的宏来获取当前这个类的指针,我就偷懒直接在原来的 print 中添加了

    zval *attr;
    attr=zend_read_property(Z_OBJCE_P(getThis()),getThis(),"attr",sizeof("attr")-1,0,NULL);
    php_var_dump(attr, 1);
    if(Z_TYPE_P(attr)==IS_STRING){
        php_printf("attr的值为:%s\n",attr->value.str->val);
    }

zend_read_property

这个函数用于获取属性,还有zend_read_static_property,用法相同,不过这个是获取静态的属性,关于更新属性可以使用zend_update_property

ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv);

第一个参数 scope 是这个类的指针,在之前的study_ce = zend_register_internal_class(&ce);获取,不过也可以这样获取Z_OBJCE_P(getThis())

第二个参数 object 是当前的对象,我们可以用getThis这个宏获取

第三个参数和第四个参数分别是 属性的名称和属性的长度

第五个参数 silent 用于是假设属性不存在的情况下是否报错

最后一个参数 rv 为魔术方法所返回的,如果不是魔术方法所返回的是一个NULL值,可以看我下面这个例子

    zval *attr,*rv=NULL;
    attr=zend_read_property(study_ce,getThis(),"attr",sizeof("attr")-1,0,rv);
    if(Z_TYPE_P(attr)==IS_STRING){
        php_printf("attr的值为:%s,%d\n",attr->value.str->val,rv);
    }

getThis

获取对象指针,不多说了

#define EX(element)            ((execute_data)->element)

#define getThis()                          ((Z_TYPE(EX(This)) == IS_OBJECT) ? &EX(This) : NULL)

类参数

其实和函数的参数一样,还有一个类似的zend_parse_method_parameters我用的时候总是错误,还没明白这个函数是干什么的,而且找不到说明的资料=_=,后面附上两个源码的区别再看看

PHP_METHOD(study_ext_class,sum)
{
    zend_long parma_num=0;
    zval* this=getThis();
    zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
    if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&parma_num)==FAILURE){
        RETURN_LONG(-1)
    }
    if(Z_TYPE_P(static_num)==IS_LONG){
        RETURN_LONG(static_num->value.lval+parma_num)
    }
    RETURN_LONG(-1)
}
ZEND_BEGIN_ARG_INFO(sum_arg,0)
ZEND_ARG_INFO(0,num)
ZEND_END_ARG_INFO()

探究

如果我们的第二个参数this_ptr为NULL或者不是OBJECT类型的话,那么效果和zend_parse_parameters一样,我之前填的是this指针,所以跳到了else分支

else分之第一句就是p++;表示字符串往后面移动一位,我填的参数是是一个单独的 l 然后一移动….没啦,后面还有两个va_arg

通过后面这两个得知,我们的两个参数,一个是 zval 的,一个是 zend_class_entry* 我们传入的 this_ptr 参数会赋值给 object 也就是我们后面的第四个参数,第五个是我们类的指针

object = va_arg(va, zval **);
ce = va_arg(va, zend_class_entry *);
*object = this_ptr;

看后面这一段,好像是校验类的,所以我觉得这个zend_parse_method_parameterszend_parse_parameters的区别就在这里,method能够对类进行校验

        if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
            zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
                ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
        }

ZEND_API zend_bool ZEND_FASTCALL instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
{
    if (ce->ce_flags & ZEND_ACC_INTERFACE) {
        return instanceof_interface(instance_ce, ce);
    } else {
        return instanceof_class(instance_ce, ce);
    }
}

static zend_always_inline zend_bool instanceof_class(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
{
    while (instance_ce) {
        if (instance_ce == ce) {//会循环校验父类是否相等
            return 1;
        }
        instance_ce = instance_ce->parent;
    }
    return 0;
}
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...) /* {{{ */
{
    va_list va;
    int retval;
    int flags = 0;

    va_start(va, type_spec);
    retval = zend_parse_va_args(num_args, type_spec, &va, flags);
    va_end(va);

    return retval;
}
/* }}} */

ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const char *type_spec, ...) /* {{{ */
{
    va_list va;
    int retval;
    int flags = 0;
    const char *p = type_spec;
    zval **object;
    zend_class_entry *ce;

    /* Just checking this_ptr is not enough, because fcall_common_helper does not set
     * Z_OBJ(EG(This)) to NULL when calling an internal function with common.scope == NULL.
     * In that case EG(This) would still be the $this from the calling code and we'd take the
     * wrong branch here. */
    zend_bool is_method = EG(current_execute_data)->func->common.scope != NULL;

    if (!is_method || !this_ptr || Z_TYPE_P(this_ptr) != IS_OBJECT) {
        va_start(va, type_spec);
        retval = zend_parse_va_args(num_args, type_spec, &va, flags);
        va_end(va);
    } else {
        p++;

        va_start(va, type_spec);

        object = va_arg(va, zval **);
        ce = va_arg(va, zend_class_entry *);
        *object = this_ptr;

        if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
            zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
                ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
        }

        retval = zend_parse_va_args(num_args, p, &va, flags);
        va_end(va);
    }
    return retval;
}
/* }}} */

使用

这里的type_spec我还加了一个O,因为在源码中,p++;这里跳过了一个字符,那么我们后面retval = zend_parse_va_args(num_args, p, &va, flags);的时候传入的就是 l 了, O 这里应该是可以乱填一个字符的

&this 又传回来了- –

PHP_METHOD(study_ext_class,sum)
{
    zend_long parma_num=0;
    zval* this=getThis();
    zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
    // zval
    if(zend_parse_method_parameters(ZEND_NUM_ARGS(),this,"Ol",&this,study_ce,&parma_num)==FAILURE){
        RETURN_LONG(-1)
    }
    if(Z_TYPE_P(static_num)==IS_LONG){
        RETURN_LONG(static_num->value.lval+parma_num)
    }
    RETURN_LONG(-1)
}
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.