PHP深切理解PHP内核(十)变量和数据类型-类型提示的落实

原文链接:http://www.orlion.ga/253/

PHP是弱类型语言,向方传递参数时也并无严格检查数据类型。不过有时光要判定传递及点子中之参数,为夫PHP中提供了片函数来判定数的项目,比如is_numeric()判断是否是一个数值或可转移为数值的字符串,比如用来判断目标的类运算符:instance
of。instance of用来测定一个加的靶子是不是来指定的对象类。instance
of运算符是PHP5引进的,在此之前是以is_a()。

    为了避免对象类型不标准引起的问题
,PHP5中引入了档次提示这概念,在定义方法参数时,同时定义参数的数据类型,如果以调用的时节,传入参数的色和定义的参数类型不符,则会报错。这样就算可过滤对象的类。

    示例:

function array_print(Array $arr) {
    print_r($arr);
}
 
array_print(1);

    上边的代码会报错,因为函数的参数指定为Array但是传入的是一个整型数据。当我们传入一个数组时则会健康运作,这是怎落实的也罢?
不管是于近似中之计还是我们调用的函数,都是行使function关键字作为那个声称的记号,而项目提示的兑现是于同函数的声明相关的,在宣称时即便都确定了参数的种类是啊几,但是用以调用时才会展示出,这里我们打零星独点证实项目提示的落实:

  1. 参数声明时的门类提示

  2. 函数或措施调用时的品种提示

 

    将刚的事例改一下:

function array_print(Array $arr = 1) {
    print_r($arr);
}
 
array_print(array(1));

    这个事例中函数的默认参数是一个平头,会报错。为什么会看出报错呢?因为默认值的检测过程有在中间代码生成等,与运行时的报错不同,它还并未变中间代码,也未尝实行中间代码的历程。在Zend/zend_language_parse.y文件被,我们找到函数的参数列表在编译时还见面调用zend_do_receive_arg函数。而在斯函数的参数列表中,第5独参数(znode
*class_type)与本文所假设抒发的类提示密切相关。这个参数的意是声称类型提示中之档次,这里的档次有三种植:

    

    (1)空,即无种提示

    (2)类型,用户定义或PHP自定义之类、接口等

    (3)数组,编译期间对应的token是T_ARRAY,即Array字符

 

    在zend_do_receive_arg函数中,针对class_type参数做了一如既往多元的操作,基本上是针对性地方列有底老三栽档次,其中对于类名,程序并无看清这个仿佛是否是,即使采用了一个免设有的类名,程序在报错时显示的呢是实参所为的目标并无是受定类的实例。

    

    

    以上是宣称类型提示的过程和以宣称过程遭到针对参数默认值的判断过程,下面我们看下在函数或艺术调用时路提示的落实。

 

    从上面的扬言过程我们领略PHP在编译类型提示的代码时调用的是Zend/zend_complie.c文件被的zend_do_receive_arg函数,在是函数中将类型提示的判断的opcode被赋值为ZEND_RECV。根据opcode的照射计算规则得出其在推行时调用的是ZEND_RECV_SPEC_HANDLER.其代码如下:

static int ZEND_FASTCALL  ZEND_RECV_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
       ...// 省略
        if (param == NULL) {
                char *space;
                char *class_name = get_active_class_name(&space TSRMLS_CC);
                zend_execute_data *ptr = EX(prev_execute_data);
 
                if (zend_verify_arg_type((zend_function *) EG(active_op_array), 
arg_num, NULL, opline->extended_value TSRMLS_CC)) {
        ...//˯省略
                }
               ...//˯省略
        } else {
              ...//˯省略
                zend_verify_arg_type((zend_function *) EG(active_op_array), 
arg_num, *param, opline->extended_value TSRMLS_CC);
              ...//˯省略
        }
      ...//˯省略
}

   
如齐所示,在ZEND_RECV_SPEC_HANDLER中最后调用的凡zend_verify_arg_type。其代码如下:

static inline int zend_verify_arg_type(zend_function *zf, zend_uint arg_num, 
zval *arg, ulong fetch_type TSRMLS_DC)
{
   ...//˯省略
 
    if (cur_arg_info->class_name) {
        const char *class_name;
 
        if (!arg) {
            need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, 
&class_name, &ce TSRMLS_CC);
            return zend_verify_arg_error(zf, arg_num, cur_arg_info, need_msg, 
class_name, "none", "" TSRMLS_CC);
        }
        if (Z_TYPE_P(arg) == IS_OBJECT) { // 既然是类对象参数,传递的参数需要时对象类型
            // 下面检查这个对象是否是参数提示类的实例对象,这里是允许传递子类实例对象
            need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, 
&class_name, &ce TSRMLS_CC);
            if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce TSRMLS_CC)) {
                return zend_verify_arg_error(zf, arg_num, cur_arg_info, 
need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name TSRMLS_CC);
            }
        } else if (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null) { // 参数为NULL,也是可以通过检查的 如果函数定义了参数默认值,不传递参数调用也是可以通过检查的

            need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, 
&class_name, &ce TSRMLS_CC);
            return zend_verify_arg_error(zf, arg_num, cur_arg_info, need_msg, 
class_name, zend_zval_type_name(arg), "" TSRMLS_CC);
        }
    } else if (cur_arg_info->array_type_hint) { // 数组
        if (!arg) {
            return zend_verify_arg_error(zf, arg_num, cur_arg_info, "be an 
array", "", "none", "" TSRMLS_CC);
        }
         }
        if (Z_TYPE_P(arg) != IS_ARRAY && (Z_TYPE_P(arg) != IS_NULL || 
!cur_arg_info->allow_null)) {
            return zend_verify_arg_error(zf, arg_num, cur_arg_info, "be an 
array", "", zend_zval_type_name(arg), "" TSRMLS_CC);
        }
    }
    return 1;
}

 zend_verify_arg_type的一体流程如图所示

PHP 1

 

    如果类型提示报错,zend_verify_arg_type函数最后还见面调用zend_verify_arg_class_kind生成报错信息,并且调用zend_verify_arg_error报错。如下所示代码:

static inline char * zend_verify_arg_class_kind(const zend_arg_info 
*cur_arg_info, ulong fetch_type, const char **class_name, zend_class_entry 
**pce TSRMLS_DC)
{
    *pce = zend_fetch_class(cur_arg_info->class_name, cur_arg_info-
>class_name_len, (fetch_type | ZEND_FETCH_CLASS_AUTO | 
ZEND_FETCH_CLASS_NO_AUTOLOAD) TSRMLS_CC);
 
    *class_name = (*pce) ? (*pce)->name: cur_arg_info->class_name;
    if (*pce && (*pce)->ce_flags & ZEND_ACC_INTERFACE) {
        return "implement interface ";
    } else {
        return "be an instance of ";
    }
}
 
 
static inline int zend_verify_arg_error(const zend_function *zf, zend_uint 
arg_num, const zend_arg_info *cur_arg_info, const char *need_msg, const char 
*need_kind, const char *given_msg, char *given_kind TSRMLS_DC)
{
    zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data;
    char *fname = zf->common.function_name;
    char *fsep;
     char *fclass;
 
    if (zf->common.scope) {
        fsep =  "::";
        fclass = zf->common.scope->name;
    } else {
        fsep =  "";
        fclass = "";
    }
 
    if (ptr && ptr->op_array) {
        zend_error(E_RECOVERABLE_ERROR, "Argument %d passed to %s%s%s() must 
%s%s, %s%s given, called in %s on line %d and defined", arg_num, fclass, fsep, 
fname, need_msg, need_kind, given_msg, given_kind, ptr->op_array->filename, 
ptr->opline->lineno);
    } else {
        zend_error(E_RECOVERABLE_ERROR, "Argument %d passed to %s%s%s() must 
%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, 
given_msg, given_kind);
    }
    return 0;
}

    在地方的代码中,我们可以看出前方的报错信息遭的一些要字Argument、passed
to、called
in等。这虽是我们在调用函数或方法时路提示显示错误信息的末梢实施职务。

相关文章