Location: PHPKode > scripts > Advanced Sourcecode Reflection in PHP > reflection_php53.php
<?php

/**
 * Advanced PHP Reflector (part of Lotos Framework)
 *
 * Copyright (c) 2005-2011 Artur Graniszewski (hide@address.com) 
 * All rights reserved.
 * 
 * @category   Library
 * @package    Lotos
 * @subpackage Lang
 * @copyright  Copyright (c) 2005-2011 Artur Graniszewski (hide@address.com)
 * @license    GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007
 * @version    $Id$
 */  
 
namespace System\Reflection
{
	use ReflectionMethod, ReflectionFunction, ReflectionClass, ReflectionProperty, ReflectionException;

	// Add compatibility with PHP 5.2
	if(!defined('T_NAMESPACE')) {
		define('T_NAMESPACE', 377);
		define('T_NS_SEPARATOR', 380);
	}

	/**
	 * The AdvancedReflectionClass class reports information about a class. 
	 */
	class AdvancedReflectionClass extends ReflectionClass
	{
		/**
		 * Returns class declaration.
		 *
		 * @return string 
		 */
		public function getDeclaration() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getDeclaration();
		}
		
		/**
		 * Returns class body.
		 *
		 * @return string 
		 */
		public function getBody() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getBody();
		}
		
		/**
		 * Returns starting column number of the first line.
		 * 
		 * @return int
		 */
		public function getStartColumn() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartColumn();
		}
		
		/**
		 * Returns file position of the first character of this class.
		 * 
		 * @return int
		 */
		public function getStartPosition() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartPosition();
		}		
		
		/**
		 * Gets the constructor from a class. 
		 *
		 * @return AdvancedReflectionMethod 
		 */
		public function getConstructor() {
			$method = parent::getConstructor();
			return new AdvancedReflectionMethod($method->getName());
		}
		
		/**
		 * Gets a AdvancedReflectionMethod about a method.
		 * 
		 * @param string $name The method name to reflect.
		 * @return AdvancedReflectionMethod
		 */
		public function getMethod($name) {
			$method = parent::getMethod($name);
			return new AdvancedReflectionMethod($method->getName());
		}
		
		/**
		 * Gets a list of methods. 
		 * 
		 * @param string $filter Any combination of ReflectionMethod::IS_STATIC, ReflectionMethod::IS_PUBLIC, ReflectionMethod::IS_PROTECTED, ReflectionMethod::IS_PRIVATE, ReflectionMethod::IS_ABSTRACT, ReflectionMethod::IS_FINAL.
		 * @return AdvancedReflectionMethod[]
		 */
		public function getMethods($filter = null) {
			$pureMethods = parent::getMethods($filter);
			$advancedMethods = array();
			foreach($pureMethods as $pureMethod) {
				$advancedMethods[] = new AdvancedReflectionMethod($pureMethod->getName());
			}
			return $advancedMethods;
		}
		
		/**
		 * Returns parent class.
		 *
		 * @return AdvancedReflectionClass
		 */
		public function getParentClass() {
			return new AdvancedReflectionClass(parent::getParentClass()->getName());
		}
		
		/**
		 * Gets the interfaces. 
		 *
		 * @return AdvancedReflectionClass[] 
		 */
		public function getInterfaces() {
			$pureInterfaces = parent::getInterfaces();
			$advancedInterfaces = array();
			foreach($pureInterfaces as $pureInterface) {
				$advancedInterfaces[] = new AdvancedReflectionClass($pureInterface->getName());
			}
			
			return $advancedInterfaces;
		}		
	}

	class AdvancedReflectionFunction extends ReflectionFunction
	{
		/**
		 * Returns function declaration.
		 *
		 * @return string 
		 */
		public function getDeclaration() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getDeclaration();
		}
		
		/**
		 * Returns function body.
		 *
		 * @return string 
		 */
		public function getBody() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getBody();
		}
		
		/**
		 * Returns starting column number of the first line.
		 * 
		 * @return int
		 */
		public function getStartColumn() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartColumn();
		}
		
		/**
		 * Returns file position of the first character of this function.
		 * 
		 * @return int
		 */
		public function getStartPosition() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartPosition();
		}		
	}
	/**
	 * The AdvancedReflectionMethod class reports information about a method. 
	 */		
	class AdvancedReflectionMethod extends ReflectionMethod
	{
		/**
		 * Returns method declaration.
		 *
		 * @return string 
		 */		
		public function getDeclaration() {
			return Tokenizer::getInstance(parent::getDeclaringClass()->getFileName())->getItemByReflection($this)->getDeclaration();
		}
		
		/**
		 * Returns method body.
		 *
		 * @return string 
		 */
		public function getBody() {
			return Tokenizer::getInstance(parent::getDeclaringClass()->getFileName())->getItemByReflection($this)->getBody();
		}
		
		/**
		 * Returns starting column number of the first line.
		 * 
		 * @return int
		 */
		public function getStartColumn() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartColumn();
		}		
		
		/**
		 * Returns file position of the first character of this class.
		 * 
		 * @return int
		 */
		public function getStartPosition() {
			return Tokenizer::getInstance($this->getFileName())->getItemByReflection($this)->getStartPosition();
		}		
		/**
		 * Gets the declaring class for the reflected method. 
		 * 
		 * @return AdvancedReflectionClass 
		 */
		public function getDeclaringClass() {
			$class = parent::getDeclaringClass();
			return new AdvancedReflectionClass($class->getName()); 
		}
		
		/**
		 * Returns the methods prototype. 
		 * 
		 * @return AdvancedReflectionMethod 
		 */
		public function getPrototype() {
			$proto = parent::getPrototype();
			return new AdvancedReflectionMethod($proto->getName());
		}		
	}

	/**
	 * The AdvancedReflectionProperty class reports information about a property [FOR FUTURE USE]. 
	 */	
	abstract class AdvancedReflectionProperty extends ReflectionProperty
	{
		
	}	
	
	/**
	 * Holds information about given PHP code block. 
	 */
	class CodeBlockItem
	{
		/**
		 * Starting line of this code block.
		 * 
		 * @var int
		 */
		protected $startLineNumber = 0;
		
		/**
		 * Starting char position of this code block (in the starting line).
		 * 
		 * @var int
		 */		
		protected $startCharColumn = 0;
		
		/**
		 * Starting char position of this code block in the global sourcecode.
		 * 
		 * @var int
		 */		
		protected $startPosition = 0;
		
		/**
		 * Ending char position of this code block in the global sourcecode.
		 * 
		 * @var int
		 */		
		protected $endPosition = 0;
		
		/**
		 * Length of this code block (in bytes).
		 * 
		 * @var int
		 */		
		protected $length = 0;
		
		/**
		 * Parent scope of the code block (namespace/class/function).
		 * 
		 * @var string[]
		 */
		protected $parentScope = array();

		/**
		 * Curent scope of the code block (namespace/class/function).
		 * 
		 * @var string[]
		 */
		protected $scope = array();
		
		/**
		 * Full name of the code block.
		 * 
		 * @var string
		 */
		protected $name;
		
		/**
		 * Global source code (reference only).
		 * 
		 * @var string
		 */
		protected $sourceCode;
		
		/**
		 * Constructor.
		 * 
		 * @param string $sourceCode Global source code.
		 * @param CodeToken $token First PHP token of this code block.
		 * @param CodeToken $typeToken PHP token describing type of the code block.
		 * @param string $name Short name of this code block.
		 * @param string[] $scope Current scope of this code block.
		 * @return CodeBlockItem
		 */
		public function __construct(& $sourceCode, CodeToken $token, $typeToken, $name, $scope) {
			$this->sourceCode = & $sourceCode;
			$this->startCharColumn = $token->charColumn;
			$this->startLineNumber = $token->lineNumber;
			$this->globalPosition = $token->globalPosition;
			$this->parentScope = $scope;
			$this->scope = $scope;
			$this->scope[$typeToken->type] = $name;
			$scope[] = $name;
			
			$this->name = preg_replace('~[\\\]{2,}~', '\\', implode('\\', $scope));
		}
		
		/**
		 * Calculates length of this code block.
		 * 
		 * @param CodeToken $token Last token of this code block.
		 */
		public function setLength(CodeToken $token) {
			$this->length = 1 + $token->globalPosition - $this->globalPosition;
			$this->endPosition = $token->globalPosition;
		}
		
		/**
		 * Returns length od this code block.
		 *
		 * @return int 
		 */
		public function getLength() {
			return $this->length;
		}
		
		/**
		 * Returns code block declaration.
		 *
		 * @return string 
		 */
		public function __getString() {
			return substr($this->sourceCode, $this->globalPosition, $this->length);
		}
		
		/**
		 * Returns code block declaration.
		 *
		 * @return string
		 */
		public function getDeclaration() {
			return $this->__getString();
		}
		
		/**
		 * Returns code block body.
		 *
		 * @return string
		 */		
		public function getBody() {
			$found = preg_match('~\{(.*?)\}~Umsx', $this->__getString(), $matches);
			if($found) {
				return $matches[1];
			}
			
			return $this->__getString();
		}
		
		/**
		 * Returns name of this code block.
		 *
		 * @return string
		 */		
		public function getName() {
			return $this->name;				
		}
		
		/**
		 * Returns scope of this code block.
		 *
		 * @return string[]
		 */		
		public function getScope() {
			return $this->scope;
		}
		
		/**
		 * Returns start position of this code block in the first line of code block.
		 * 
		 * @return int 
		 */		
		public function getStartColumn() {
			return $this->startCharColumn;
		}
		
		/**
		 * Returns file start position of this code block.
		 * 
		 * @return int 
		 */
		public function getStartPosition() {
			return $this->globalPosition;
		}		
	}
	
	/**
	 * Holds information about given PHP token.
	 */
	class CodeToken
	{
		/**
		 * Starting char position of this token (in the starting line of code).
		 * 
		 * @var int
		 */
		public $charColumn = 0;
		
		/**
		 * Starting line number of this token.
		 * 
		 * @var int
		 */		
		public $lineNumber = 0;
		
		/**
		 * Starting char position of this token in the global sourcecode.
		 * 
		 * @var int
		 */
		public $globalPosition = 0;
		
		/**
		 * Token content.
		 * 
		 * @var string
		 */
		public $content;
		
		/**
		 * Token type.
		 * 
		 * @var int
		 */
		public $type;
		
		/**
		 * Token type name.
		 * 
		 * @var string
		 */
		public $typeName;
		
		/**
		 * Constructor.
		 * 
		 * @param mixed $token PHP token (array or string).
		 * @param int $lineNumber Current line number in the global source code.
		 * @param int $charColumn Current char position in the current line number.
		 * @param int $globalPosition Current char position in the global source code.
		 * @return CodeToken
		 */
		public function __construct($token, & $lineNumber, & $charColumn, & $globalPosition) {
			$this->globalPosition = $globalPosition;
			if(is_array($token)) {
				$this->type = $token[0];
				$this->typeName = token_name($this->type);
				$this->charColumn = (int)$charColumn;
				$this->content = $token[1];
				$this->lineNumber = $lineNumber;
				
			} else {
				$this->lineNumber = $lineNumber;
				$this->charColumn = (int)$charColumn;
				$this->content = $token;
				if(trim($token, "\n\r\t ") == '') {
					$this->type = T_WHITESPACE;
					$this->typeName = 'T_WHITESPACE';
				} else {
					$this->type = -1;
					$this->typeName = 'T_UNKNOWN';
				}
			}
						
			$length = strlen($this->content);
			
			$newLines = substr_count($this->content, "\n");
			if($newLines > 0) {
				$lineNumber += $newLines;
				$charColumn = strpos(strrev($this->content), "\n");
			} else {
				$charColumn += $length;
			}
			
			$globalPosition += $length;
		}
	}
	
	/**
	 * PHP Tokenizer (AdvancedReflection* helper)
	 */
	class Tokenizer
	{
		/**
		 * Line number of current token.
		 * 
		 * @var int
		 */
		protected $lineNumber = 0;
		
		/**
		 * Char position of curent token (in the current line of code).
		 * 
		 * @var int
		 */		
		protected $charColumn = 0;
		
		/**
		 * Starting char position of current token in the global sourcecode.
		 * 
		 * @var int
		 */		
		protected $globalPosition = 0;
		
		/**
		 * List of PHP tokens.
		 * 
		 * @var mixed[]
		 */
		protected $tokens = array();
		
		/**
		 * Curly bracket level counter.
		 * 
		 * @var int
		 */
		protected $openBraces = 0;
		
		/**
		 * Level counter of curly brackets for given type of scope.
		 * 
		 * @var int[]
		 */
		protected $currentBraces = array(T_NAMESPACE => 0, T_CLASS => 0, T_FUNCTION => 0);
		
		/**
		 * Scope of the current token.
		 * 
		 * @var string[]
		 */
		protected $currentScope = array(T_NAMESPACE => null, T_CLASS => null, T_FUNCTION => null);
		
		/**
		 * List of code blocks in this source code file.
		 * 
		 * @var CodeBlockItem[]
		 */
		protected $blocks = array(T_NAMESPACE => array(), T_CLASS => array(), T_FUNCTION => array());
		
		/**
		 * Source code of this file.
		 * 		
		 * @var string
		 */
		protected $sourceCode;
		
		/**
		 * Source files cache.
		 * 
		 * @var Tokenizer[]
		 */
		protected static $filesCache = array();
		
		/**
		 * Returns tokenizer instance for the given file.
		 * 
		 * @param string $fileName Name of the source file.
		 * @return Tokenizer
		 */
		public static function getInstance($fileName) {
			if(!isset(self::$filesCache[$fileName])) {
				self::$filesCache[$fileName] = new Tokenizer(file_get_contents($fileName));
			}
			
			return self::$filesCache[$fileName];
		}
		
		/**
		 * Constructor.
		 * 
		 * @param string $sourceCode Global PHP source code.
		 * @return Tokenizer
		 */
		public function __construct($sourceCode) {
			$this->sourceCode = $sourceCode;
			$this->tokens = token_get_all($sourceCode);
			foreach($this->tokens as $index => $token) {
				$this->tokens[$index] = new CodeToken($token, $this->lineNumber, $this->charColumn, $this->globalPosition);
			}
			$this->guessType();
		}		
		
		/**
		 * Finds the code block using reflection mechanism.
		 * 
		 * @param mixed $reflection ReflectionClass or ReflectionMethod instance.
		 */
		public function getItemByReflection($reflection) {
			$shortName = $reflection->getShortName();
			$namespace = null;
			if($reflection instanceof ReflectionClass) {
				$class = $reflection;
				$functionName = null;
			} else if($reflection instanceof ReflectionMethod) {
				$class = $reflection->getDeclaringClass();
				$functionName = $reflection->getShortName();
			} else if($reflection instanceof ReflectionFunction) {
				$class = null;
				$functionName = $reflection->getShortName();
			} else{
				throw new ReflectionException('Unknown reflection type');
			}

			if($class) {			
				if($class->isInternal()) {
					throw new ReflectionException('Cannot read source code of the internal PHP class');
				}			
				$className = $class->getShortName();
				if(method_exists($class, 'getNamespaceName')) {
					$namespace = $class->getNamespaceName();
				} 
			} else {
				if($reflection->isInternal()) {
					throw new ReflectionException('Cannot read source code of the internal PHP function');
				}
				if(method_exists($reflection, 'getNamespaceName')) {
					$namespace = $reflection->getNamespaceName();
				}
				$className = null;
			}			

			$result = array(T_NAMESPACE => $namespace, T_CLASS => $className, T_FUNCTION => $functionName);
			foreach($this->blocks as $blockType) {
				foreach($blockType as $name => $block) {
					if($result == $block->getScope()) {
						return $block;
					}
				}
			}			
			return null;
		}
		
		/**
		 * Get next token and count the curly braces.
		 *
		 * @return CodeToken 
		 */
		protected function nextToken() {
			$token = next($this->tokens);
			if(!$token) {
				return;
			}
			if($token->content === '{') {
				$this->openBraces++;
			} else if($token->content === '}') {
				$this->openBraces--;
				foreach($this->currentScope as $type => $value) {
					if($value) {
						if($this->currentBraces[$type] === $this->openBraces) {
							$this->blocks[$type][$this->currentScope[$type]]->setLength($token);
							$this->currentBraces[$type] = 0;
							$this->currentScope[$type] = null;
						}
					}
				}
			} 
			
			return $token;
		}
		
		/**
		 * Finds all supported code block types.
		 *
		 * @return void 
		 */
		protected function guessType() {
			$token = clone(current($this->tokens));
			do {
				if(in_array($token->type, array(T_NAMESPACE, T_INTERFACE, T_CLASS, T_FUNCTION))) {
					if($token->type === T_INTERFACE) {
						$token->type = T_CLASS;
					}
					$firstToken = $this->findModifiers($token);
					if(!$firstToken) {
						$firstToken = $token;
					}

					$name = $this->getName();
					$block = new CodeBlockItem($this->sourceCode, $firstToken, $token, $name, $this->currentScope);
					
					$this->blocks[$token->type][$name] = $block;
					$this->currentScope[$token->type] = $name;
					$this->currentBraces[$token->type] = $this->openBraces;
				}
			} while($token = $this->nextToken());
		}
		
		/**
		 * Finds token containing class/method modifiers for given code block.
		 * 
		 * @param CodeToken $parentToken
		 * @return mixed
		 */
		protected function findModifiers($parentToken) {
			static $ignorable = array(T_DOC_COMMENT, T_COMMENT, T_WHITESPACE);
			$possibilites = array(
				T_NAMESPACE => array(),
				T_CLASS => array_merge($ignorable, array(T_FINAL, T_ABSTRACT)),			
				T_FUNCTION => array_merge($ignorable, array(T_FINAL, T_ABSTRACT, T_PUBLIC, T_PRIVATE, T_PROTECTED)),			
			);
			
			$foundToken = null;
			
			$key = key($this->tokens);
			prev($this->tokens);
			
			while(($token = prev($this->tokens)) && in_array($token->type, $possibilites[$parentToken->type])) {
				if($token->type !== T_WHITESPACE) {
					$foundToken = $token;
				}
			}
			
			while(key($this->tokens) !== $key) {
				next($this->tokens);
			}
			return $foundToken ? $foundToken : $parentToken;
		}
		
		/**
		 * Finds name of the source code block.
		 *
		 * @return name; 
		 */
		protected function getName() {
			$firstToken = current($this->tokens);
			do {
				$token = $this->nextToken();
			} while($token && ($token->type !== T_STRING && $token->type !== T_NS_SEPARATOR));
			
			if($firstToken->type === T_NAMESPACE) {
				$name = '';
				while(in_array($token->type, array(T_NS_SEPARATOR, T_STRING))) {
					$name .= $token->content;
					$token = next($this->tokens);					
				}
				
				prev($this->tokens);
				return $name;
			}
			
			return $token ? $token->content : null;			
		}
	}
}

Return current item: Advanced Sourcecode Reflection in PHP