Press "Enter" to skip to content

PHP Internals — The Lifetime of a Variable (Part 1)

As for where the data in PHP comes from, there are only two kinds:

1. From the code
2. From outside (GET/POST/DB)

For variables in the code (that is, literals), allocation/assignment happens at compile time, they are live during execution, and they are destroyed at request shutdown. For these variables, if you use APC for Opcode caching, the values of this part of the variables are cached.
For variables that come from outside, allocation/assignment happens after the compiler and before execution, and they are destroyed at request shutdown. For these variables, if you use APC for OpCode caching, they are not cached.
Today let's focus on one part of the external variables: the whole lifecycle of data coming from GET.
Suppose the following request arrives:

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

And, in index.php:

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

As we know, at the very end, during execution, the $_GET array must contain the following fragment:

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

So today let's focus on how the Query String is built into the $_GET array (for how GET variables are generated, please also read my earlier article: "The Generation Process of PHP's Large Variables such as GET/POST"):
When a request arrives, php_request_startup (defined in main.c) is called to set up the scene. This process includes setting timeout values and calling each module's request initialization function. And of course, it also includes the thing we care about: creating the variable environment.
php_hash_environment initializes each predefined large variable in turn according to variables_order in php.ini. So for $_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;
...

Roughly speaking, this logic first uses treat_data to generate the variable hash (PG(http_globals)[TRACK_VARS_GET]), and if auto_register_globals is enabled, it then adds the variables in the $_GET array into the symbol table.
treat_data is a member of sapi_module_struct:

 Note:	This article is based on the apache2handler SAPI, and this startup
process differs slightly from the startup process in my earlier article on
SAPI. php5 registers apache2's ap_hook_post_config hook and starts php when
the apache server starts (php_apache_server_startup, defined in
sapi/apache2hander/sapi_apache2.c); in that function it calls sapi_startup
to start the SAPI, then calls php_apache2_startup to register the sapi
module struct, then calls php_module_startup to initialize PHP, which also
initializes the ZEND engine and fills in the treat_data member of
zend_module_struct (via php_startup_sapi_content_types) with
php_default_treat_data

Now let's go back and continue looking at treat_data (that is, 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 */
....

In the logic above, res is assigned query_string. SG(request_info) is a struct representing the information of the current request, and its query_string is obtained in php_apache_request_ctor by copying args from apache's request_rec struct.
For the example in this article, res is now "name=laruence&career[]=yahoo&career[]=baidu".
Continuing in treat_data, the following logic is:

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 == '') {
			goto next_cookie;
		}
	}
	if (val) { /* have a value */
		int val_len;
		unsigned int new_val_len;
		*val++ = '';
		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 {
...

First, php_strtok_r splits res by "&" into individual "key=value" segments. Then var and val are assigned the key and the value respectively. Note that during this process php_url_decode is applied to var and val separately.
Finally, through php_register_variable_safe, a member named var with the value val is added to array_ptr (which at this point points to PG(http_globals)[TRACK_VARS_GET], that is, $_GET).
At this point, our $_GET array contains the following members:

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

To be continued (the process of destroying variables)...

Be First to Comment

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.