提问人:goffreder 提问时间:8/29/2012 最后编辑:goffreder 更新时间:8/29/2012 访问量:2343
变量和超全局变量
Variable variables and superglobals
问:
我正在将一个巨大的PHP软件从PHP4过渡到PHP5,在我面临的许多(许多)问题中,到目前为止最大的问题似乎是以前的程序员刚刚为register_globals功能而大吃一惊,时不时地抛出一些变量而没有指定来源,并无耻地将警告和通知隐藏在地毯下。
我试图通过创建一个函数来解决这个问题,该函数遍历一个数组(作为参数传递)并通过“变量变量”功能创建全局变量,然后在每个页面中调用它,和 。这是代码:$_POST
$_GET
$_SESSION
function fix_global_array($array) {
foreach($array as $key => $value){
if(!isset($$key)) {
global $$key;
$$key = $value;
}
}
}
此函数的问题在于条件永远不会为真,因此括号内的代码始终被执行并覆盖前面的声明。isset($$key)
这种行为有什么解释吗?我阅读了 PHP 文档,其中指出
请注意,变量变量不能在函数或类方法中与 PHP 的超全局数组一起使用。
但我不明白它是否与我的问题有关(说实话,我也不明白这是什么意思,我找不到任何例子)。
PS:请不要费心告诉我使用全局变量和/或变量变量是糟糕的编程,我自己太清楚了,但另一种选择是修改大约 2.700 个文件,每行每行有一千行代码,我是这里唯一的程序员......但是,如果你知道一个更好的解决方案来摆脱所有这些“未定义的变量”警告,你就可以让我开心了。
PPS:对我的英语也^_^要有耐心
答:
在给定的代码中,永远不会为真的原因是因为你在条件检查之后调用;该变量在向 global
注册之前不在范围内。要解决此问题,只需将该行移动到 上方,您的函数将如下所示:isset($$key)
global $$key
if-statement
function fix_global_array($array) {
foreach($array as $key => $value){
global $$key;
if(!isset($$key)) {
$$key = $value;
}
}
}
这在传递数组时可以正常工作,即使所述数组是 or 。不过,您在数组中传递的顺序很重要。如果索引/键在 和 中定义,并且您首先传递给函数 - 则 from 的值不会存储到变量中。$_POST
$_GET
$_POST
$_GET
$_POST
$_GET
或者,如果你想避免使用变量变量,无论是出于可读性问题还是简单的偏好,你都可以用同样的方式使用超全局$GLOBALS
:
function fix_global_array($array) {
foreach($array as $key => $value){
if(!isset($GLOBALS[$key])) {
$GLOBALS[$key] = $value;
}
}
}
使用此方法,变量仍然可以访问,就像它们被正常定义一样。例如:
$data = array('first' => 'one', 'second' => 'two');
fix_global_array($data);
echo $first; // outputs: one
echo $second; // outputs: two
此示例适用于上述两个代码示例。
另外,您可以使用 PHP 的 extract()
函数。它的目的是完全按照你的方法去做 - 甚至有一个标志来覆盖现有的变量值。用法示例:fix_global_array()
extract($data);
echo $first; // outputs: one
PHP网站给出了一个直接适用于这种情况的警告:extract()
不要对不受信任的数据使用 extract(),例如用户输入(即 $_GET, _FILES 美元等)。如果这样做,例如,如果要运行旧代码 暂时依赖register_globals,请确保使用 非覆盖extract_type值,例如 EXTR_SKIP 和 be aware 您应该按照 php.ini 中variables_order中定义的相同顺序提取。
评论
globals $$key
isset
extract
$GLOBALS
但是,如果你知道一个更好的解决方案来摆脱所有这些“未定义的变量”警告,你就可以让我开心了。
有。修复了未使用超全局变量的问题。现在,自然而然地,我并不是说你应该自己手动更改每个翻转变量调用,但我想这是你可以自动化的,也许。看看你能不能跟着我的脑电波走。
首先,您必须获取所有“未定义变量”通知的列表。这就像注册错误处理程序一样简单,检查E_NOTICE调用并检查它是否是未定义的变量调用。我冒昧地写了一小段代码来做到这一点。
<?php
/**
* GlobalsLog is a class which can be used to set an error handler which will
* check for undefined variables and checks whether they exist in superglobals.
*
* @author Berry Langerak
*/
class GlobalsLog {
/**
* Contains an array of all undefined variables which *are* present in one of the superglobals.
*
* @var array
*/
protected $globals;
/**
* This contains the order in which to test for presence in the superglobals.
*
* @var array
*/
protected $order = array( 'SERVER', 'COOKIE', 'POST', 'GET', 'ENV' );
/**
* This is where the undefined variables should be stored in, so we can replace them later.
*
* @var string
*/
protected $logfile;
/**
* Construct the logger. All undefined variables which are present in one of the superglobals will be stored in $logfile.
*
* @param string $logfile
*/
public function __construct( $logfile ) {
$this->logfile = $logfile;
set_error_handler( array( $this, 'errorHandler' ), E_NOTICE );
}
/**
* The error handler.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return boolean
*/
public function errorHandler( $errno, $errstr, $errfile, $errline ) {
$matches = array( );
if( preg_match( '~^Undefined variable: (.+)$~', $errstr, $matches ) !== 0 ) {
foreach( $this->order as $superglobal ) {
if( $this->hasSuperglobal( $superglobal, $matches[1] ) ) {
$this->globals[$errfile][] = array( $matches[1], $superglobal, $errline );
return true;
}
}
}
}
/**
* Called upon destruction of the object, and writes the undefined variables to the logfile.
*/
public function __destruct( ) {
$globals = array_merge( $this->globals, $this->existing( ) );
file_put_contents(
$this->logfile,
sprintf( "<?php\nreturn %s;\n", var_export( $globals, true ) )
);
}
/**
* Gets the undefined variables that were previously discovered, if any.
*
* @return array
*/
protected function existing( ) {
if( file_exists( $this->logfile ) ) {
$globals = require $this->logfile;
return $globals;
}
return array( );
}
/**
* Checks to see if the variable $index exists in the superglobal $superglobal.
*
* @param string $superglobal
* @param string $index
* @return bool
*/
protected function hasSuperglobal( $superglobal, $index ) {
return array_key_exists( $index, $this->getSuperglobal( $superglobal ) );
}
/**
* Returns the value of the superglobal. This has to be done on each undefined variable, because
* the session superglobal maybe created *after* GlobalsLogger has been created.
*
* @param string $superglobal
* @return array
*/
protected function getSuperglobal( $superglobal ) {
$globals = array(
'SERVER' => $_SERVER,
'COOKIE' => $_COOKIE,
'POST' => $_POST,
'GET' => $_GET,
'ENV' => $_ENV
);
return isset( $globals[$superglobal] ) ? $globals[$superglobal] : array( );
}
}
/**
* Lastly, instantiate the object, and store all undefined variables that exist
* in one of the superglobals in a file called "undefined.php", in the same
* directory as this file.
*/
$globalslog = new GlobalsLog( __DIR__ . '/undefined.php' );
如果您要在请求的每个页面中包含此文件(可选使用 ),则在单击整个应用程序后,您最终将在“undefined.php”中获得所有未定义的变量。php_prepend_file
这是一个相当有趣的信息,因为您现在知道哪个未定义的变量位于哪个文件中,在哪一行上,以及它实际存在于哪个超全局变量中。在确定超全局变量时,我牢记了 Environment、Get、Post、Cookie 和 Server 的顺序,以决定哪个优先。
对于我们巧妙的小技巧的下一部分,我们必须遍历所有找到通知的文件,并尝试将未定义的变量替换为其超全局对应变量。这实际上也很简单,同样,我创建了一个脚本来做到这一点:undefined variable
#!/usr/bin/php
<?php
/**
* A simple script to replace non globals with their globals counterpart.
*/
$script = array_shift( $argv );
$logfile = array_shift( $argv );
$backup = array_shift( $argv ) === '--backup';
if( $logfile === false || !is_file( $logfile ) || !is_readable( $logfile ) ) {
print "Usage: php $script <logfile> [--backup].\n";
exit;
}
$globals = require $logfile;
if( !is_array( $globals ) || count( $globals ) === 0 ) {
print "No superglobals missing found, nothing to do here.\n";
exit;
}
$replaced = 0;
/**
* So we have the files where superglobals are missing, but shouldn't be.
* Loop through the files.
*/
foreach( $globals as $filename => $variables ) {
if( !is_file( $filename ) || !is_writable( $filename ) ) {
print "Can't write to file $filename.\n";
exit;
}
foreach( $variables as $variable ) {
$lines[$variable[2]] = $variable;
}
/**
* We can write to the file. Read it in, line by line,
* and see if there's anything to do on that line.
*/
$fp = fopen( $filename, 'rw+' );
$i = 0;
$buffer = '';
while( $line = fgets( $fp, 1000 ) ) {
++$i;
if( array_key_exists( $i, $lines ) ) {
$search = sprintf( '$%s', $lines[$i][0] );
$replace = sprintf( "\$_%s['%s']", $lines[$i][1], $lines[$i][0] );
$line = str_replace( $search, $replace, $line );
$replaced ++;
}
$buffer .= $line;
}
if( $backup ) {
$backupfile = $filename . '.bck';
file_put_contents( $backupfile, file_get_contents( $filename ) );
}
file_put_contents( $filename, $buffer );
}
echo "Executed $replaced replacements.\n";
unlink( $logfile );
现在,只需调用此脚本即可。我已经测试过了,这是我测试过的文件:
<?php
require 'logger.php';
$_GET['foo'] = 'This is a value';
$_POST['foo'] = 'This is a value';
$_GET['bar'] = 'test';
function foo( ) {
echo $foo;
}
foo( );
echo $bar;
有两个未定义的变量 ( 和 ),它们都存在于一个(或多个)超全局变量中。在我的浏览器中访问该页面后,我的日志文件中有两个条目;即 foo 和 bar。然后,我运行了命令,该命令给了我以下输出:$foo
$bar
undefined.php
php globalsfix.php undefined.php --backup
berry@berry-pc:/www/public/globalfix% php globalsfix.php undefined.php --backup
Executed 2 replacements.
好奇结果是什么?好吧,我也是。在这里:
<?php
require 'logger.php';
$_GET['foo'] = 'This is a value';
$_POST['foo'] = 'This is a value';
$_GET['bar'] = 'test';
function foo( ) {
echo $_POST['foo'];
}
foo( );
echo $_GET['bar'];
欢呼!不再有未定义的变量,这些变量是从正确的超全局变量中读取的。大胖免责声明:先创建备份。此外,这不会立即解决您的所有问题。如果你有一个语句,undefined 变量将确保相应的块永远不会被执行,这意味着很可能不是所有未定义的变量都会被一次性捕获(但它会在第二次或第三次使用此脚本时解决这个问题)。不过;这是开始“清理”代码库的好地方。if( $foo )
另外,恭喜您阅读了我的完整答案。:)
评论
global $$key
isset
extract
extract()
isset