<?php
/**
* @author Martin Scotta <martinscotta@gmail.com>
*/
/**
* PHP5.3 version to 5.2.x translator
*
* It attemps to the its best to translate classes names like
* <code>\php53\Class</code> into <code>php53_Class</code>
*
* Usage: <code>
$vt = VersionTranslater::getTranslator( '/path/to/Class.php' );
$vt->translateTo( '/other/path/Class.php' );
*</code>
*/
abstract class VersionTranslater {
/**
* Return the correct Translator for a given <code>$path</code>
*
* @param string $path
* @return VersionTranslater <code>null</code> if $path is neither a file nor a directory
*/
static function getTranslator($path) {
if( is_dir( $path )) {
return new DirectoryTranslator( $path );
}
if( is_file( $path )) {
return new FileTranslator( $path );
}
}
/**
* Translates the <code>$path</code> into the OS format
* @param string $path
* @return string
*/
static function fixPath($path){
return strtr( $path, array( '\\' => DIRECTORY_SEPARATOR, '/' => DIRECTORY_SEPARATOR));
}
/**
* @var string
*/
private $path;
/**
* @param string $path a filesystem path to the resource to be translated
*/
function __construct($path) {
$path = self::fixPath($path);
$this->path = new SplFileInfo($path);
}
/**
* @return string
*/
function getPath() {
return $this->path;
}
/**
* Runs the translation process and outputs the result in the <code>$outputPath</code>
*
* @param string $outputPath
* @throws RuntimeException on parser error
*/
abstract function translateTo($outputPath);
}
/**
* a directory translator
*/
class DirectoryTranslator extends FileTranslator {
/**
* Perform a full directory translation directory and outputs the results on <code>$outputDir</code>
* It skips everything that starts with '.'
*
* @param string $outputDir
* @throw RuntimeException
*/
function translateTo($outputDir) {
$outputPath = self::fixPath($outputDir);
if( !is_dir( $outputDir )) {
mkdir( $outputDir );
}
$directoryIterator = new RecursiveDirectoryIterator( $this->getPath(), RecursiveIteratorIterator::CHILD_FIRST);
foreach($directoryIterator as $file) {
$fileName = $file->getFileName();
if( '.' === $fileName[0]) {
continue;
}
$translator = VersionTranslater::getTranslator( $file->getPathName() );
$translator->translateTo( $outputDir . DIRECTORY_SEPARATOR . $fileName);
}
}
}
/**
* A file version translator
*
* This is where all the magic happens
*/
class FileTranslator extends VersionTranslater {
/**
* Tranlates a php file
*
* @param string $outputFile
* @throw RuntimeException
*/
function translateTo($outputFile) {
if( strtolower(strpos($this->getPath(), -3)) !== 'php') {
file_put_contents( $outputFile, $this->getCode() );
}
$tokens = PHPToken::getTokens( $this );
if(!$output = fopen($outputFile, 'w')){
throw new RuntimeException("Unable to open $outputFile for writting");
}
$namespace = array();
$uses = array();
translate_start:
if(!$token = array_shift($tokens)) goto translate_end;
switch( $token->getCode() ) {
case T_NS_SEPARATOR:goto translate_start;
case T_NAMESPACE: goto translate_namespace;
case T_USE: goto translate_use;
case T_STRING: array_unshift($tokens, $token);
goto translate_string;
case T_CLASS:
case T_INTERFACE:
case T_EXTENDS:
case T_IMPLEMENTS:
case T_INSTANCEOF:
case T_NEW:
fprintf($output, '%s', $token );
goto translate_class_name;
default: fprintf($output, '%s', $token );
if( count($tokens) > 0 )
goto translate_start;
}
goto translate_end;
translate_namespace:
if(!$token = array_shift($tokens)) goto translate_end;
switch( $token->getCode() ) {
case T_STRING: $namespace[] = (string) $token;
case T_NS_SEPARATOR:
case T_WHITESPACE: goto translate_namespace;
case '{':
case ';': $namespace = implode( '_', $namespace);
goto translate_start;
default: goto translate_error;
}
translate_use:
if(!$token = array_shift($tokens)) goto translate_end;
$parseUse = array();
$parseUseAs = false;
$parseUseIsGlobal = true;
switch( $token->getCode() ) {
case T_WHITESPACE: goto translate_use;
case T_NS_SEPARATOR: goto translate_use_in;
case T_STRING: $parseUse[] = (string) $token;
$parseUseIsGlobal = false;
goto translate_use_in;
default: goto translate_error;
}
translate_use_in:
if(!$token = array_shift($tokens)) goto translate_end;
switch( $token->getCode() ) {
case T_STRING: $parseUse[] = (string) $token;
case T_WHITESPACE:
case T_NS_SEPARATOR: goto translate_use_in;
case T_AS: goto translate_use_as;
case ';': $parseUseClass = $parseUseAs ? $parseUseAs : end( $parseUse );
$parseUse = implode('_', $parseUse);
if( $namespace && !$parseUseIsGlobal ) {
$parseUse = $namespace . '_' . $parseUse;
}
$uses[ $parseUseClass ] = $parseUse;
unset($parseUse, $parseUseClass, $parseUseIsGlobal, $parseUseAs);
goto translate_start;
default: goto translate_error;
}
translate_use_as:
if(!$token = array_shift($tokens)) goto translate_end;
switch($token->getCode()) {
case T_WHITESPACE: goto translate_use_as;
case T_STRING: $parseUseAs = (string) $token;
goto translate_use_in;
default: goto translate_error;
}
translate_class_name:
$parseClass = array();
$parseClassIsGlobal = false;
if(!$token = array_shift($tokens)) goto translate_end;
if( $token->getCode() === T_WHITESPACE ) {
fprintf($output, $token);
} else {
array_unshift($tokens, $token);
}
translate_class_name_in:
if(!$token = array_shift($tokens)) goto translate_end;
switch( $token->getCode() ) {
case T_NAMESPACE: goto translate_class_name_in;
case T_NS_SEPARATOR: if( count($parseClass) === 0 )
$parseClassIsGlobal = true;
goto translate_class_name_in;
case T_STRING: $parseClass[] = (string) $token;
goto translate_class_name_in;
default: if( count($parseClass) > 0 ) {
$parseClass = implode('_', $parseClass);
if( array_key_exists($parseClass, $uses)) {
$parseClass = $uses[$parseClass];
} elseif( $namespace && !$parseClassIsGlobal && !class_exists($parseClass, false)){
$parseClass = $namespace . '_' . $parseClass;
}
fprintf($output, '%s', $parseClass );
}
fprintf($output, '%s', $token);
unset($parseClass);
goto translate_start;
}
translate_string:
if(!$token = array_shift($tokens)) goto translate_end;
if( $token->getCode() !== T_STRING ) goto translate_end;
switch( (string) $token ) {
case 'self':
case 'parent':
case 'static':
fprintf($output, $token);
goto translate_start;
}
$parseString = $token;
if(!$token = array_shift($tokens)) goto translate_end;
switch( $token->getCode() ) {
case T_NS_SEPARATOR:
case T_DOUBLE_COLON: array_unshift($tokens, $token);
array_unshift($tokens, $parseString);
unset( $parseString );
goto translate_class_name;
default: fprintf($output, '%s', $parseString);
fprintf($output, '%s', $token);
goto translate_start;
}
translate_error:
$tokenName = $token->isLiteral() ? "'{$token->getName()}'" : $token->getName();
throw new RuntimeException(
"Parse error: unexpected {$tokenName} in $outputFile on line {$token->getLine()}"
);
translate_end:
fclose( $output );
}
/**
* Reads all the file and
*/
function getCode() {
return file_get_contents( $this->getPath() );
}
}
/**
* A helper class used for hold php token information
*/
class PHPToken {
/**
* @param FileTranslator $translator
* @return array
*/
static function getTokens(FileTranslator $translator) {
$tokens = array();
foreach(token_get_all( $translator->getCode() ) as $token) {
$tokens[] = new self($token);
}
return $tokens;
}
/**
* @var array|string
*/
private $token;
/**
* @param array|string the parsed php token structure
*/
function __construct($token) {
$this->token = $token;
}
/**
* Returns the token as a string, it's the code
*
* @return string
*/
function __toString() {
return $this->isLiteral() ? $this->token : $this->token[1];
}
/**
* Returns the php token structure
*/
function getToken() {
return $this->token;
}
/**
* Returns the token code
* @return int
*/
function getCode() {
return $this->isLiteral() ? $this->token : $this->token[0];
}
/**
* Returns true, only and only if, the token is a string literal
*
* <code>
$t1 = new PHPToken( array(T_OPEN_TAG, '<?php'));
$t2 = new PHPToken( ';' );
var_dump( $t1->iaLiteral() ); // false
var_dump( $t2->iaLiteral() ); // true
</code>
*
* @return boolean
*/
function isLiteral() {
return !is_array($this->token);
}
/**
* Return php constant name for the token or the token if it's literal
*
* @return string
*/
function getName(){
return $this->isLiteral() ? $this->token : token_name( $this->token[0] );
}
/**
* Return the line number of the token, or <code>false</code> if it's literal
*
* @return int
*/
function getLine(){
return $this->isLiteral() ? false : $this->token[2];
}
}
|