386 lines
12 KiB
Markdown
386 lines
12 KiB
Markdown
---
|
||
title: PHP扩展开发(三)---类
|
||
---
|
||
|
||
> 前面已经了解了函数和参数,今天来了解一下类
|
||
|
||
## 例子
|
||
|
||
定义了一个 **study_ext_class** 类,里面只有一个 **print** 方法
|
||
|
||
类使用 **PHP_ME**和**PHP_METHOD** 宏,与方法最大的不同的地方是类需要注册
|
||
|
||
这里我写了一个 **init_class** 方法,**PHP_MINIT_FUNCTION**中调用,主要是需要注册类
|
||
|
||
```c
|
||
/* {{{ 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
|
||
|
||
这是我们扩展启动时会执行的一个函数,所以在这里注册类
|
||
|
||
```c
|
||
#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_FE** ,**PHP_FUNCTION** 差不多
|
||
|
||
```c
|
||
#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** 多了几个参数,主要是方法的属性和类名
|
||
|
||
```c
|
||
#define ZEND_ME(classname, name, arg_info, flags) ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)
|
||
```
|
||
|
||
flags 是方法的属性,我们可以用 **|** 连接它们
|
||
|
||
```c
|
||
/* 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** 的接口将方法进行了返回
|
||
|
||
```c
|
||
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 这个变量中
|
||
|
||
```c
|
||
//太长了,就只贴了一部分
|
||
#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** 就好了
|
||
|
||
```c
|
||
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*\*** 给我们的类添加属性,还可以给他们赋予默认值
|
||
|
||
```c
|
||
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);
|
||
}
|
||
```
|
||
|
||
```php
|
||
$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** 中添加了
|
||
|
||
```c
|
||
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`
|
||
|
||
```c
|
||
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**值,可以看我下面这个例子
|
||
|
||
```c
|
||
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
|
||
|
||
获取对象指针,不多说了
|
||
|
||
```c
|
||
#define EX(element) ((execute_data)->element)
|
||
|
||
#define getThis() ((Z_TYPE(EX(This)) == IS_OBJECT) ? &EX(This) : NULL)
|
||
|
||
```
|
||
|
||
### 类参数
|
||
|
||
其实和函数的参数一样,还有一个类似的`zend_parse_method_parameters`我用的时候总是错误,还没明白这个函数是干什么的,而且找不到说明的资料=\_=,后面附上两个源码的区别再看看
|
||
|
||
```c
|
||
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** 也就是我们后面的第四个参数,第五个是我们类的指针
|
||
|
||
```c
|
||
object = va_arg(va, zval **);
|
||
ce = va_arg(va, zend_class_entry *);
|
||
*object = this_ptr;
|
||
```
|
||
|
||
看后面这一段,好像是校验类的,所以我觉得这个`zend_parse_method_parameters`和`zend_parse_parameters`的区别就在这里,method 能够对类进行校验
|
||
|
||
```c
|
||
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;
|
||
}
|
||
```
|
||
|
||
```c
|
||
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** 又传回来了- -
|
||
|
||
```c
|
||
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)
|
||
}
|
||
```
|