标签 PHP 下的文章

require_once导致的一次Bug

代码如下:

public function test($table) {
    require_once ROOT_PATH . '/config/db.config.php';
    $result = false;

    if (!$DB_CONFIG[$table]) {
        return $result;
    }

    // 代码省略
}

上线后发现test方法没执行,加断点调试后发现走到这里的if判断里直接return掉了。
断点调试,发现$DB_CONFIG为NULL,这是什么灵异的问题。

检查代码后,发现是由于require_once导致的。
这里如果在函数外其他地方加载了db.config.php文件,这里就不再加载这个文件,导致$DB_CONFIG这个变量为空。
改为require后恢复正常。

总结

对于配置文件,不要使用require_once进行加载。
另外也不推荐使用require_once进行文件加载,具体见《再一次, 不要使用(include/require)_once》

PHP的GET/POST参数里带有.号的问题

最近看其他项目的框架代码,发现在处理xxx.n这类参数时,使用的是$_GET['xxx_n']。
我的理解,这里不是应该是用$_GET['xxx.n']么?
为什么会变成\_了呢?

现象

带有.的参数解析

原因分析

转载自鸟哥的《PHP的GET/POST等大变量生成过程》

首先明确一个问题,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;
                     ....以下省略:
   }}}

可以看到,离成功不远了,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的时候,会将(.)转换成(_).

结论

PHP会把http请求里的点号(.)和空格( )都转换为下划线(_).

PHP里的try/catch和set_exception_handler的执行顺序

在PHP里,try/catch和set_exception_handler都是用来捕获异常的,但是如果同时定义了这两个的话,谁会先执行呢?

如下代码:

set_exception_handler('myException');

function test($a) {
	if ($a > 1) {
		throw new Exception('the param is illegal !', 123);
	}

	echo $a;
}

try {
	test(2);
} catch (Exception $e) {
	echo 'I am try/catch.';
}

function myException($e) {
	echo 'I am set_exception_handler.';
}

输出结果:

I am try/catch.

结论:

set_exception_handler是用来处理所有未被捕获的异常。