543 lines
14 KiB
PHP
543 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* PHP Token Reflection
|
|
*
|
|
* Version 1.3.1
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This source file is subject to the new BSD license that is bundled
|
|
* with this library in the file LICENSE.
|
|
*
|
|
* @author Ondřej Nešpor
|
|
* @author Jaroslav Hanslík
|
|
*/
|
|
|
|
namespace TokenReflection;
|
|
|
|
use TokenReflection\Broker, TokenReflection\Exception;
|
|
use RecursiveDirectoryIterator, RecursiveIteratorIterator;
|
|
|
|
// Detect if we have native traits support
|
|
define('NATIVE_TRAITS', defined('T_TRAIT'));
|
|
if (!NATIVE_TRAITS) {
|
|
define('T_TRAIT', -1);
|
|
define('T_TRAIT_C', -2);
|
|
define('T_INSTEADOF', -3);
|
|
define('T_CALLABLE', -4);
|
|
}
|
|
|
|
/**
|
|
* Reflection broker.
|
|
*
|
|
* Parses files and directories and stores their structure.
|
|
*/
|
|
class Broker
|
|
{
|
|
/**
|
|
* Turns on saving of parsed token streams.
|
|
*
|
|
* @var integer
|
|
*/
|
|
const OPTION_SAVE_TOKEN_STREAM = 0x0001;
|
|
|
|
/**
|
|
* Turns on parsing function/method body.
|
|
*
|
|
* This effectively turns on parsing of static variables in functions/methods.
|
|
*
|
|
* @var integer
|
|
*/
|
|
const OPTION_PARSE_FUNCTION_BODY = 0x0002;
|
|
|
|
/**
|
|
* Default options.
|
|
*
|
|
* @var integer
|
|
*/
|
|
const OPTION_DEFAULT = 0x0003;
|
|
|
|
/**
|
|
* Cache identifier for namespaces.
|
|
*
|
|
* @var string
|
|
*/
|
|
const CACHE_NAMESPACE = 'namespace';
|
|
|
|
/**
|
|
* Cache identifier for classes.
|
|
*
|
|
* @var string
|
|
*/
|
|
const CACHE_CLASS = 'class';
|
|
|
|
/**
|
|
* Cache identifier for constants.
|
|
*
|
|
* @var string
|
|
*/
|
|
const CACHE_CONSTANT = 'constant';
|
|
|
|
/**
|
|
* Cache identifier for functions.
|
|
*
|
|
* @var string
|
|
*/
|
|
const CACHE_FUNCTION = 'function';
|
|
|
|
/**
|
|
* Namespace/class backend.
|
|
*
|
|
* @var \TokenReflection\Broker\Backend
|
|
*/
|
|
private $backend;
|
|
|
|
/**
|
|
* Tokenized reflection objects cache.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $cache;
|
|
|
|
/**
|
|
* Broker/parser options.
|
|
*
|
|
* @var integer
|
|
*/
|
|
private $options;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param \TokenReflection\Broker\Backend $backend Broker backend instance
|
|
* @param integer $options Broker/parsing options
|
|
*/
|
|
public function __construct(Broker\Backend $backend, $options = self::OPTION_DEFAULT)
|
|
{
|
|
$this->cache = array(
|
|
self::CACHE_NAMESPACE => array(),
|
|
self::CACHE_CLASS => array(),
|
|
self::CACHE_CONSTANT => array(),
|
|
self::CACHE_FUNCTION => array()
|
|
);
|
|
|
|
$this->options = $options;
|
|
|
|
$this->backend = $backend
|
|
->setBroker($this)
|
|
->setStoringTokenStreams((bool) ($options & self::OPTION_SAVE_TOKEN_STREAM));
|
|
}
|
|
|
|
/**
|
|
* Returns broker/parser options.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getOptions()
|
|
{
|
|
return $this->options;
|
|
}
|
|
|
|
/**
|
|
* Returns if a particular option setting is set.
|
|
*
|
|
* @param integer $option Option setting
|
|
* @return boolean
|
|
*/
|
|
public function isOptionSet($option)
|
|
{
|
|
return (bool) ($this->options & $option);
|
|
}
|
|
|
|
/**
|
|
* Parses a string with the PHP source code using the given file name and returns the appropriate reflection object.
|
|
*
|
|
* @param string $source PHP source code
|
|
* @param string $fileName Used file name
|
|
* @param boolean $returnReflectionFile Returns the appropriate \TokenReflection\ReflectionFile instance(s)
|
|
* @return boolean|\TokenReflection\ReflectionFile
|
|
*/
|
|
public function processString($source, $fileName, $returnReflectionFile = false)
|
|
{
|
|
if ($this->backend->isFileProcessed($fileName)) {
|
|
$tokens = $this->backend->getFileTokens($fileName);
|
|
} else {
|
|
$tokens = new Stream\StringStream($source, $fileName);
|
|
}
|
|
|
|
$reflectionFile = new ReflectionFile($tokens, $this);
|
|
if (!$this->backend->isFileProcessed($fileName)) {
|
|
$this->backend->addFile($tokens, $reflectionFile);
|
|
|
|
// Clear the cache - leave only tokenized reflections
|
|
foreach ($this->cache as $type => $cached) {
|
|
if (!empty($cached)) {
|
|
$this->cache[$type] = array_filter($cached, function(IReflection $reflection) {
|
|
return $reflection->isTokenized();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return $returnReflectionFile ? $reflectionFile : true;
|
|
}
|
|
|
|
/**
|
|
* Parses a file and returns the appropriate reflection object.
|
|
*
|
|
* @param string $fileName Filename
|
|
* @param boolean $returnReflectionFile Returns the appropriate \TokenReflection\ReflectionFile instance(s)
|
|
* @return boolean|\TokenReflection\ReflectionFile
|
|
* @throws \TokenReflection\Exception\BrokerException If the file could not be processed.
|
|
*/
|
|
public function processFile($fileName, $returnReflectionFile = false)
|
|
{
|
|
try {
|
|
if ($this->backend->isFileProcessed($fileName)) {
|
|
$tokens = $this->backend->getFileTokens($fileName);
|
|
} else {
|
|
$tokens = new Stream\FileStream($fileName);
|
|
}
|
|
|
|
$reflectionFile = new ReflectionFile($tokens, $this);
|
|
if (!$this->backend->isFileProcessed($fileName)) {
|
|
$this->backend->addFile($tokens, $reflectionFile);
|
|
|
|
// Clear the cache - leave only tokenized reflections
|
|
foreach ($this->cache as $type => $cached) {
|
|
if (!empty($cached)) {
|
|
$this->cache[$type] = array_filter($cached, function(IReflection $reflection) {
|
|
return $reflection->isTokenized();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return $returnReflectionFile ? $reflectionFile : true;
|
|
} catch (Exception\ParseException $e) {
|
|
throw $e;
|
|
} catch (Exception\StreamException $e) {
|
|
throw new Exception\BrokerException($this, 'Could not process the file.', 0, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a PHAR archive.
|
|
*
|
|
* @param string $fileName Archive filename.
|
|
* @param boolean $returnReflectionFile Returns the appropriate \TokenReflection\ReflectionFile instance(s)
|
|
* @return boolean|array of \TokenReflection\ReflectionFile
|
|
* @throws \TokenReflection\Exception\BrokerException If the PHAR PHP extension is not loaded.
|
|
* @throws \TokenReflection\Exception\BrokerException If the given archive could not be read.
|
|
* @throws \TokenReflection\Exception\BrokerException If the given archive could not be processed.
|
|
*/
|
|
public function processPhar($fileName, $returnReflectionFile = false)
|
|
{
|
|
if (!is_file($fileName)) {
|
|
throw new Exception\BrokerException($this, 'File does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
|
|
}
|
|
|
|
if (!extension_loaded('Phar')) {
|
|
throw new Exception\BrokerException($this, 'The PHAR PHP extension is not loaded.', Exception\BrokerException::PHP_EXT_MISSING);
|
|
}
|
|
|
|
try {
|
|
$result = array();
|
|
foreach (new RecursiveIteratorIterator(new \Phar($fileName)) as $entry) {
|
|
if ($entry->isFile()) {
|
|
$result[$entry->getPathName()] = $this->processFile($entry->getPathName(), $returnReflectionFile);
|
|
}
|
|
}
|
|
|
|
return $returnReflectionFile ? $result : true;
|
|
} catch (Exception\ParseException $e) {
|
|
throw $e;
|
|
} catch (Exception\StreamException $e) {
|
|
throw new Exception\BrokerException($this, 'Could not process the archive.', 0, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes recursively a directory and returns an array of file reflection objects.
|
|
*
|
|
* @param string $path Directora path
|
|
* @param string|array $filters Filename filters
|
|
* @param boolean $returnReflectionFile Returns the appropriate \TokenReflection\ReflectionFile instance(s)
|
|
* @return boolean|array of \TokenReflection\ReflectionFile
|
|
* @throws \TokenReflection\Exception\BrokerException If the given directory does not exist.
|
|
* @throws \TokenReflection\Exception\BrokerException If the given directory could not be processed.
|
|
*/
|
|
public function processDirectory($path, $filters = array(), $returnReflectionFile = false)
|
|
{
|
|
$realPath = realpath($path);
|
|
if (!is_dir($realPath)) {
|
|
throw new Exception\BrokerException($this, 'File does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
|
|
}
|
|
|
|
try {
|
|
$result = array();
|
|
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realPath)) as $entry) {
|
|
if ($entry->isFile()) {
|
|
$process = empty($filters);
|
|
if (!$process) {
|
|
foreach ((array) $filters as $filter) {
|
|
$whitelisting = '!' !== $filter{0};
|
|
if (fnmatch($whitelisting ? $filter : substr($filter, 1), $entry->getPathName(), FNM_NOESCAPE)) {
|
|
$process = $whitelisting;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($process) {
|
|
$result[$entry->getPathName()] = $this->processFile($entry->getPathName(), $returnReflectionFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $returnReflectionFile ? $result : true;
|
|
} catch (Exception\ParseException $e) {
|
|
throw $e;
|
|
} catch (Exception\StreamException $e) {
|
|
throw new Exception\BrokerException($this, 'Could not process the directory.', 0, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a file, directory or a PHAR archive.
|
|
*
|
|
* @param string $path Path
|
|
* @param boolean $returnReflectionFile Returns the appropriate \TokenReflection\ReflectionFile instance(s)
|
|
* @return boolean|array|\TokenReflection\ReflectionFile
|
|
* @throws \TokenReflection\Exception\BrokerException If the target does not exist.
|
|
*/
|
|
public function process($path, $returnReflectionFile = false)
|
|
{
|
|
if (is_dir($path)) {
|
|
return $this->processDirectory($path, array(), $returnReflectionFile);
|
|
} elseif (is_file($path)) {
|
|
if (preg_match('~\\.phar(?:$|\\.)~i', $path)) {
|
|
return $this->processPhar($path, $returnReflectionFile);
|
|
}
|
|
|
|
return $this->processFile($path, $returnReflectionFile);
|
|
} else {
|
|
throw new Exception\BrokerException($this, 'The given directory/file does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if the broker contains a namespace of the given name.
|
|
*
|
|
* @param string $namespaceName Namespace name
|
|
* @return boolean
|
|
*/
|
|
public function hasNamespace($namespaceName)
|
|
{
|
|
return isset($this->cache[self::CACHE_NAMESPACE][$namespaceName]) || $this->backend->hasNamespace($namespaceName);
|
|
}
|
|
|
|
/**
|
|
* Returns a reflection object of the given namespace.
|
|
*
|
|
* @param string $namespaceName Namespace name
|
|
* @return \TokenReflection\ReflectionNamespace|null
|
|
*/
|
|
public function getNamespace($namespaceName)
|
|
{
|
|
$namespaceName = ltrim($namespaceName, '\\');
|
|
|
|
if (isset($this->cache[self::CACHE_NAMESPACE][$namespaceName])) {
|
|
return $this->cache[self::CACHE_NAMESPACE][$namespaceName];
|
|
}
|
|
|
|
$namespace = $this->backend->getNamespace($namespaceName);
|
|
if (null !== $namespace) {
|
|
$this->cache[self::CACHE_NAMESPACE][$namespaceName] = $namespace;
|
|
}
|
|
|
|
return $namespace;
|
|
}
|
|
|
|
/**
|
|
* Returns if the broker contains a class of the given name.
|
|
*
|
|
* @param string $className Class name
|
|
* @return boolean
|
|
*/
|
|
public function hasClass($className)
|
|
{
|
|
return isset($this->cache[self::CACHE_CLASS][$className]) || $this->backend->hasClass($className);
|
|
}
|
|
|
|
/**
|
|
* Returns a reflection object of the given class (FQN expected).
|
|
*
|
|
* @param string $className CLass bame
|
|
* @return \TokenReflection\ReflectionClass|null
|
|
*/
|
|
public function getClass($className)
|
|
{
|
|
$className = ltrim($className, '\\');
|
|
|
|
if (isset($this->cache[self::CACHE_CLASS][$className])) {
|
|
return $this->cache[self::CACHE_CLASS][$className];
|
|
}
|
|
|
|
$this->cache[self::CACHE_CLASS][$className] = $this->backend->getClass($className);
|
|
return $this->cache[self::CACHE_CLASS][$className];
|
|
}
|
|
|
|
/**
|
|
* Returns all classes from all namespaces.
|
|
*
|
|
* @param integer $types Returned class types (multiple values may be OR-ed)
|
|
* @return array
|
|
*/
|
|
public function getClasses($types = Broker\Backend::TOKENIZED_CLASSES)
|
|
{
|
|
return $this->backend->getClasses($types);
|
|
}
|
|
|
|
/**
|
|
* Returns if the broker contains a constant of the given name.
|
|
*
|
|
* @param string $constantName Constant name
|
|
* @return boolean
|
|
*/
|
|
public function hasConstant($constantName)
|
|
{
|
|
return isset($this->cache[self::CACHE_CONSTANT][$constantName]) || $this->backend->hasConstant($constantName);
|
|
}
|
|
|
|
/**
|
|
* Returns a reflection object of a constant (FQN expected).
|
|
*
|
|
* @param string $constantName Constant name
|
|
* @return \TokenReflection\ReflectionConstant|null
|
|
*/
|
|
public function getConstant($constantName)
|
|
{
|
|
$constantName = ltrim($constantName, '\\');
|
|
|
|
if (isset($this->cache[self::CACHE_CONSTANT][$constantName])) {
|
|
return $this->cache[self::CACHE_CONSTANT][$constantName];
|
|
}
|
|
|
|
if ($constant = $this->backend->getConstant($constantName)) {
|
|
$this->cache[self::CACHE_CONSTANT][$constantName] = $constant;
|
|
}
|
|
|
|
return $constant;
|
|
}
|
|
|
|
/**
|
|
* Returns all constants from all namespaces.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getConstants()
|
|
{
|
|
return $this->backend->getConstants();
|
|
}
|
|
|
|
/**
|
|
* Returns if the broker contains a function of the given name.
|
|
*
|
|
* @param string $functionName Function name
|
|
* @return boolean
|
|
*/
|
|
public function hasFunction($functionName)
|
|
{
|
|
return isset($this->cache[self::CACHE_FUNCTION][$functionName]) || $this->backend->hasFunction($functionName);
|
|
}
|
|
|
|
/**
|
|
* Returns a reflection object of a function (FQN expected).
|
|
*
|
|
* @param string $functionName Function name
|
|
* @return \TokenReflection\ReflectionFunction|null
|
|
*/
|
|
public function getFunction($functionName)
|
|
{
|
|
$functionName = ltrim($functionName, '\\');
|
|
|
|
if (isset($this->cache[self::CACHE_FUNCTION][$functionName])) {
|
|
return $this->cache[self::CACHE_FUNCTION][$functionName];
|
|
}
|
|
|
|
if ($function = $this->backend->getFunction($functionName)) {
|
|
$this->cache[self::CACHE_FUNCTION][$functionName] = $function;
|
|
}
|
|
|
|
return $function;
|
|
}
|
|
|
|
/**
|
|
* Returns all functions from all namespaces.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getFunctions()
|
|
{
|
|
return $this->backend->getFunctions();
|
|
}
|
|
|
|
/**
|
|
* Returns if the broker contains a file reflection of the given name.
|
|
*
|
|
* @param string $fileName File name
|
|
* @return boolean
|
|
*/
|
|
public function hasFile($fileName)
|
|
{
|
|
return $this->backend->hasFile($fileName);
|
|
}
|
|
|
|
/**
|
|
* Returns a reflection object of a file.
|
|
*
|
|
* @param string $fileName File name
|
|
* @return \TokenReflection\ReflectionFile|null
|
|
*/
|
|
public function getFile($fileName)
|
|
{
|
|
return $this->backend->getFile($fileName);
|
|
}
|
|
|
|
/**
|
|
* Returns all processed files reflections.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getFiles()
|
|
{
|
|
return $this->backend->getFiles();
|
|
}
|
|
|
|
/**
|
|
* Returns an array of tokens from a processed file.
|
|
*
|
|
* @param string $fileName File name
|
|
* @return \TokenReflection\Stream\StreamBase|null
|
|
*/
|
|
public function getFileTokens($fileName)
|
|
{
|
|
return $this->backend->getFileTokens($fileName);
|
|
}
|
|
|
|
/**
|
|
* Returns a real system path.
|
|
*
|
|
* @param string $path Source path
|
|
* @return string|boolean
|
|
*/
|
|
public static function getRealPath($path)
|
|
{
|
|
if (0 === strpos($path, 'phar://')) {
|
|
return is_file($path) || is_dir($path) ? $path : false;
|
|
} else {
|
|
return realpath($path);
|
|
}
|
|
}
|
|
}
|