---
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)
}
```
