风雪之隅
msgbartop
PHP源码分析,Zend引擎分析,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作什么用的。
    宏的定义在哪,一直找不到

    [回复]

    雪候鸟 Reply:

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

    [回复]

  2. |

    呵呵,找到了,在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

    [回复]



Leave a Comment