PHP扩展开发——类

这篇开始在扩展中实现PHP的类,先定个目标,实现如下的PHP类:

<?php

class Person {
    public const MALE = 1;
    public const FEMALE = 2;

    private $name;
    private $age;

    public function __construct(string $name, string $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

相关结构体、宏及函数

  • zend_class_entry: 类入口
  • PHP_METHOD: 声明类的方法
  • zend_update_property: 更新类的属性
  • zend_read_property: 获取类的属性
  • PHP_ME: 类方法的参数信息
  • INIT_CLASS_ENTRY: 初始化zend_class_entry
  • zend_register_internal_class: 向zend中注册类
  • zend_declare_property: 声明属性

实现Person类

1. 声明类入口、实现类方法

// 类入口
zend_class_entry *person_ce;


// __construct(string name, int age)
PHP_METHOD(Person, __construct)
{
    zend_string *name;
    zend_long age;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_STR(name)
        Z_PARAM_LONG(age)
    ZEND_PARSE_PARAMETERS_END();

    // 更新属性
    zend_update_property_str(person_ce, getThis(), "name", sizeof("name")-1, name);
    zend_update_property_long(person_ce, getThis(), "age", sizeof("age")-1, age);

    zend_string_release(name);
}

// getName(): string
PHP_METHOD(Person, getName)
{
    zval rv, *name;

    name = zend_read_property(person_ce, getThis(), "name", sizeof("name")-1, 0, &rv);

    RETURN_STR(Z_STR_P(name))
}

// setName(string name)
PHP_METHOD(Person, setName)
{
    zend_string *name;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_STR(name)
    ZEND_PARSE_PARAMETERS_END();

    zend_update_property_str(person_ce, getThis(), "name", sizeof("name")-1, name);

    zend_string_release(name);
}

2. 方法参数信息

ZEND_BEGIN_ARG_INFO(arginfo_person_construct, 0)
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
    ZEND_ARG_TYPE_INFO(0, age, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_person_setname, 0)
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
ZEND_END_ARG_INFO()

3. 方法入口

与普通函数一样,都是使用zend_function_entry结构体作为入口,不同的只是把宏换成了PHP_ME

zend_function_entry person_methods[] = {
    // ZEND_ACC_PUBLIC, 方法访问权限都为public
    PHP_ME(Person, __construct, arginfo_person_construct, ZEND_ACC_PUBLIC)
    PHP_ME(Person, getName, NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Person, setName, arginfo_person_setname, ZEND_ACC_PUBLIC)

    PHP_FE_END
};

4. 向Zend注册类

最后在扩展初始化的Hook中,将类注册到Zend中。

PHP_MINIT_FUNCTION(ext_test)
{
    // 初始化zend_class_entry,将方法绑定到类中
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Person", person_methods)

    // 注册类
    person_ce = zend_register_internal_class(&ce);

    // 声明类常量
    zend_declare_class_constant_long(person_ce, "MALE", sizeof("MALE")-1, 1);
    zend_declare_class_constant_long(person_ce, "FEMALE", sizeof("FEMALE")-1, 2);

    // 声明类的属性及访问权限
    zend_declare_property_null(person_ce, "name", sizeof("name")-1, ZEND_ACC_PRIVATE);
    zend_declare_property_null(person_ce, "age", sizeof("age")-1, ZEND_ACC_PRIVATE);
}

至此,类的实现基本完成,大致步骤为:

  1. 声明类入口
  2. 实现相关的方法
  3. 声明方法参数信息
  4. 声明方法入口
  5. 在扩展初始化的Hook中注册到Zend中

测试PHP代码

<?php

$person = new Person('anhoder', 18);
var_dump($person->getName());
$person->setName('alan');
var_dump($person);

输出代码:

string(7) "anhoder"
object(Person)#1 (2) {
  ["name":"Person":private]=>
  string(4) "alan"
  ["age":"Person":private]=>
  int(18)
}