msgbartop
PHP源码分析,Zend引擎分析,Web相关技术研究,Web技术分享–左手代码 右手诗
msgbarbottom

07 Nov 08 PHP的GET/POST等大变量生成过程

主要探讨了PHP的大变量的生成过程。另外如果你注意到, 当在表单中提交的input的name中如果有点号的时候, 在PHP中会自动把点号处理成下划线。并且你很想知道这是为什么,在什么时候发生的? 呵呵,本文也就这个问题做了回答。

首先明确一个问题,PHP的变量名中是不能包含点号的。 但是为了处理表单中的点号命名,PHP就会自动把点号(.)转换成下划线(_)。
要知道PHP是怎么处理的,首先我们要了解,$_GET, $_POST, $_COOKIE等变量的构造过程。
在每个请求到来以后,apache处理到response阶段的时候, 会将控制权交给PHP模块, PHP模块会在处理请求之前首先间接调用 php_request_startup (具体调用序列是send_php -> apache_php_module_main -> php_request_startup, 关于这部门可以参看我前面的文章( PHP Life Cycle) , 在php_request_startup中:

   int php_request_startup(TSRMLS_D)
{
    int retval = SUCCESS;

#if PHP_SIGCHILD
    signal(SIGCHLD, sigchld_handler);
#endif

    if (php_start_sapi() == FAILURE) {
        return FAILURE;
    }

    php_output_activate(TSRMLS_C);
    sapi_activate(TSRMLS_C);
    php_hash_environment(TSRMLS_C);

    zend_try {
        PG(during_request_startup) = 1;
        php_output_activate(TSRMLS_C);
        if (PG(expose_php)) {
            sapi_add_header(SAPI_PHP_VERSION_HEADER, sizeof(SAPI_PHP_VERSION_HEADER)-1, 1);
        }
    } zend_catch {
        retval = FAILURE;
    } zend_end_try();

    return retval;
}
   

注意其中的php_hash_environment(TSRMLS_C) 函数调用 , 这个函数就是在请求处理前, 初始化请求相关的变量的函数。
这个函数定义在: main/php_variables.c中 , 有兴趣的可以看看:

  int php_hash_environment(TSRMLS_D)
{
        char *p;
        unsigned char _gpc_flags[5] = {0, 0, 0, 0, 0};
        zend_bool jit_initialization = (PG(auto_globals_jit) && !PG(register_globals) && !PG(register_long_arrays));
        struct auto_global_record {
                char *name;
                uint name_len;
                char *long_name;
                uint long_name_len;
                zend_bool jit_initialization;
        } auto_global_records[] = {
                { "_POST", sizeof("_POST"), "HTTP_POST_VARS", sizeof("HTTP_POST_VARS"), 0 },
                { "_GET", sizeof("_GET"), "HTTP_GET_VARS", sizeof("HTTP_GET_VARS"), 0 },
                { "_COOKIE", sizeof("_COOKIE"), "HTTP_COOKIE_VARS", sizeof("HTTP_COOKIE_VARS"), 0 },
                { "_SERVER", sizeof("_SERVER"), "HTTP_SERVER_VARS", sizeof("HTTP_SERVER_VARS"), 1 },
                { "_ENV", sizeof("_ENV"), "HTTP_ENV_VARS", sizeof("HTTP_ENV_VARS"), 1 },
                { "_FILES", sizeof("_FILES"), "HTTP_POST_FILES", sizeof("HTTP_POST_FILES"), 0 },
        };
        size_t num_track_vars = sizeof(auto_global_records)/sizeof(struct auto_global_record);
        size_t i;

        /* jit_initialization = 0; */
        for (i=0; i<num_track_vars; i++) {
                PG(http_globals)[i] = NULL;
        }

        for (p=PG(variables_order); p && *p; p++) {
                switch(*p) {
                        case 'p':
                        case 'P':
                                if (!_gpc_flags[0] && !SG(headers_sent) && SG(request_info).request_method && !strcasecmp(SG(request_info).request_method, "POST")) {
                                        sapi_module.treat_data(PARSE_POST, NULL, NULL TSRMLS_CC);       /* POST Data */
                                        _gpc_flags[0] = 1;
                                        if (PG(register_globals)) {
                                                php_autoglobal_merge(&EG(symbol_table), Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_POST]) TSRMLS_CC);
                                        }
                                }
                                break;
                   ....以下省略:
 }}}
 

到了这里说个题外话, 就是在php.ini中, 可以使用variables_order来控制PHP是否生成某个大变量,已经大变量的生成顺序。
关于顺序,就是说, 如果打开了auto_register_globals的情况下, 如果先处理p,后处理g,那么$_GET['a'],就会覆盖$_POST['a'];

可以看到,离成功不远了,sapi_module.treat_data 也就是php_default_treat_data,
在php_default_treat_data中,对于变量,都调用php_register_variable_safe来注册变量, 而php_register_variable_safe最终会调用php_register_variable_ex:

  PHPAPI void php_register_variable_ex(char *var, zval *val, zval *track_vars_array TSRMLS_DC)
{
    char *p = NULL;
    char *ip;       /* index pointer */
    char *index, *escaped_index = NULL;
    int var_len, index_len;
    zval *gpc_element, **gpc_element_p;
    zend_bool is_array = 0;
    HashTable *symtable1 = NULL;

    assert(var != NULL);

    if (track_vars_array) {
        symtable1 = Z_ARRVAL_P(track_vars_array);
    } else if (PG(register_globals)) {
        symtable1 = EG(active_symbol_table);
    }
    if (!symtable1) {
        /* Nothing to do */
        zval_dtor(val);
        return;
    }

    /*
     * Prepare variable name
     */

    /* ignore leading spaces in the variable name */
    while (*var && *var==' ') {
        var++;
    }

    /* ensure that we don't have spaces or dots in the variable name (not binary safe) */
    //特别注意以下这段。。。。
    for (p = var; *p; p++) {
        if (*p == ' ' || *p == '.') {
            *p='_';
        } else if (*p == '[') {
            is_array = 1;
            ip = p;
            *p = 0;
            break;
        }
  ....以下省略

呵呵,问题的原因找到了, 就是在php_register_variable的时候,会将(.)转换成(_).
最后,再介绍下$_REQUEST变量的生成, 其实很简单, 在php_hash_environment中的最后, 会调用 php_auto_globals_create_request("_REQUEST", sizeof("_REQUEST")-1 TSRMLS_CC)来注册_REQUEST大变量, 在php_auto_globals_create_request("_REQUEST", sizeof("_REQUEST")-1 TSRMLS_CC)中,只是简单的将$_GET, $_POST, $_COOKIE merge起来(G(http_globals)[TRACK_VARS_COOKIE]这部分,可以参看我较早前的) :

static zend_bool php_auto_globals_create_request(char *name, uint name_len TSRMLS_DC)
{
    zval *form_variables;
    unsigned char _gpc_flags[3] = {0, 0, 0};
    char *p;

    ALLOC_ZVAL(form_variables);
    array_init(form_variables);
    INIT_PZVAL(form_variables);

    for (p = PG(variables_order); p && *p; p++) {
        switch (*p) {
            case 'g':
            case 'G':
                if (!_gpc_flags[0]) {
                    php_autoglobal_merge(Z_ARRVAL_P(form_variables), Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]) TSRMLS_CC);
                    _gpc_flags[0] = 1;
                }
                break;                                                                                                                    case 'p':
            case 'P':
                if (!_gpc_flags[1]) {
                    php_autoglobal_merge(Z_ARRVAL_P(form_variables), Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_POST]) TSRMLS_CC);
                    _gpc_flags[1] = 1;
                }
                break;
            case 'c':
            case 'C':                                                                                                                         if (!_gpc_flags[2]) {
                    php_autoglobal_merge(Z_ARRVAL_P(form_variables), Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_COOKIE]) TSRMLS_CC);
                    _gpc_flags[2] = 1;
                }
                break;
        }
    }

    zend_hash_update(&EG(symbol_table), "_REQUEST", sizeof("_REQUEST"), &form_variables, sizeof(zval *), NULL);
    return 0;
}

Related Posts:

Tags: , , ,

Reader's Comments

  1. |

    想问个问题,PG作什么用的。
    宏的定义在哪,一直找不到

  2. |

    PG是为了兼容线程安全模式的一种对php_globals的访问包装, 定义在 main/php_globals.h(php 5.x)

  3. |

    呵呵,找到了,在php_globals.h里面定义的。
    #ifdef ZTS
    # define PG(v) TSRMG(core_globals_id, php_core_globals *, v)
    extern PHPAPI int core_globals_id;
    #else
    # define PG(v) (core_globals.v)
    extern ZEND_API struct _php_core_globals core_globals;
    #endif

  4. |

    你简直就是我偶像

  5. |

    贵站做的很好

  6. |

    [...] 那么, 我们今天就重点关注下, Query String是如何构建成_GET数组的(关于GET变量的生成, 可以参看我之前的文章: “PHP的GET/POST等大变量生成过程“): [...]

Leave a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word