Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#!/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
You can’t perform that action at this time.