Skip to content

Instantly share code, notes, and snippets.

@hpbuniat
Last active April 3, 2021 22:33
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hpbuniat/3860059 to your computer and use it in GitHub Desktop.
Save hpbuniat/3860059 to your computer and use it in GitHub Desktop.
Super-simple script to convert a existing project to use namespaces
<?php
/**
* Super-simple script to convert a existing project to use namespaces
*
* @author Hans-Peter Buniat <hpbuniat@googlemail.com>
* @copyright 2012 Hans-Peter Buniat <hpbuniat@googlemail.com>
* @license http://opensource.org/licenses/BSD-3-Clause
*/
class namespaceRefactor {
/**
* The found files
*
* @var array
*/
protected $_aFiles = array();
/**
* The found classes
*
* @var array
*/
protected $_aClasses = array();
/**
* Write the results to the file or just dump the changes
*
* @var bool
*/
protected $_bWrite = false;
/**
* The directory where we're looking for files
*
* @var string
*/
protected $_sDirectory = '';
/**
* An optional prefix
*
* @var string
*/
protected $_sPrefix;
/**
* Create
*
* @param array $aOpts
*/
public function __construct(array $aOpts) {
$this->_sDirectory = $aOpts['d'];
$this->_sPrefix = $aOpts['p'];
if ($aOpts['w'] === 'true') {
$this->_bWrite = true;
}
}
/**
* Get all php-files from the given directory
*
* @return namespaceRefactor
*/
public function getFiles() {
$oIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_sDirectory));
$aSuffixes = array(
'php'
);
$sRegex = '/^.+\.(' . implode('|', $aSuffixes) . ')$/i';
$oFiles = new RegexIterator($oIterator, $sRegex, RecursiveRegexIterator::GET_MATCH);
$this->_aFiles = array();
foreach($oFiles as $aFile) {
$this->_aFiles[] = $aFile[0];
}
return $this;
}
/**
* Parse all files and find class-declarations
* - If a file does contain a namespace declaration, the class-declaration should not be converted
*
* @return namespaceRefactor
*/
public function readClasses() {
$aFiles = array();
foreach ($this->_aFiles as $sFile) {
$aFiles[$sFile] = array();
$aTokens = token_get_all(file_get_contents($sFile));
$aFound = array();
$bLocked = $bClassFound = false;
foreach ($aTokens as $aToken) {
$sType = is_array($aToken) ? $aToken[0] : null;
$sData = (string) (is_array($aToken) ? $aToken[1] : $aToken);
if ($bLocked === false and $bClassFound === true and $sType === T_STRING) {
$this->_aClasses[$sData] = $sData;
$aFound[] = $sData;
$aFiles[$sFile] = $sData;
$bClassFound = false;
}
if ($sType === T_CLASS or $sType === T_INTERFACE) {
$bClassFound = true;
}
// ensure, that classes which are already namespaced are not replaced again
if ($sType === T_NAMESPACE) {
$bLocked = true;
foreach ($aFound as $sFoundClass) {
if (empty($this->_aClasses[$sFoundClass]) !== true) {
unset($this->_aClasses[$sFoundClass]);
}
}
}
}
}
$this->_aFiles = $aFiles;
return $this;
}
/**
* Create the new namespace class for all found classes
*
* @return namespaceRefactor
*/
public function getNamespaces() {
$aClasses = array();
foreach ($this->_aClasses as $sClass) {
$aClass = explode('_', $sClass);
if (count($aClass) > 1 or empty($this->_sPrefix) !== true) {
$sNewClass = array_pop($aClass);
$sNamespace = (count($aClass) > 1) ? sprintf('\%s', implode('\\', $aClass)): '';
$aClasses[$sClass] = array(
'class' => $sNewClass,
'namespace' => $sNamespace
);
if (empty($this->_sPrefix) !== true) {
$iLenght = strlen($this->_sPrefix);
if (substr($aClasses[$sClass]['namespace'], 1, $iLenght) !== $this->_sPrefix) {
$aClasses[$sClass]['namespace'] = sprintf('\%s%s', $this->_sPrefix, $aClasses[$sClass]['namespace']);
}
}
}
}
$this->_aClasses = $aClasses;
return $this;
}
/**
* Replace the declarations
*
* @return void
*/
public function replace() {
foreach ($this->_aFiles as $sFile => $sClass) {
$sContent = file_get_contents($sFile);
$sCompare = $sContent;
if (empty($sClass) !== true and empty($this->_aClasses[$sClass]) !== true) {
$aContent = explode(' */', $sContent);
$aContent[1] = sprintf('%snamespace %s;%s%s',PHP_EOL, substr($this->_aClasses[$sClass]['namespace'], 1), PHP_EOL, $aContent[1]);
$sContent = implode(' */', $aContent);
}
foreach ($this->_aClasses as $sFindClass => $aReplace) {
$sReplace = ($sClass === $sFindClass) ? $aReplace['class'] : sprintf('%s\%s', $aReplace['namespace'], $aReplace['class']);
$sContent = str_replace($sFindClass, $sReplace, $sContent);
}
if ($sCompare !== $sContent) {
if ($this->_bWrite === true) {
file_put_contents($sFile, $sContent);
}
else {
print_r(sprintf('%s:%s%s', $sFile, PHP_EOL, $sContent));
}
}
}
}
}
$aOpts = getopt('d:w:p:');
if (empty($aOpts['d']) === true) {
print_r('usage: ' . $argv[0] . ' -p prefix -d directory -w [false|true]' . PHP_EOL);
exit;
}
if (empty($aOpts['w']) === true) {
$aOpts['w'] = 'false';
}
$o = new namespaceRefactor($aOpts);
$o->getFiles()->readClasses()->getNamespaces()->replace();
@sebastianbrandner
Copy link

hi

my first change was to fix the bug that i found if you try to parse a file without a FILE COMMENT.
the "namespace" was inserted before the first member variable.

so i changed the split/insert/merge part with regex

//                $aContent = explode(' */', $sContent);  
//                $aContent[1] = sprintf('%snamespace %s;%s%s',PHP_EOL, substr($this->_aClasses[$sClass]['namespace'], 1), PHP_EOL,  $aContent[1]);  
//                $sContent = implode(' */', $aContent); 
                $pattern = '/^(\s*<\?(php?).*?\n(\/\*(.*?)\*\/\s+)?)/s'; 
                $sContent = preg_replace( 
                    $pattern, 
                    "\\1" . PHP_EOL . "namespace " . substr($this->_aClasses[$sClass]['namespace'], 1) . ";" . PHP_EOL . PHP_EOL, 
                    $sContent 
                ); 

kr
sebastian

@sebastianbrandner
Copy link

sebastianbrandner commented Jul 21, 2017

the next part was, to comment the require/include out, if the class inside that file is changed and you can access it via autoloading

if someone maybe need such a change:
https://gist.github.com/sebastianbrandner/b2b916a5be36dabcad571b2f3b439052

the dump functionality printed the expected result with my test file setup.

kr
sebastian

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