Location: PHPKode > scripts > Version Translator > VersionTranslater.php
<?php	
/**
 * @author Martin Scotta <hide@address.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];
	}
}
Return current item: Version Translator