Press "Enter" to skip to content

深入理解PHP原理之变量生命期(一)

对于PHP的中的数据来源, 不外乎有俩种:

1. 来自代码中
2. 来自外部(GET/POST/DB)

对于代码中的变量(也就是直接量)来说, 变量分配/赋值在编译期, 活跃在执行器, 在请求关闭期被销毁.对于这些变量来说, 使用APC进行Opcode缓存, 则会缓存这部分变量的值.
而对于来自外部的变量, 变量分配/赋值在编译器后, 执行期前, 在请求关闭期被销毁,对于这些变量来说, 使用APC进行OpCode缓存, 是不会被缓存的.
今天就着重关注下外部变量的一个部分,GET来的数据的整个生命周期.
假设, 有如下请求到来:

	GET /index.php?name=laruence&career[]=yahoo&career[]=baidu

而, 在index.php中:

<?php
	$name 	= $_GET['name'];
	$career = $_GET['career']; //array

我们知道, 在最后的执行期, $_GET数组必然包含如下片段:

	$_GET = array(
		'name'   => 'laruence',
		'career' => array(
			'yahoo', 'baidu',
		),
	)

那么, 我们今天就重点关注下, Query String是如何构建成_GET数组的(关于GET变量的生成, 请一并阅读我之前的文章: "PHP的GET/POST等大变量生成过程"):
在请求到来时刻,php_request_startup(定义在main.c)被调用,来做初始化现场. 在这个过程中包括设置超时值,调用各个模块的请求初始化函数. 当然也包括我们关心的, 创建变量环境.
php_hash_environment根据php.ini中的variables_order来依次初始化各个预定义大变量, 那么对于$_GET来说:

...
case 'g':
case 'G':
	if (!_gpc_flags[2]) {
		sapi_module.treat_data(PARSE_GET, NULL, NULL TSRMLS_CC);
		_gpc_flags[2] = 1;
		if (PG(register_globals)) {
			php_autoglobal_merge(&EG(symbol_table),
				Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]) TSRMLS_CC);
		}
	}
break;
...

大体可以看出,这段逻辑,首先通过treat_data来生成变量hash(PG(http_globals)[TRACK_VARS_GET]), 如果打开了auto_register_globals,则再把$_GET数组中的变量加入到符号表中.
treat_data是属于sapi_module_struct中的一个成员:

 注意:	本文基于apache2handler方式的sapi, 这个启动过程和之前的文章sapi
原理中的启动过程略有不同, php5通过注册apache2的ap_hook_post_config挂钩,
在apache server启动的时候启动php(php_apache_server_startup,定义在
sapi/apache2hander/sapi_apache2.c中), 在这个函数中调用sapi_startup启动sapi,
继而通过调用php_apache2_startup来注册sapi module struct,
然后调用php_module_startup来初始化PHP, 其中又会初始化ZEND引擎,
以及填充zend_module_struct中的treat_data成员(通过php_startup_sapi_content_types)
为php_default_treat_data

现在回过头来继续看treat_data(也就是php_default_treat_data):

....
if (arg == PARSE_GET) {     /* GET data */
	c_var = SG(request_info).query_string;
	if (c_var && *c_var) {
		res = (char *) estrdup(c_var);
		free_buffer = 1;
	} else {
		free_buffer = 0;
	}
} else if (arg == PARSE_COOKIE) {       /* Cookie data */
....

在上面的逻辑中, 给res复制为query_string, SG(request_info)是一个代表了当前请求信息的结构体, 其中query_string是在php_apache_request_ctor中通过复制apache的reqeust_rec结构体中的args而来的.
对于本文的例子来说, 此时res即为"name=laruence&career[]=yahoo&career[]=baidu",
继续在treat_data中, 随后的逻辑是:

var = php_strtok_r(res, separator, &strtok_buf);
...
while (var) {
	val = strchr(var, '=');
	if (arg == PARSE_COOKIE) {
		/* Remove leading spaces from cookie names,
			needed for multi-cookie header where ; can be followed by a space */
		while (isspace(*var)) {
			var++;
		}
		if (var == val || *var == '\0') {
			goto next_cookie;
		}
	}
	if (val) { /* have a value */
		int val_len;
		unsigned int new_val_len;
		*val++ = '\0';
		php_url_decode(var, strlen(var));
		val_len = php_url_decode(val, strlen(val));
		val = estrndup(val, val_len);
		if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len TSRMLS_CC)) {
			php_register_variable_safe(var, val, new_val_len, array_ptr TSRMLS_CC);
		}
		efree(val);
	} else {
...

首先, 通过php_strtok_r把res根据"&"分割成一个一个的"key=value"段, 接下来分别为var和val复制为key和value, 注意到这个过程中会分别对var和val做php_url_decode.
最后通过php_register_variable_safe, 给array_ptr(此时指向PG(http_globals)[TRACK_VARS_GET], 也就是$_GET)添加一个名为var值为val的成员.
到了这一步, 我们的$_GET数组中, 就包含了如下的成员:

'name'   => 'laruence',
'career' => array(
	'yahoo', 'baidu',
),

未完待续(变量的销毁过程)...

20 Comments

  1. wyf
    wyf March 23, 2016

    SG、PG 这些预编译的宏在哪里可以看到?

  2. Kathryn
    Kathryn January 16, 2015

    Very good post! We will be linking to this great article on our website.
    Keep up the good writing.
    web site (Kathryn)

  3. bbqq
    bbqq May 10, 2014

    鸟哥这里说的:
    “而对于来自外部的变量, 变量分配/赋值在编译器后, 执行期前, 在请求关闭期被销毁”
    GET\POST等外部变量实在环境初始化的时候就已经分配了吧,环境初始化这个阶段是在编译之前吧,所以很质疑你说这些变量是在编译之前进行分配赋值。。。

  4. […] NO.1 PaD:变量未初始化 && php.ini register_global = On 这种情况需要依赖PHP语言本身的特性,即在PHP早期的版本中支持变量不需初始化直接调用,并且PHP执行起始会将Client提交的数据自动注册到执行环境的符号表[解释]中。 […]

  5. 库从志
    库从志 June 22, 2010

    博主 写得很深入

  6. Anonymous
    Anonymous March 25, 2010

    怎么没有了,写完吧,大虾

  7. eve
    eve December 30, 2009

    楼主,求交往

  8. 雪候鸟
    雪候鸟 December 28, 2009

    @phpcoder 呵呵,就是简单的字符串parse了, 遇到”[]”, 就知道是个数组..

  9. phpcoder
    phpcoder December 28, 2009

    强悍的博主, 还有点不明白就是数组是怎么parse来的呢?

  10. 天缘
    天缘 December 28, 2009

    还没真的没深究过这些源码,佩服博主认真的研究

  11. 雪候鸟
    雪候鸟 December 26, 2009

    @robin 这样的话,最终career的值为baidu

  12. Robin
    Robin December 26, 2009

    Hi,麻烦问问,如果想去掉 “[]”,改成
    name=laruence&career=yahoo&career=baidu
    $_GET数组还要不变,怎么改?
    谢谢了。

Leave a Reply

Your email address will not be published. Required fields are marked *