<?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$
*/
// 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;
}
}