But if you know a better solution to get rid of all those "undefined variable" warnings, you can make my day.
There is. Fix the problem of not having superglobals used. Now, naturally, I'm not saying you should manually change every flipping variable call by yourself, but I imagine this is something you could automate, maybe. See if you can follow my brainwave.
First, you would have to get a listing of all "undefined variable" notices. This is as simple as registering an error handler, checking for E_NOTICE calls and check if it's an undefined variable call. I have taken the liberty of writing up a little piece of code that does precisely that.
<?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' );
If you were to include this file in each page that is requested (optionally using php_prepend_file
), you'll end up with all your undefined variables in 'undefined.php', after clicking through your entire application.
This is rather interesting information, as you now know which undefined variable is located in which file, on which line, and in which superglobal it actually exists. When determining the superglobal, I've kept the order of Environment, Get, Post, Cookie, and Server in mind, for deciding which takes precedence.
For the next part of our neat little trick, we'll have to loop through all the files where a undefined variable
notice was found, and try to replace the undefined variable with its superglobal counterpart. This is actually pretty easy as well, and again, I've created a script to do so:
#!/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 );
Now, there's simply the matter of calling this script. I've tested this, and this is is the file I've tested it with:
<?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;
There are two undefined variables ($foo
and $bar
), both of which exist in one (or more) of the superglobals. After visiting the page in my browser, there were two entries in my logfile undefined.php
; namely foo and bar. Then, I ran the command php globalsfix.php undefined.php --backup
which gave me the following output:
berry@berry-pc:/www/public/globalfix% php globalsfix.php undefined.php --backup
Executed 2 replacements.
Curious as to what the results were? Well, so was I. Here it is:
<?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'];
Hurrah! No more undefined variables, those are being read from the correct superglobals as of now. Big fat disclaimer: create a backup first. Also, this will not solve all of your problems right away. If you have an if( $foo )
statement, the undefined variable will make sure the corresponding block is never executed, meaning it's very likely not all undefined variables will be caught in one go (but it will solve that issue on the second or third go with this script). Nevertheless; it is a good place to start "cleaning up" your code base.
Also, congrats on reading my entire answer. :)