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

386 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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