2

我正在将一个巨大的 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:对我的英语也有耐心^_^

4

2 回答 2

2

在您给定的代码中,永远不正确的原因isset($$key)是因为您global $$key在条件检查之后调用;在使用 .注册之前,该变量不在范围内global。要解决此问题,只需将该行移至 上方if-statement,您的函数将如下所示:

function fix_global_array($array) {
    foreach($array as $key => $value){
        global $$key;
        if(!isset($$key)) {
            $$key = $value;
        }
    }
}

当传递一个数组时,这将正常工作,即使所述数组是$_POSTor $_GET。不过,您传入数组的顺序很重要。$_POST如果在和 中定义了索引/键$_GET,并且您首先传递$_POST给函数 - 值 from$_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

一个关于 的警告extract(),直接适用于这种情况,来自 PHP 网站:

不要对不受信任的数据使用 extract(),例如用户输入(即 $_GET、$_FILES 等)。如果这样做,例如,如果您想临时运行依赖于register_globals的旧代码,请确保使用不可覆盖的 extract_type 值之一,例如 EXTR_SKIP 并注意您应该按照 variables_order中定义的相同顺序进行提取php.ini。

于 2012-08-29T12:33:11.460 回答
1

但是,如果您知道一个更好的解决方案来摆脱所有那些“未定义变量”警告,那么您可以度过我的一天。

有。修复未使用超全局变量的问题。现在,自然地,我并不是说您应该自己手动更改每个翻转变量调用,但我想这可能是您可以自动化的事情。看看你能不能跟随我的脑电波。

首先,您必须获得所有“未定义变量”通知的列表。这就像注册错误处理程序、检查 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' );

如果您要在请求的每个页面中包含此文件(可选地使用php_prepend_file),在单击整个应用程序后,您最终会在“undefined.php”中得到所有未定义的变量。

这是相当有趣的信息,因为您现在知道哪个未定义变量位于哪个文件中、在哪一行以及它实际存在于哪个超全局中。在确定超全局时,我记住了 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),它们都存在于一个(或多个)超全局变量中。在我的浏览器中访问该页面后,我的日志文件中有两个条目undefined.php;即 foo 和 bar。然后,我运行了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'];

万岁!没有更多未定义的变量,这些是从正确的超全局变量中读取的。大胖免责声明:先创建备份。此外,这不会立即解决您的所有问题。如果你有一个if( $foo )语句,未定义的变量将确保相应的块永远不会被执行,这意味着很可能不是所有未定义的变量都会一次被捕获(但它会在第二次或第三次使用此脚本时解决该问题) . 尽管如此; 这是开始“清理”代码库的好地方。

另外,恭喜您阅读了我的全部答案。:)

于 2012-08-29T14:24:33.267 回答