在我们的示例扩展中使用 OOP

让我们尝试扩展我们的示例扩展,用 C 语言实现以下 PHP 类。

<?php

class Scaler {
    const DEFAULT_FACTOR = 2;
    private $factor = DEFAULT_FACTOR;
    function __construct($factor = self:: DEFAULT_FACTOR) {
        $this->factor = factor;
    }

    function scale(&$x) {
        test_scale($x, $this->factor);
    }
}

方法的 C 代码版本的编写方法与 PHP 内部函数类似。只需使用带有类和方法名称的 PHP_METHOD() 宏,而不是 PHP_FUNCTION。在方法中,$this 变量(zval)可作为 ZEND_THIS 宏使用。在方法 __construct() 中,我们使用 zend_update_property_long() 函数为属性 $factor 赋值,并在方法 scale() 中使用 zend_read_property() 读取它。

static zend_class_entry *scaler_class_entry = NULL;

#define DEFAULT_SCALE_FACTOR 2

PHP_METHOD(Scaler, __construct)
{
    zend_long factor = DEFAULT_FACTOR; // default value

    ZEND_PARSE_PARAMETERS_START(0, 1)
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(factor)
    ZEND_PARSE_PARAMETERS_END();

    if (ZEND_NUM_ARGS() > 0) {
        zend_update_property_long(Z_OBJCE_P(ZEND_THIS), ZEND_THIS,
            "factor", sizeof("factor")-1, factor);
    }
}

PHP_METHOD(Scaler, scale)
{
    zval *x, *zv, tmp;
    zend_long factor;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ZVAL(x)
    ZEND_PARSE_PARAMETERS_END();

    zv = zend_read_property(Z_OBJCE_P(ZEND_THIS), ZEND_THIS,
        "factor", sizeof("factor")-1, 0, &tmp);
    factor = zval_get_long(zv);

    do_scale_ref(x, factor);
}

参数信息描述符的创建和使用方法与普通函数完全相同。然后,必须将所有类方法的信息收集到一个数组中。这类似于扩展函数列表,但使用的是 PHP_ME() 宏而不是 ZEND_FE()。PHP_ME() 需要两个附加参数。第一个是类名,第四个是方法标志(例如 ZEND_ACC_PUBLIC、ZEND_ACC_PROTECTED、ZEND_ACC_PRIVATE、ZEND_ACC_STATIC)。

ZEND_BEGIN_ARG_INFO(arginfo_scaler_construct, 0)
    ZEND_ARG_INFO(0, factor)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_scaler_scale, 0)
    ZEND_ARG_INFO(1, x) // pass by reference
ZEND_END_ARG_INFO()

static const zend_function_entry scaler_functions[] = {
    PHP_ME(Scaler, __construct, arginfo_scaler_construct, ZEND_ACC_PUBLIC)
    PHP_ME(Scaler, scale, arginfo_scaler_scale, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

最后,我们必须在 MINIT 中注册类及其实体。

INIT_CLASS_ENTRY() 会初始化临时类入口结构,设置类名,并添加给定的类方法。 zend_ register_class_entry() 会在全局类表中注册类,并返回生成的类入口。然后为类添加常量和属性。

PHP_MINIT_FUNCTION(test)
{
    zend_class_entry ce;

    REGISTER_INI_ENTRIES();

    INIT_CLASS_ENTRY(ce, "Scaler", scaler_functions);
    scaler_class_entry = zend_register_internal_class(&ce);

    zend_declare_class_constant_long(scaler_class_entry,
        "DEFAULT_FACTOR", sizeof("DEFAULT_FACTOR")-1, DEFAULT_SCALE_FACTOR);

    zend_declare_property_long(scaler_class_entry,
        "factor", sizeof("factor")-1, DEFAULT_SCALE_FACTOR, ZEND_ACC_PRIVATE);
    return SUCCESS;
}

这运行:

$ php -r '$o = new Scaler(5); $x = 5; $o->scale($x); var_dump($x, $o);'
int(25)
object(Scaler)#1 (1) {
    ["factor":"Scaler":private]=>
    int(5)
}

实现的类没有太大意义(教育意义除外),因为它并不比用 PHP 编写的原始类更好用。在实践中,如果用 C 语言重新实现的东西效率更高、占用内存更少,或者无法用 PHP 编写,那么用 C 语言重新实现的东西就有意义了。例如,我们可以在 PHP 对象中嵌入一些 C 语言数据。