PHP实现强类型函数返回值

澳门新葡亰网址 1

支持四种强制类型限制:int、array、bool、object,当返回值与函数声明中的类型不匹配时,抛出warning,本来想抛出error,但是觉得

  以上的分析是针对函数定义时的参数设置,这些参数是固定的。而在实际编写程序时可能我们会用到可变参数。此时我们会用到函数func_num_args和func_get_args。它们是以内部函数存在。于是在Zendzend_builtin_functions.c文件中找到这两个函数的实现。我们首先来看func_num_args函数的实现,其代码如下:

这还不够,还需要修改函数声明定义的处理逻辑

  执行代码会报错Fatal error: Cannot redeclare t() (previously declared
in …)

比如下面的代码:

 

先修改语法扫描 Zend/zend_language_scanner.l文件

  在Zend/zend_language_parser.y文件中找到函数的声明过程标记如下:

在开发过程中,函数的返回值类型应该是确定不变的,但PHP是弱类型的语言,

  为验证这个实现,我们看一段代码

澳门新葡亰网址 1

  PHP中函数都有返回值,没return返回null

详情,这里就不讲了,

  取参数的个数是通过ZEND_NUM_ARGS()宏来实现的,其定义如下:

<ST_IN_SCRIPTING>”int” {
return T_FUNCTION_RETURN_INT;
}
<ST_IN_SCRIPTING>”bool” {
return T_FUNCTION_RETURN_OBJECT;
}
<ST_IN_SCRIPTING>”object” {
return T_FUNCTION_RETURN_OBJECT;
澳门新葡亰网址,}
<ST_IN_SCRIPTING>”resource” {
return T_FUNCTION_RETURN_RESOURCE;
}

/* {{{ proto int func_num_args(void)
   Get the number of arguments that were passed to the function */
ZEND_FUNCTION(func_num_args)
{
    zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;

    if (ex && ex->function_state.arguments) {
        RETURN_LONG((long)(zend_uintptr_t)*(ex->function_state.arguments));
    } else {
        zend_error(E_WARNING,
"func_num_args():  Called from the global scope - no function context");
        RETURN_LONG(-1);
    }
}
/* }}} */

意思很简单,扫描器扫描到到关键字
int、bool、object、resource、array时返回相应的T_FUNCTION_*
,这是一个token,

  函数的返回值在程序执行时存储在*EG(return_value_ptr_ptr)。ZEND内核对值返回和引用返回作了区别,并且在此基础上对常量,临时变量和其他类型的变量在返回时作了不同的处理。在return执行完之后,ZEND内核通过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。这也是ZEND内核自动给函数加上NULL返回的原因之一。

Zend/zend_compile.c ::zend_do_begin_function_declaration

……
zend_op_array op_array;
char *name = function_name->u.constant.value.str.val;
int name_len = function_name->u.constant.value.str.len;
int function_type  = function_token->u.EA.var;
//保存函数类型,在语法解释器中增加的: $$.u.EA.var = IS_LONG;
int function_begin_line = function_token->u.opline_num;
……
op_array.function_name = name;
op_array.fn_type = function_type; //将类型保存到op_array中,
op_array.return_reference = return_reference;
op_array.fn_flags |= fn_flags;
op_array.pass_rest_by_reference = 0;
……….

  表示对于PHP来说T和t是同一个函数名,校验函数名是否重复,这个过程是在哪进行的呢?

太狠了,只能算是个异常,不能算错误,所以就用warning好了。

  • 第一个参数num_args表明表示想要接收的参数个数,我们经常使用ZEND_NUM_ARGS()来表示对传入的参数“有多少要多少”
  • 第二个参数应该是宏TSRMLS_CC。
  • 第三个参数type_spec是一个字符串,用来指定我们所期待接收的各个参数的类型,有点类似于printf中指定输出格式的那个格式化字符串。
  • 剩下的参数就是我们用来接收PHP参数值的变量的指针。

因此我就想,既然不能规范,那直接强制好了。

  对于参数的个数,中间代码中包含的arg_nums字段在每次执行**zend_do_receive_argxx时都会加1.如下代码:

if((EG(active_op_array)->fn_type > 0) &&
Z_TYPE_P(retval_ptr) != EG(active_op_array)->fn_type){
php_error_docref0(NULL TSRMLS_DC,E_WARNING, “function name %s
return a wrong type.”, EG(active_op_array)->function_name );
}

  参数的值传递和参数传递的区别是通过pass_by_reference参数在生成中间代码时实现的。

$$.u.EA.var 存储的是 函数返回类型,最后要拿他来跟返回值类型做匹配,

  整个参数的传递是通过给中间代码的arg_info字段执行赋值操作完成。关键点是在arg_info字段,arg_info字段的结构如下:

最后要修改opcode的毁掉函数,函数的返回 return 会生成token
T_RETURN,T_RETURN会根据返回的类型调用不同的calback函数:

  3、函数的返回值

在callback函数return之前增加如下代码

 

所以要在这三个callback函数中增加处理逻辑:

  1、用户自定义函数的参数

fn_type 去跟 返回值的类型作比较,如果没有匹配到,就会抛出这个warning。

 

scanner根据不同的token做不同的处理,token要先在Zend/zend_language_parser.y文件中定义

  PHP内部函数在解析参数时使用的是zend_parse_parameters。它可以大大简化参数的接收处理工作,虽然它在处理可变参数时还有点弱。

<?php
function getArticles(…){
$arrData = array();
if($exp1){
return $arrData;
}else if($exp2){
return 1;
}else{
return false;
}

}
$arrData =getArticles(…);
foreach($arrData as $record){
//do something.
….
}
?>

<ST_IN_SCRIPTING>"function" {
    return T_FUNCTION;
}

它有三个callback,如果返回值是一个 const类型的数据,则
ZEND_RETURN_SPEC_CONST_HANDLER
返回值是临时数据,如 : return 1,则ZEND_RETURN_SPEC_TMP_HANDLER
返回值是一个变量,如 : return $a,则ZEND_RETURN_SPEC_VAR_HANDLER

#define ZEND_NUM_ARGS()     (ht)

下载补丁:php-syntax.patch

void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) /* {{{ */
{
    zend_op *opline;
    int start_op_number, end_op_number;
 if (do_end_vparse) {
        if (CG(active_op_array)->return_reference
                && !zend_is_function_or_method_call(expr)) {
            zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC);/* 处理返回引用 */
        } else {
            zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC);/* 处理常规变量返回 */
        }
    }

   ...// 省略,取其他中间代码操作

    opline->opcode = ZEND_RETURN;

    if (expr) {
        opline->op1 = *expr;

        if (do_end_vparse && zend_is_function_or_method_call(expr)) {
            opline->extended_value = ZEND_RETURNS_FUNCTION;
        }
    } else {
        opline->op1.op_type = IS_CONST;
        INIT_ZVAL(opline->op1.u.constant);
    }

    SET_UNUSED(opline->op2);
}
/* }}} */

我已经打了补丁,目前只支持php5.3版本,有需要的可以拿去玩一玩。

function:
    T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }
;

is_reference:
        /* empty */ { $$.op_type = ZEND_RETURN_VAL; }
    |   '&'         { $$.op_type = ZEND_RETURN_REF; }
;

unticked_function_declaration_statement:
        function is_reference T_STRING {
zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }
            '(' parameter_list ')' '{' inner_statement_list '}' {
                zend_do_end_function_declaration(&$1 TSRMLS_CC); }
;

这样语法解释器就可以处理我们新的php语法了。

cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];

所以PHP是没有此类语法验证的,正因为如此,造成了很多坑坑。

  语法解析后,我们看到所执行编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其实现如下:

PHP本身是不支持 int function
这样的语法的,所以要支持,就先要搞定语法解析器,关于语法解析器,可以移步这里>>>查看

  zend_parse_parameters()在解析参数的同时户尽可能的转换参数类型,这样就可以确保我们总是能得到所期望的类型的变量

ZEND_RETURN_SPEC_CONST_HANDLER
ZEND_RETURN_SPEC_TMP_HANDLER
ZEND_RETURN_SPEC_VAR_HANDLER

  并且当前参数的索引为ŒCG(active_op_array)->num_args-1.如下代码:

(关于opcode你可以想象一下
从c转为汇编,我博客中也有相关文章,可以参考一下)

  (2)解析参数列表

所以要做处理就要把函数的类型保存到opcode中:op_array.fn_type =
function_type;

  以常见的count函数为例,其参数处理部分的代码如下:

PHP是先解析PHP语法生成相应的opcode,将需要的环境、参数信息保存到execute_data全局变量中,最后在通过execute函数逐条执行opcode,

  函数的定义只是一个将函数名注册到函数列表的过程。

增加如下代码:

function T() {
    echo 1;
}

function t() {
    echo 2;
}

函数getArticles根据不同的条件返回不同类型的值,有bool、int、还有数组,正常情况这类函数是希望返回数组,然后拿数组去做一些其他操作,

  2、内部函数的参数

可因为函数返回值类型不固定,调用时就很可能产生各种预想不到的坑,

do_bind_function(EX(opline), EG(function_table), 0);

函数/方法返回值可以强制类型,如 图

  这里包括了两个操作:一个是取参数的个数,一个是解析参数列表。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图