Last active
August 29, 2015 14:26
-
-
Save tlevi/dd7930026cb5b0ef7b97 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/php | |
<?php | |
if (empty($argv[1]) || preg_match('#help#', $argv[1])) { | |
echo <<<END | |
Magically rename php4 style constructors to php5 style and add a deprecated stub for BC. | |
Recommended usage: | |
time git grep -lP '^\s*((abstract|final)\s+)*class [\w\d]+(\s*\{.*|\s*|\s+extends.*)$' -- '*.php' | sort -R | xargs -rn200 -P8 replace_deprecated_constructors.php | |
You need php-parser checked out in the same directory as this script: | |
git clone https://github.com/nikic/PHP-Parser.git php-parser | |
Optional first arg: | |
"callsonly": replace only the calls to old style constructors | |
END; | |
exit (1); | |
} | |
require_once __DIR__.'/php-parser/lib/bootstrap.php'; | |
use \PhpParser\Node; | |
class mark_php4_constructor implements \PhpParser\NodeVisitor { | |
public $marked = array(); | |
public function enterNode(Node $node) { | |
if ($node->getType() != 'Stmt_Class') { | |
return; | |
} | |
$php5 = $php4 = null; | |
foreach ($node->stmts as $i => $child) { | |
if ($child->getType() == 'Stmt_ClassMethod') { | |
if (strcasecmp($child->name, $node->name) === 0) { | |
$php4 = $child; | |
} else if ($child->name == '__construct') { | |
$php5 = $child; | |
break; // php5 found, no need to go further. | |
} | |
} | |
} | |
if (!$php5 && !$php4) { | |
return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; | |
} | |
$child = $php5 ? $php5 : $php4; | |
$start = $child->getLine(); | |
$end = $child->getAttribute('endLine'); | |
$extends = isset($node->extends->parts[0]) ? $node->extends->parts[0] : null; | |
$this->marked[$node->name] = (object) array( | |
'class' => $node->name, | |
'method' => $child->name, | |
'start' => $start-1, | |
'end' => $end, | |
'extends' => $extends, | |
'php4' => $php5 ? false : true, | |
); | |
return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; | |
} | |
public function leaveNode(Node $node) { | |
return; | |
} | |
public function afterTraverse(array $nodes) { | |
return; | |
} | |
public function beforeTraverse(array $nodes) { | |
return; | |
} | |
} | |
function do_it() { | |
global $argv; | |
ini_set('memory_limit', '1G'); | |
$files = $argv; | |
$callsonly = false; | |
if (preg_match('#callsonly#', $files[1])) { | |
$callsonly = true; | |
unset($files[1]); | |
} | |
unset($files[0]); | |
foreach ($files as $file) { | |
if (!file_exists($file)) { | |
throw new Exception('No such file: '.$file); | |
} | |
echo "$file\n"; | |
analyze_and_replace($file, $callsonly); | |
} | |
} | |
function analyze_and_replace($file, $callsonly = false) { | |
$finfo = finfo_open(); | |
$endl = preg_match('#CRLF#', finfo_file($finfo, $file)) ? "\r\n" : "\n"; | |
list($marks, $code) = parse_file($file); | |
foreach ($marks as $classname => $mark) { | |
replace_one($file, $classname, $endl, $callsonly); | |
} | |
} | |
function replace_one($file, $classname, $endl, $callsonly = false) { | |
list($marks, $code) = parse_file($file); | |
if (!isset($marks[$classname])) { | |
throw new Exception("cannot find expected class mark!"); | |
} | |
$mark = $marks[$classname]; | |
echo " {$mark->class}::{$mark->method}\n"; | |
$lines = file($file); | |
// Change php4 to php5 constructor. | |
if (empty($callsonly) && $mark->php4) { | |
$lines[$mark->start] = preg_replace("#function(\s+){$mark->method}(\s*)\(#i", 'function\1__construct\2(', $lines[$mark->start]); | |
} | |
// Replace old style parent constructor calls. | |
if ($mark->extends) { | |
$scopes = array("{$mark->extends}::", 'parent::', preg_quote('$this->')); | |
$filter = '#(?:'.implode('|', $scopes)."){$mark->extends}#i"; | |
} | |
for ($i=$mark->start+1; $i < $mark->end; $i++) { | |
if ($mark->extends) { | |
$lines[$i] = preg_replace($filter, 'parent::__construct', $lines[$i]); | |
} | |
$lines[$i] = preg_replace('#(\s)([\w\d\\\]+)::\2\(#i', '\1\2::__construct(', $lines[$i]); | |
} | |
// Insert our legacy constructor. | |
if (empty($callsonly) && $mark->php4) { | |
preg_match('#^\s*#', $lines[$mark->start], $matches); | |
$indent = isset($matches[0]) ? $matches[0] : ' '; | |
$legacy = getLegacyConstructor($mark->method, $indent, $endl); | |
array_splice($lines, $mark->end, 0, $legacy); | |
} | |
$newcode = implode('', $lines); | |
if ($newcode != $code) { | |
file_put_contents($file, $newcode); | |
} | |
} | |
function clean_exec($cmd) { | |
$output = array(); | |
$first = exec($cmd, $output, $status); | |
return array($status, $output, $first); | |
} | |
function getLegacyConstructor($classname, $indent, $endl) { | |
$legacy = <<<ENDFRAG | |
/** | |
* Legacy style constructor, for BC. | |
* @deprecated since 2.9, use $classname::__construct instead | |
*/ | |
public function $classname() { | |
\$msg = 'Legacy constructor called, please update your code to call php5 constructor!'; | |
if (function_exists('debugging')) { | |
debugging(\$msg, DEBUG_DEVELOPER); | |
} else { | |
trigger_error(\$msg, E_USER_DEPRECATED); | |
} | |
\$args = func_get_args(); | |
call_user_func_array('self::__construct', \$args); | |
} | |
ENDFRAG; | |
$legacy = explode("\n", $legacy); | |
foreach ($legacy as $k => $line) { | |
if (!preg_match('#^\s*$#', $line)) { | |
$legacy[$k] = $indent.$line; | |
} | |
$legacy[$k] .= $endl; | |
} | |
return $legacy; | |
} | |
function parse_file($file) { | |
$parser = new \PhpParser\Parser(new \PhpParser\Lexer); | |
$visitor = new mark_php4_constructor(); | |
$traverser = new \PhpParser\NodeTraverser(); | |
$traverser->addVisitor($visitor); | |
$code = file_get_contents($file); | |
$tree = $parser->parse($code); | |
$traverser->traverse($tree); | |
return array($visitor->marked, $code); | |
} | |
do_it(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment