返回

PHP扩展中如何检测ArrayIterator类型?

php

检测PHP扩展中的ArrayIterator

PHP 扩展开发中,常常需要处理各种由 PHP 脚本传递给扩展函数的参数与返回值。其中,ArrayIterator 是一种常用的数据类型,它提供了一种迭代访问数组的方式。当从 PHP 脚本接收到一个 zval 类型的值,并且需要判断它是否为 ArrayIterator 时,可能面临一些挑战,本文将探讨如何有效地检测 ArrayIterator 类型。

问题分析

zval 是 Zend 引擎中用来表示所有 PHP 变量的结构体。它包含一个类型字段,通常可以通过 Z_TYPE_P 宏来访问。一个常见的误解是,所有对象都会被标记为 IS_OBJECT 类型,然而这并非绝对。实际上,PHP 内部对于像 ArrayIterator 这样的类,它使用了特殊的机制,并不会将其 zval 类型标记为 IS_OBJECT。这就是为什么当试图使用 Z_TYPE_P 获取一个 ArrayIterator 类型的变量时,你会发现得到的是 IS_UNDEF(即 0)而不是 IS_OBJECT 的原因。 简单通过检查 zval 的类型标志不足以确定它是否为 ArrayIterator。 需要采取其他方式才能完成类型判断。

解决方案

1. 使用 instanceof 进行类型判断

instanceof 用于检查对象是否是特定类的实例,也可以用来检查是否为接口的实例。对于 ArrayIterator 类型的 zval,可以使用 PHP 提供的 instanceof 机制在 C++ 层面实现相同的类型检查逻辑。

实现步骤:

  1. 确保你拥有指向目标 zval 类型的指针 (zval *zv)。
  2. 利用 Zend 提供的宏 instanceof_function ( ZEND_INSTANCEOF_CLASS_CONST_EX 在更现代的 PHP 版本中),调用 instanceof 判断目标 zval 指针所指向的对象是否为指定类型,在我们的例子中,应该传入ArrayIterator的类名("ArrayIterator")。
  3. instanceof_function 或者 ZEND_INSTANCEOF_CLASS_CONST_EX 返回一个 zend_bool 的值,如果对象是该类的实例返回 1, 否则返回 0

代码示例:

#include "php.h"
#include "Zend/zend_interfaces.h"

zend_bool is_array_iterator(zval *zv) {
    zend_class_entry *array_iterator_ce;
    // 从 zend 的 class table 中寻找 "ArrayIterator" class 的类实体
    array_iterator_ce = zend_hash_str_find_ptr(EG(class_table), "arrayiterator", sizeof("arrayiterator") -1);

    // 检测是否存在 ArrayIterator 类
    if (!array_iterator_ce){
        return 0; // ArrayIterator 类不存在,返回 0。
    }

    //  进行 instance of 判断,返回一个 zend_bool 值表示 是否是  ArrayIterator 类型的实例。
     return  ZEND_INSTANCEOF_CLASS_CONST_EX(Z_OBJ_P(zv), array_iterator_ce,0);
}


//  一个模拟函数,用于演示类型检测。实际情况下你会在 php 扩展中使用。
void check_iterator_type(zval *val){

    if (is_array_iterator(val)){
           php_printf("It's a ArrayIterator\n");
     } else {
            php_printf("It's not a ArrayIterator\n");
      }
}



// Example using in an Extension. You should write similar codes in a php_module struct's relevant entry
PHP_FUNCTION(test_check_iterator_type){

   zval *test_val;

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

  check_iterator_type(test_val);
  RETURN_NULL();
}

使用步骤:

  1. 将以上代码添加到你的 PHP 扩展中。
  2. 使用 PHP 的扩展加载机制编译并加载该扩展。
  3. 在 PHP 脚本中,可以调用 test_check_iterator_type 函数,并传入需要测试的变量。
    例如: test_check_iterator_type(new ArrayIterator(['a','b','c']))
    输出: It's a ArrayIterator
    test_check_iterator_type([1,2,3]);
    输出: It's not a ArrayIterator

2. 基于类型名称比较的方式

除了使用 instanceof, 还可以通过对比对象的 class name 的方式检测 ArrayIterator. 在PHP中,可以使用函数zend_get_class_name获取zval所表示对象的 class name,然后和字符串"ArrayIterator" 进行对比。

实现步骤:

  1. 使用宏 Z_OBJ_Pzval *zv 中取出对象的对象结构体指针 zend_object *obj
  2. 通过zend_get_class_name 函数来获取对象所对应的 class 的类名。需要一个 zend_string 接收结果。
  3. 调用 zend_string_equals 和 字符串 "ArrayIterator" 比对类型名称是否一致。

代码示例:

#include "php.h"
#include "Zend/zend_API.h"


zend_bool is_array_iterator_by_name(zval *zv) {
    if(Z_TYPE_P(zv) != IS_OBJECT){
         return 0;
    }

    zend_object *obj =  Z_OBJ_P(zv);
     zend_string *class_name = NULL;

    //获取对象的类名。
     if (obj){
            class_name = zend_get_class_name(obj);

            //检测类名是否是 arrayIterator. 如果相同,返回true,否则 false
            if (class_name){
               bool ret = zend_string_equals(class_name, "ArrayIterator");
                zend_string_release(class_name);
               return ret;
             }

    }
     return 0;
}



//  一个模拟函数,用于演示类型检测。实际情况下你会在 php 扩展中使用。
void check_iterator_type_by_name(zval *val){

    if (is_array_iterator_by_name(val)){
        php_printf("It's a ArrayIterator\n");
     } else {
          php_printf("It's not a ArrayIterator\n");
      }
}

PHP_FUNCTION(test_check_iterator_type_by_name){

    zval *test_val;
    ZEND_PARSE_PARAMETERS_START(1,1)
         Z_PARAM_ZVAL(test_val)
    ZEND_PARSE_PARAMETERS_END();

  check_iterator_type_by_name(test_val);
    RETURN_NULL();

}

使用步骤:

  1. 和方法一类似,将以上代码加入扩展中编译并加载
  2. 在 PHP 脚本中调用 test_check_iterator_type_by_name 方法来进行验证类型, 使用示例和之前一样

其他安全建议:

  • 务必确保正确处理内存。在C++ 代码中注意 zend_string_release 释放资源。
  • zval 可能是一个用户提供的输入时,增加一些额外校验避免类型错误。
  • 仔细检查php的版本号以及php内核变化,比如从 php7 到 php8 一些内部结构的修改。

总结

检测PHP 扩展中 ArrayIterator 类型不能简单的通过 Z_TYPE_P 的方式来处理, 我们提供了两种实现方式: 通过 instanceof_function 或者 ZEND_INSTANCEOF_CLASS_CONST_EX 函数和 直接对比class name,都是可以高效,安全的方式, 可以根据自己使用环境选择一种方式。选择时考虑不同方法的使用场景。比如instanceof能更好的处理继承情况, 也比较符合通常的开发直觉,对比 classname的方式 则可以更直接进行名称的比对, 代码更加紧凑。