前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Php扩展开发(三)面向对象

Php扩展开发(三)面向对象

作者头像
用户2131907
发布2019-02-27 13:30:01
1.1K0
发布2019-02-27 13:30:01
举报

本节将会通过实现一个简单的PHP扩展类,介绍在PHP扩展开发过程中如何实现面向对象。

在PHP扩展实现中,类的创建主要包含三步:

  1. 创建一个全局的zend_class_entry变量,用于存储类的入口。
  2. 创建一个zend_function_entry结构体数组,用于存储类中包含的方法。
  3. 在扩展的MINIT方法中注册类。

下面将对这三个步骤进行展开描述,我们将会继续在[PHP扩展开发 – 构建第一个PHP扩展]一节中创建的 ext_demo_1扩展的基础之上进行开发,这里我们所写的所有代码都在ext_demo_1.c文件中。

创建一个简单的空类

首先,我们创建一个名为php_democlass_entryzend_class_entry结构体变量, 该结构体变量实际存储了我们创建的类的入口。

zend_class_entry *php_democlass_entry;

这里的php_democlass_entry在扩展源文件中是一个全局变量,为了使其它扩展可以使用我们创建的类, 这个全局变量应该在头文件中导出。

接下来,我们创建zend_function_entry结构体数组,这个数组与函数定义时的数组是一样的。

const zend_function_entry ext_demo_1_democlass_functions[] = {
    PHP_FE_END
};

与函数注册不同的是,对于类的创建,我们需要在MINIT方法中注册该类。

PHP_MINIT_FUNCTION(ext_demo_1)
{
    /* 创建一个临时类入口变量 */
    zend_class_entry temp_ce;
    INIT_CLASS_ENTRY(temp_ce, "DemoClass", ext_demo_1_democlass_functions);

    php_democlass_entry = zend_register_internal_class(&temp_ce TSRMLS_CC);
    return SUCCESS;
}

MINIT函数中,首先创建了一个temp_ce变量用于存储临时的类入口,接下来使用INIT_CLASS_ENTRY 宏初始化该变量,之后使用zend_register_internal_class()将该类注册到Zend引擎, 该函数会返回一个最终的类入口,将其赋值给前面创建的全局变量。

这里的INIT_CLASS_ENTRY是一个宏定义:

/* zend_API.h 162-163 */
#define INIT_CLASS_ENTRY(class_container, class_name, functions) \
    INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL)

它接受三个参数,第一个参数为类容器,也就是我们创建的zend_class_entry变量, 第二个参数为我们要创建的对象名称,第三个参数为我们创建的类包含哪些函数

在使用INIT_CLASS_ENTRY之后,都执行了哪些操作呢?跟进该宏定义的实现代码后可以发现, 在该宏的定义中,首先为结构体(zend_class_entry)变量class_container设置name属性, 然后对该结构体变量进行初始化(zend_API.h 176-204)。

现在,我们就有了一个空的类,编译该扩展之后,我们可以使用php --rc DemoClass测试一下:

ext_demo_1 mylxsw$ php --rc DemoClass
Class [ <internal:ext_demo_1> class DemoClass ] {

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [0] {
  }

  - Methods [0] {
  }
}
为PHP类添加方法

接下来,我们为我们刚才创建的空类添加一个名为sayHello的方法。

ext_demo_1.c文件中,创建一个sayHello的方法:

PHP_METHOD(DemoClass, sayHello)
{
    char *name, *str_hello;
    int name_len, str_hello_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }

    str_hello_len = spprintf(&str_hello, 0, "Hello, %s, You are welcome!\n", name);
    RETURN_STRINGL(str_hello, str_hello_len, 0);
}

这里我们创建为DemoClass类创建了一个名为sayHello的方法,该方法接收一个字符串类型的参数, 并且返回Hello, 提供的参数, You are welcome!

不要忘记在头文件php_ext_demo_1.h文件中声明一下,否则编译的时候会因为PHP_ME宏中使用了该方法而报错。

PHP_METHOD(DemoClass, sayHello);

ext_demo_1_democlass_functions结构体数组中加入该方法:

const zend_function_entry ext_demo_1_democlass_functions[] = {
    PHP_ME(DemoClass, sayHello, NULL, ZEND_ACC_PUBLIC) /* sayHello方法为PUBLIC可见性 */
    PHP_FE_END
};

这里的PHP_ME宏与之前函数部分中PHP_FE类似,区别在于增加了第一个参数,用于指定该方法所属的类名, 最后一个参数用于指定方法属性。这里的方法属性包含 ZEND_ACC_PUBLICZEND_ACC_PROTECTEDZEND_ACC_PRIVATEZEND_ACC_STATICZEND_ACC_FINALZEND_ACC_ABSTRACT

其中,前三个参数可以与后面几个组合使用,多个参数组合时,使用|进行分隔, 例如:

PHP_ME(
    Test, protectedFinalStaticMethod, arginfo_xyz,
    ZEND_ACC_PROTECTED | ZEND_ACC_FINAL | ZEND_ACC_STATIC
)

对于抽象方法来说,并不能直接使用ZEND_ACC_ABSTRACT宏,而应该使用Zend定义的 PHP_ABSTRACT_ME(类名, 抽象方法名, 参数信息)取代。

类似于PHP_FUNCTION宏,这里的PHP_METHOD宏展开后如下所示:

/* PHP_METHOD(ClassName, methodName) { } */
void zim_ClassName_methodName(INTERNAL_FUNCTION_PARAMETERS) { }

对于sayHello方法,我们可以使用ZEND_ARG_INFO系列宏为其参数提供类型提示功能。 ZEND_BEGIN_ARG_INFO_EX(democlass_sayhello_args, 0, 0, 1) ZEND_ARG_INFO(0, name) ZEND_END_ARG_INFO(); 添加该段代码之后,需要修改PHP_ME函数的第三个参数NULL为democlass_sayhello_args

重新编译扩展,执行以下PHP脚本测试是否扩展功能正常:

<?php
$demo = new DemoClass();
echo $demo->sayHello('mylxsw');

程序输出:

$ php test.php
Hello, mylxsw, You are welcome!

在类方法内,使用getThis()方法获取当前对象实例,返回值类型为zval *,对应PHP中的$this

为类添加属性

要在创建好的类中添加属性,要使用zend_declare_property_*系列函数。在MINIT方法中, 添加如下代码:

PHP_MINIT_FUNCTION(ext_demo_1)
{
    ...
    zend_declare_property_long(php_democlass_entry, "age", sizeof("age") - 1, 24, ZEND_ACC_PUBLIC TSRMLS_CC);
    ...
}

这里我们使用zend_declare_property_long()函数为DemoClass类添加了一个age字段, 并且设置其可见性为PUBLIC,类型为long型。

下面是zend_declare_property_*系列函数:

ZEND_API int zend_declare_property(zend_class_entry *ce, char *name, int name_length, zval *property, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_ex(zend_class_entry *ce, const char *name, int name_length, zval *property, int access_type, char *doc_comment, int doc_comment_len TSRMLS_DC);

ZEND_API int zend_declare_property_null(zend_class_entry *ce, char *name, int name_length, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_bool(zend_class_entry *ce, char *name, int name_length, long value, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_long(zend_class_entry *ce, char *name, int name_length, long value, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_double(zend_class_entry *ce, char *name, int name_length, double value, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_string(zend_class_entry *ce, char *name, int name_length, char *value, int access_type TSRMLS_DC);

ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, char *name, int name_length, char *value, int value_len, int access_type TSRMLS_DC);

要访问类中的属性,比如获取属性的值或者是修改属性的值等,使用zend_read_property()zend_update_property()函数。

/* zend_API.h 326-328 */
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_API void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC);

/* 下面这个是zend_update_property_*系列函数,与declare系列类似,不做详述,参考zend_API.h 309-328 */
ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC);

为了演示,我们注册一个名为age的long型属性,并创建对应的getter/setter方法。

/* getAge方法的参数类型提示,无参数 */
ZEND_BEGIN_ARG_INFO_EX(democlass_getage_args, 0, 0, 0)
ZEND_END_ARG_INFO()

/* setAge方法参数类型提示,一个必须参数,名称$age */
ZEND_BEGIN_ARG_INFO_EX(democlass_setage_args, 0, 0, 1)
    ZEND_ARG_INFO(0, age)
ZEND_END_ARG_INFO()

/* 将创建的方法告诉zend_function_entry,设置可见性为public */
const zend_function_entry ext_demo_1_democlass_functions[] = {
    ...
    PHP_ME(DemoClass, setAge, democlass_setage_args, ZEND_ACC_PUBLIC)
    PHP_ME(DemoClass, getAge, democlass_getage_args, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

/* setAge($age)方法 */
PHP_METHOD(DemoClass, setAge)
{
    long age;
    zval *obj;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &age) == FAILURE) {
        RETURN_NULL();
    }
    obj = getThis();/* 获取$this对象 */
    /* 更新age字段的信息 */
    zend_update_property_long(php_democlass_entry, obj, "age", sizeof("age") - 1, age);
}

/* getAge方法*/
PHP_METHOD(DemoClass, getAge)
{
    zval *obj, *age;
    obj = getThis();
    /* 检索age属性的值 */
    age = zend_read_property(php_democlass_entry, obj, "age", sizeof("age") - 1, 1 TSRMLS_CC);
    RETURN_ZVAL(age, 1, 0);
}

/* MINIT方法*/
PHP_MINIT_FUNCTION(ext_demo_1)
{
    .../* 这里省略了,完成类的注册 */
    /* 声明属性age,并设置为public可见性 */
    zend_declare_property_long(php_democlass_entry, "age", sizeof("age") - 1, 24, ZEND_ACC_PUBLIC TSRMLS_CC);
    return SUCCESS;
}

要创建static属性的话,同样使用zend_declare_property*系列函数,区别是类型标识符需要增加 ZEND_ACC_STATIC。对static字段的操作使用zend_read_static_property()zend_update_static_property*系列函数。

还有一个是类常量,类常量使用zend_declare_class_constant*系列宏声明,使用zend_update_class_constants 进行操作。

接口和继承

与在PHP中使用类和接口类似,在扩展开发中,扩展内部的类也可以继承其它类或者实现接口。

当我们创建的类需要继承一个已经存在的类的时候,我们只需要将注册类的函数修改为zend_register_internal_class_ex

designner_entry = zend_register_internal_class_ex(&temp_designner_ce, person_entry, NULL TSRMLS_CC);

与之前使用zend_register_internal_class不同的是,_ex版本的注册函数增加了两个参数, 第二个参数为要继承的类的全局标识,也就是在创建类的时候我们那个zend_class_entry全局对象。 这里第三个参数为NULL,这个参数的作用是在调用其它扩展类时,如果扩展没有按照规范导出类的全局标识符的话, 我们将第二个参数设置为NULL,第三个参数设为字符串形式的类名,当然,不推荐这样做,例如:

custom_exception_ce = zend_register_internal_class_ex(
    &tmp_ce, NULL, "RuntimeException" TSRMLS_CC
);

接口的创建与类相似,区别在于在接口创建时,在zend_function_entry中,需要将接口所有的方法 使用PHP_ABSTRACT_ME添加,其它步骤与类的创建一样,在MINIT方法中,当初始化类之后,只需要再 设置创建类的ce_flags字段为ZEND_ACC_INTERFACE即可。

zend_class_entry *php_sample3_iface_entry;
PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                        php_sample3_iface_methods);
    php_sample3_iface_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_iface_entry->ce_flags|= ZEND_ACC_INTERFACE;/* 注意这里使用了“|” */
    ...

如果创建的类要实现接口,只需要再使用zend_class_implements()函数添加一下就可以了。

/* DataClass implements Countable, ArrayAccess, IteratorAggregate */
zend_class_implements(
        data_class_ce TSRMLS_CC, 3, spl_ce_Countable, zend_ce_arrayaccess, zend_ce_aggregate
    );

这里的zend_class_implements()函数是个变参函数,第一个参数为需要实现接口的类的zend_class_entry对象,第二个参数为需要实现的接口的个数,其它参数是可变的,都为需要实现的接口。

对象创建

前面我们讲解了如何在PHP扩展开发中创建一个类,这里我们再说一说如何在扩展中实例化一个类,创建对象。 Zend引擎为创建类对象提供了以下三个宏:

/* zend_API.h 359-351 下面宏定义已经展开 */
int object_init(zval *arg);
int object_init_ex(zval *arg, zend_class_entry *ce);
int object_and_properties_init(zval *arg, zend_class_entry *ce, HashTable *properties);

实现范例:

/* 创建一个SomeClass对象,并且传递初始化属性 */
zval *obj;
MAKE_STD_ZVAL(obj);
object_and_properties_init(obj, class_entry_of_SomeClass, properties_hashtable);

/* 创建一个SomeClass对象,使用默认属性 */
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) */

当使用object_init()创建一个stdClass对象之后,我们可能需要为它添加一些属性,这时候需要注意的是, 不要使用zend_update_property函数,而是使用add_property宏。

add_property_long(obj, "id", id);
add_property_string(obj, "name", name, 1); // 1 means the string should be copied
add_property_bool(obj, "isAdmin", is_admin);
// also _null(), _double(), _stringl(), _resource() and _zval()

附录

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    HashTable *guards;
} zend_object;
对象结构

zval中,与对象有关的字段是zvalue_value联合体中的zend_object_value字段, 该类型定义如下:

/* zend_types.h 53-59 */
typedef unsigned int zend_object_handle;
typedef struct _zend_object_handlers zend_object_handlers;

typedef struct _zend_object_value {
    zend_object_handle handle;
    zend_object_handlers *handlers;
} zend_object_value;

该结构体zend_object_value中,zend_object_handle类型的handle为int类型的整数值, 该handle是一个唯一的对象ID标识,用于从对象存储中查询实际的对象。

第二个参数是一个指向zend_object_handlers结构体的指针,该结构体定义了实际对象的行为(zend_object_handlers.h 113-141)。

类结构

在PHP扩展中,Zend引擎定义了zend_class_entry结构体来表示一个类的基本结构。

/* zend.h 418-470 */
struct _zend_class_entry {
    char type;/* 类的类型:ZEND_INTERNAL_CLASS 或者 ZEND_USER_CLASS */
    char *name;/* 类的名称 */
    zend_uint name_length;/* 类名称长度 , sizeof(name) - 1 */
    struct _zend_class_entry *parent; /* 该类的父类 */
    int refcount;/* 引用计数 */
    zend_bool constants_updated;
  /*
   * ce_flags指定了类的基本属性
   *    ZEND_ACC_IMPLICIT_ABSTRACT_CLASS 隐式的抽象类(含有abstract方法)
   *    ZEND_ACC_EXPLICIT_ABSTRACT_CLASS 添加了abstract关键字的抽象类
   *    ZEND_ACC_FINAL_CLASS 该类是final的
   *    ZEND_ACC_INTERFACE 这是一个接口
   */
    zend_uint ce_flags;

    HashTable function_table; /* 方法哈希表 */
    HashTable default_properties; /* 默认属性哈希表 */
    HashTable properties_info; /* 属性信息哈希表 */
    HashTable default_static_members; /* 默认的静态成员变量哈希表 */
  /*
   * type == ZEND_USER_CLASS    ->  &default_static_members
   * type == ZEND_INTERAL_CLASS -> NULL
   */
    HashTable *static_members;
    HashTable constants_table; /* 常量哈希表 */
    const struct _zend_function_entry *builtin_functions; /* 方法定义入口 */

  /* 构造函数、析构函数、clone函数以及其它魔术方法 */
    union _zend_function *constructor;
    union _zend_function *destructor;
    union _zend_function *clone;
    union _zend_function *__get;
    union _zend_function *__set;
    union _zend_function *__unset;
    union _zend_function *__isset;
    union _zend_function *__call;
    union _zend_function *__callstatic;
    union _zend_function *__tostring;
    union _zend_function *serialize_func;
    union _zend_function *unserialize_func;

    zend_class_iterator_funcs iterator_funcs;

    /* 类句柄handler */
    zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC);

  /* 类声明的接口 */
    int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC);
    union _zend_function *(*get_static_method)(zend_class_entry *ce, char* method, int method_len TSRMLS_DC);

    /* 序列化回调函数 */
    int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
    int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);

    zend_class_entry **interfaces; /* 类实现的接口 */
    zend_uint num_interfaces; /* 类实现的接口数目 */

    char *filename; /* 类所在的文件 */
    zend_uint line_start; /* 类定义的开始行 */
    zend_uint line_end;/* 类定义的结束行 */
    char *doc_comment;
    zend_uint doc_comment_len;

    struct _zend_module_entry *module;/* 类所在的模块入口*/
};

参考文献:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-01-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建一个简单的空类
  • 为PHP类添加方法
  • 为类添加属性
  • 接口和继承
  • 对象创建
  • 附录
    • 对象结构
      • 类结构
      • 参考文献:
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档