[ PHP 内核与扩展开发系列] 类与面向对象:如何定义一个类


zend_class_entry

zend_class_entry 是内核中定义的一个结构体,是内核实现 PHP 语言中类与对象的一个非常基础、关键的结构类型,相当于我们定义类的原型。如果我们想获得一个名字为 myclass 的类该怎么做呢?首先我们定义一个 zend_class_entry 变量,并为它设置名字,最后注册到 runtime 中去:
这样我们便定义了一个类 myclass,而且我们可以在 PHP 语言中使用它,比如:
我们上面还定义了一个 myclass_ce 指针,它是干什么用的呢?当我们在扩展中对这个类进行操作,比如生成实例的时候,会使用到它,它的作用就类似于打开文件的操作句柄。

定义类对应 zend_class_entry

在这一节中,我们正式的定义一个类。首先我给出 PHP 语言的实现:
定义类的第一步,便是先定义好这个类的 zend_class_entry,这一步操作是在 MINIT 阶段完成的。
这就是最简单的一个类,没有属性没有方法,但是可以使用了:
输出如下:
注:记得将源码 zend_module_entry 中的 `/* MINIT */` 部分替换成 `ZEND_MINIT(test)`,否则无法找到 myclass
某个类的 zend_class_entry 会经常用到,所以我们一般会把它保存在一个变量里,供扩展中其它地方的程序使用,所以上述的代码组合一般是这样的:

为类定义属性

我们可以用 zend_declare_property* 系列函数来完成这项操作,为某个类定义属性一般会需要三个信息:
  • 属性的名称
  • 属性的默认值
  • 属性的访问权限
我们为上面的 myclass 类定义一个名为 public_var 的属性,默认值为 null,访问权限为public
ZEND_ACC_PUBLICZEND_ACC 系列掩码中的一个,代表着 public,其余的还有ZEND_ACC_PRIVATEZEND_ACC_PROTECTED 等,详细描述请见后面的章节。这三个掩码比较简单,就不再叙述了。

为类定义方法

为类定义方法比较繁琐一些,首先我们先回顾一下 zend_function_entry 结构,在以前我们用它来保存我们扩展的函数,通过它把 PHP 语言中的函数和我们用 C 语言编写的函数联系起来,在这它也发挥了这么一个桥梁的作用。下面我们来实现 myclass 类的 public 方法和构造方法。 首先,定义这个函数的 C 语言部分,不过这一次我们使用的是 ZEND_METHOD
然后,用 PHP_METHOD 声明 public_method__construct
最后,在 MINIT 阶段 register internal class 的时候将它作为一个参数传递进去:
现在我们在 PHP 脚本中调用一下这个方法,看看输出结果:
输出结果如下:
这里在定义 __construct 方法的时候,使用到了 ZEND_ACC_CTOR,它的作用便是声明这个方法是此类的构造函数,而 ZEND_ACC_PUBLIC|ZEND_ACC_CTOR 是我们常见的掩码或运算,代表它是一个public 类型的构造函数。如果我们去掉 ZEND_ACC_CTOR 标志,那么此构造函数还会起作用吗?在这里的例子中它仍然起作用,但是在别的环境下我就不敢保证了。 protectedprivate 类型的属性与方法的定义和 public 的一样。而定义 static 的属性与方法只是在掩码标志中加入 ZEND_ACC_STATIC 即可。下面详细地罗列出了所有掩码,fn_flags 代表可以在定义方法时使用,zend_property_info.flags 代表可以在定义属性时使用,ce_flags 代表在定义 zend_class_entry 时候可用。
ZEND_ACC_CTORZEND_ACC_DTOR 是比较特殊的掩码标志,分别代表着构造函数与析构函数,不要将这两个标志位用在其它方法上面。其它的一些魔术方法,如 __get__call 等大都需要 arginfo,有关它们的内容将在后续章节中描述。

为类定义常量

这个比较简单,只涉及到一组函数,可以查看 Zend/zend_API.h

完整测试示例

下面是来演示一个比较完整的测试示例:
重新编译扩展,编写测试代码如下:
该示例代码运行结果如下:

<< 上一篇: [ PHP 内核与扩展开发系列] PHP 中的资源类型:持久资源

>> 下一篇: [ PHP 内核与扩展开发系列] 类与面向对象:接口实现与类的继承