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:どうか、グローバル変数や変数変数を使用するのは悪いプログラミングだとわざわざ言わないでください。これは私自身ではあまりにもよく知っていますが、他のオプションは、各行に1,000行のコードで約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;
        }
    }
}

$_POSTこれは、配列がorであっても、配列が渡されたときに正常に機能し$_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

extract()この状況に直接適用されるに関する警告は、PHP Web サイトからのものです。

ユーザー入力 (つまり、$_GET、$_FILES など) などの信頼できないデータに対しては、extract() を使用しないでください。たとえば、一時的にregister_globalsに依存する古いコードを実行する場合は、必ず EXTR_SKIP などの上書きされない extract_type 値のいずれかを使用し、 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 の順序を念頭に置きました。

巧妙な小さなトリックの次の部分では、notice が見つかったすべてのファイルをループして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;

2 つの未定義変数 ($fooおよび$bar) があり、どちらも 1 つ (または複数) のスーパーグローバルに存在します。ブラウザでページにアクセスした後、ログファイルに 2 つのエントリがありました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 )、未定義の変数は、対応するブロックが実行されないようにします。つまり、すべての未定義の変数が一度にキャッチされるわけではありません (ただし、このスクリプトを使用すると、2 回目または 3 回目の実行でその問題が解決されます)。 . それにもかかわらず; コードベースの「クリーンアップ」を開始するのに適した場所です。

また、私の回答全体を読んでおめでとうございます。:)

于 2012-08-29T14:24:33.267 に答える