Skip to content

Instantly share code, notes, and snippets.

@rmccue
Forked from nacin/find-local-vars.php
Created January 22, 2012 14:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmccue/1657281 to your computer and use it in GitHub Desktop.
Save rmccue/1657281 to your computer and use it in GitHub Desktop.
Find Unset Local Variables
<?php
/**
* Find unset PHP variables
*
* Deals with scope fairly correctly.
*
* Known bugs:
* - Does not yet support `if: ... endif;` (etc)
* - Does not yet support {$a}/${a} (coming soon)
*/
// By default, parse ourself!
if (empty($path_to_file)) {
$path_to_file = __FILE__;
}
$file = file_get_contents($path_to_file);
// Tokenize
$tokens = token_get_all($file);
// Setup
$errors = array(); // array(array($var, $line), ...)
$last = null; // Last T_VARIABLE
$last_line = null; // Where $last was defined
$expecting = false; // Are we expecting a new variable? (After => in foreach, or "as")
$ignore_closeblock = false;
$inherits_scope = false;
$add_on_new_scope = null;
// $defined acts as a stack
// A new array is created for each scope
$defined = array(array());
$current_scope = 0;
foreach ($tokens as $token) {
if (is_string($token)) {
// Not sure when it'd be null, but just in case
if ($token === '=' && $last !== null) {
$defined[$current_scope][] = $last;
$last = null;
$last_line = null;
}
elseif ($token === '{') {
if ($inherits_scope) {
$defined[] = &$defined[$current_scope];
}
else {
$defined[] = array();
}
$current_scope++;
if ($add_on_new_scope !== null) {
$defined[$current_scope][] = $add_on_new_scope;
$add_on_new_scope = null;
}
}
elseif ($token === '}' && !$ignore_closeblock) {
array_pop($defined);
$current_scope--;
}
elseif ($last !== null) {
$errors[] = array($last, $last_line);
$last = null;
$last_line = null;
}
continue;
}
switch ($token[0]) {
case T_WHITESPACE:
continue;
// We're about to expect a new variable
case T_DOUBLE_ARROW: // This includes array('a' => 'b') too, that should be fixed
case T_AS:
case T_EMPTY:
case T_ISSET:
$expecting = true;
continue;
// We found a variable!
case T_VARIABLE:
// Prefixed with $
$name = substr($token[1], 1);
// Are we expecting one?
if ($expecting) {
$add_on_new_scope = $name;
$expecting = false;
continue;
}
// Do we know about it?
if (in_array(substr($token[1], 1), $defined[$current_scope])) {
continue;
}
// Whoops, we already had a variable, what's this doing here?
// (Unlikely to ever get here)
if ($last !== null) {
$errors[] = array($last, $last_line);
}
$last = $name;
$last_line = $token[2];
continue;
// Includes a { in it, ignore that for closing blocks
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_CURLY_OPEN:
case T_STRING_VARNAME:
$ignore_closeblock = true;
// more here pls
continue;
// All types of assignment
case T_AND_EQUAL:
case T_MOD_EQUAL:
case T_MUL_EQUAL:
case T_SL_EQUAL:
case T_SR_EQUAL:
case T_XOR_EQUAL:
$defined[$current_scope][] = $last;
$last = null;
$last_line = null;
continue;
// These have no scope, so we fake one
case T_DO:
case T_WHILE:
case T_IF:
case T_ELSE:
case T_FOR:
case T_FOREACH:
$inherits_scope = true;
default:
if ($last !== null) {
$errors[] = array($last, $last_line);
$last = null;
$last_line = null;
}
}
}
foreach ($errors as $error) {
printf('L%2$d: $%1$s' . PHP_EOL, $error[0], $error[1]);
}
@rmccue
Copy link
Author

rmccue commented Jan 22, 2012

Probably also doesn't deal with assignment correctly, not sure on that.

@rmccue
Copy link
Author

rmccue commented Jan 22, 2012

Added T_EMPTY and T_ISSET to tokens that expect an undefined (more that they don't care, really).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment