おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

func_get_args系関数がPHP5.3より関数呼び出しの引数としても使えるように

関数に渡された引数の数および引数を受け取るための関数として、func_num_args()func_get_arg()func_get_args()が用意されています。可変引数関数の実装なんかに使います。
これらの関数は、関数呼び出しの引数として、

<?php
function hoge()
{
  $content = join('', func_get_args());
}
hoge();
?>

とか呼び出しちゃうと、

PHP Fatal error:  func_get_args(): Can't be used as a function parameter

と警告でちゃうというイケてない仕様になっているという話を昔書いたんですが、

これが、マニュアルにも記述のあるとおり、PHP5.3から修正され、関数の引数として使えるようになりました。
まぁ、関数呼び出しになっていると判別できてちゃんとエラーだすくらいなら、debug_backtrace()なんかは対応しているわけだしきちんと実装しろやという感じだったのですが。。
func_get_args()を例に、どのような修正がされたかを貼り付けておきます。
#実はずいぶん前にメモしてたので、バージョンが古い罠。変わってないと思いますが。

それぞれのコード

PHP5.2.6
ZEND_FUNCTION(func_get_args)
{
    void **p;
    int arg_count;
    int i;

    p = EG(argument_stack).top_element-1-1;
    arg_count = (int)(zend_uintptr_t) *p;       /* this is the amount of arguments passed to func_get_args(); */
    p -= 1+arg_count;
    if (*p) {
        zend_error(E_ERROR, "func_get_args(): Can't be used as a function parameter");
    }
    --p;

    if (p<EG(argument_stack).elements) {
        zend_error(E_WARNING, "func_get_args():  Called from the global scope - no function context");
        RETURN_FALSE;
    }
    arg_count = (int)(zend_uintptr_t) *p;


    array_init(return_value);
    for (i=0; i<arg_count; i++) {
        zval *element;

        ALLOC_ZVAL(element);
        *element = **((zval **) (p-(arg_count-i)));
        zval_copy_ctor(element);
        INIT_PZVAL(element);
        zend_hash_next_index_insert(return_value->value.ht, &element, sizeof(zval *), NULL);
    }
}
PHP5.3
ZEND_FUNCTION(func_get_args)
{
    void **p;
    int arg_count;
    int i;
    zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;

    if (!ex || !ex->function_state.arguments) {
        zend_error(E_WARNING, "func_get_args():  Called from the global scope - no function context");
        RETURN_FALSE;
    }

    p = ex->function_state.arguments;
    arg_count = (int)(zend_uintptr_t) *p;       /* this is the amount of arguments passed to func_get_args(); */

    array_init_size(return_value, arg_count);
    for (i=0; i<arg_count; i++) {
        zval *element;

        ALLOC_ZVAL(element);
        *element = **((zval **) (p-(arg_count-i)));
        zval_copy_ctor(element);
        INIT_PZVAL(element);
        zend_hash_next_index_insert(return_value->value.ht, &element, sizeof(zval *), NULL);
    }
}

5.2までのコードは、EG(argument_stack)から引数を取っていましたが、5.3からは、

EG(current_execute_data)->prev_execute_data->function_state.arguments

あたりから引数を取ってくるように変わっています。これ自体、5.3から利用できるようになったものです。
気が向いたら詳しく書きます。