【问题标题】:How to make ZEND_BEGIN_ARG_INFO_EX control number of arguments, passed to a PHP extension?如何使 ZEND_BEGIN_ARG_INFO_EX 控制参数数量,传递给 PHP 扩展?
【发布时间】:2013-01-16 02:31:14
【问题描述】:

我正在使用 C 开发一个 PHP 扩展。到目前为止,我正在对从 PHP 用户空间传递给扩展函数的参数进行正确验证。

ZEND_BEGIN_ARG_INFO_EX 可用于为 Zend Engine 提供有关函数参数的信息。宏的第 4 个参数,命名为required_num_args,让引擎自动控制参数的数量,让我免去这个麻烦。但是,我找不到让它工作的方法:引擎总是在没有任何警告的情况下运行扩展的函数,即使 PHP 脚本没有传递足够的参数。

这是我对函数参数的定义:

ZEND_BEGIN_ARG_INFO_EX(test_func_swt_arginfo, 0, 0, 3)
    ZEND_ARG_INFO(1, firstArg)
    ZEND_ARG_ARRAY_INFO(0, secondArg, true)
    ZEND_ARG_OBJ_INFO(1, thirdArg, SomeClass, false)
ZEND_END_ARG_INFO()

这是我对函数的定义,由 PHP 扩展导出:

static const zend_function_entry test_func_functions[] = {
    PHP_FE(sample_with_types, test_func_swt_arginfo)
    PHP_FE_END
};

这是我的功能:

PHP_FUNCTION(sample_with_types)
{
    RETURN_TRUE;
}

这是我运行的 PHP 脚本:

<?php
sample_with_types();

预期结果:PHP 显示错误/警告/异常,类似于 “没有足够的参数传递给函数”;该函数不执行。

实际结果:函数执行并返回true

如何正确配置函数参数结构,以便 Zend Engine 自动检查参数数量?还是我弄错了ZEND_BEGIN_ARG_INFO_EX 宏中required_num_args 参数的用途?

【问题讨论】:

    标签: php c php-extension php-internals


    【解决方案1】:

    据我所知,这不是ZEND_BEGIN_ARG_INFO_EX 的用途。

    ZEND_BEGIN_ARG_INFO_EX 是 PHP 5 的新增功能,用于生成更简洁的代码,支持类型提示、引用传递和反射。考虑以下 arginfo 声明,用于只返回 true 的实际函数:

    ZEND_BEGIN_ARG_INFO_EX(arginfo_test, 0, 0, 3)
        ZEND_ARG_INFO(0, firstArg)
        ZEND_ARG_OBJ_INFO(0, objNonNull, stdClass, 0)
        ZEND_ARG_OBJ_INFO(0, obj, stdClass, 1)
        ZEND_ARG_OBJ_INFO(1, objByRef, stdClass, 1)
    ZEND_END_ARG_INFO()
    

    有如下效果:

    sample_with_types();                          // ok
    sample_with_types(1, null);                   // error: arg #2 should be stdClass
    sample_with_types(1, new stdClass, null);     // ok
    sample_with_types(1, new stdClass, 1);        // error: arg #3 should be stdClass
    sample_with_types(1, new stdClass, null, 2);  // error: arg #4 must be reference
    

    此外,它还为您的函数提供反射功能:

    $ref = new ReflectionFunction('sample_with_types');
    var_dump($ref->getParameters());
    

    ...给出类似于以下内容的输出:

    array(4) {
      [0]=>
      &object(ReflectionParameter)#2 (1) {
        ["name"]=>
        string(8) "firstArg"
      }
      [1]=>
      &object(ReflectionParameter)#3 (1) {
        ["name"]=>
        string(10) "objNonNull"
      }
      [2]=>
      &object(ReflectionParameter)#4 (1) {
        ["name"]=>
        string(3) "obj"
      }
      [3]=>
      &object(ReflectionParameter)#5 (1) {
        ["name"]=>
        string(8) "objByRef"
      }
    }
    

    如果省略 arginfo,ReflectionFunction::getParameters() 将返回一个空数组。

    required_num_args宏参数专门用于反射,表示反射函数时需要多少个参数。

    如果你需要在使用反射时将参数设置为必填,而不仅仅是将它们标记为必填,你仍然必须使用zend_parse_parameters,在大多数情况下,你仍然需要获取参数的实际值:

    PHP_FUNCTION(sample_with_types)
    {
        long arg1;
        zval *arg2 = NULL, *arg3 = NULL, *arg4 = NULL;
        zend_class_entry ce2, ce3, ce4;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "looo", &arg1, 
                                  &arg2, &ce2, &arg3, &ce3, &arg4, &ce4) == FAILURE)
        {
            return;
        }
    
        RETURN_TRUE;
    }
    

    注意我在上面是如何使用"looo"(通用对象类型)而不是"lOO!O!"(带有空说明符的特定对象类型)的。类型提示已经用 arginfo 指定了,不需要重复两次。

    所以,没有 arginfo:

    • 您必须使用少量 zend_fetch_class 调用和类条目来键入提示您的对象参数。
    • 它不会启用反射。
    • 您将无法声明通过引用传递的参数。
    • 它显然会产生不太干净的代码。

    出于显而易见的原因,您需要确保您的 arginfo 声明和您的 zend_parse_parameters 调用匹配。

    【讨论】:

    • 感谢@netcoder 为阐明该宏的目的提供的信息和努力。这很有道理,实际上我在问这个问题之前就发现了这一点。然而,引起我困惑并让我去 SO 的主要事情是 required_num_args 参数。这似乎是 args num 的自动验证,但实际上它什么也没做。有没有可能,你知道它的目的?
    • @AndreyTserkus:required_num_args 用于反射,表示何时根据需要停止计算参数(即:调用isOptional() 时)。除此之外没有任何作用。 (我也用这些信息更新了答案。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-09
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2014-11-14
    • 1970-01-01
    相关资源
    最近更新 更多