<?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]; } }
|