2024-03-22 17:42:41 +08:00

12 KiB
Raw Blame History

title
title
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_FE PHP_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
};

类属性

在初始化注册类的时候,使用 zenddeclare_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)
}