ClassCollectionLoader.
@author Fabien Potencier <fabien@symfony.com>
Expanded class hierarchy of ClassCollectionLoader
class ClassCollectionLoader {
private static $loaded;
private static $seen;
private static $useTokenizer = true;
/**
* Loads a list of classes and caches them in one big file.
*
* @param array $classes An array of classes to load
* @param string $cacheDir A cache directory
* @param string $name The cache name prefix
* @param Boolean $autoReload Whether to flush the cache when the cache is stale or not
* @param Boolean $adaptive Whether to remove already declared classes or not
* @param string $extension File extension of the resulting file
*
* @throws \InvalidArgumentException When class can't be loaded
*/
public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') {
// each $name can only be loaded once per PHP process
if (isset(self::$loaded[$name])) {
return;
}
self::$loaded[$name] = true;
$declared = array_merge(get_declared_classes(), get_declared_interfaces());
if (function_exists('get_declared_traits')) {
$declared = array_merge($declared, get_declared_traits());
}
if ($adaptive) {
// don't include already declared classes
$classes = array_diff($classes, $declared);
// the cache is different depending on which classes are already declared
$name = $name . '-' . substr(md5(implode('|', $classes)), 0, 5);
}
$classes = array_unique($classes);
$cache = $cacheDir . '/' . $name . $extension;
// auto-reload
$reload = false;
if ($autoReload) {
$metadata = $cache . '.meta';
if (!is_file($metadata) || !is_file($cache)) {
$reload = true;
}
else {
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));
sort($meta[1]);
sort($classes);
if ($meta[1] != $classes) {
$reload = true;
}
else {
foreach ($meta[0] as $resource) {
if (!is_file($resource) || filemtime($resource) > $time) {
$reload = true;
break;
}
}
}
}
}
if (!$reload && is_file($cache)) {
require_once $cache;
return;
}
$files = array();
$content = '';
foreach (self::getOrderedClasses($classes) as $class) {
if (in_array($class
->getName(), $declared)) {
continue;
}
$files[] = $class
->getFileName();
$c = preg_replace(array(
'/^\\s*<\\?php/',
'/\\?>\\s*$/',
), '', file_get_contents($class
->getFileName()));
// fakes namespace declaration for global code
if (!$class
->inNamespace()) {
$c = "\nnamespace\n{\n" . $c . "\n}\n";
}
$c = self::fixNamespaceDeclarations('<?php ' . $c);
$c = preg_replace('/^\\s*<\\?php/', '', $c);
$content .= $c;
}
// cache the core classes
if (!is_dir(dirname($cache))) {
mkdir(dirname($cache), 0777, true);
}
self::writeCacheFile($cache, '<?php ' . $content);
if ($autoReload) {
// save the resources
self::writeCacheFile($metadata, serialize(array(
$files,
$classes,
)));
}
}
/**
* Adds brackets around each namespace if it's not already the case.
*
* @param string $source Namespace string
*
* @return string Namespaces with brackets
*/
public static function fixNamespaceDeclarations($source) {
if (!function_exists('token_get_all') || !self::$useTokenizer) {
if (preg_match('/namespace(.*?)\\s*;/', $source)) {
$source = preg_replace('/namespace(.*?)\\s*;/', "namespace\$1\n{", $source) . "}\n";
}
return $source;
}
$rawChunk = '';
$output = '';
$inNamespace = false;
$tokens = token_get_all($source);
for (reset($tokens); false !== ($token = current($tokens)); next($tokens)) {
if (is_string($token)) {
$rawChunk .= $token;
}
elseif (in_array($token[0], array(
T_COMMENT,
T_DOC_COMMENT,
))) {
// strip comments
continue;
}
elseif (T_NAMESPACE === $token[0]) {
if ($inNamespace) {
$rawChunk .= "}\n";
}
$rawChunk .= $token[1];
// namespace name and whitespaces
while (($t = next($tokens)) && is_array($t) && in_array($t[0], array(
T_WHITESPACE,
T_NS_SEPARATOR,
T_STRING,
))) {
$rawChunk .= $t[1];
}
if ('{' === $t) {
$inNamespace = false;
prev($tokens);
}
else {
$rawChunk = rtrim($rawChunk) . "\n{";
$inNamespace = true;
}
}
elseif (T_START_HEREDOC === $token[0]) {
$output .= self::compressCode($rawChunk) . $token[1];
do {
$token = next($tokens);
$output .= is_string($token) ? $token : $token[1];
} while ($token[0] !== T_END_HEREDOC);
$output .= "\n";
$rawChunk = '';
}
elseif (T_CONSTANT_ENCAPSED_STRING === $token[0]) {
$output .= self::compressCode($rawChunk) . $token[1];
$rawChunk = '';
}
else {
$rawChunk .= $token[1];
}
}
if ($inNamespace) {
$rawChunk .= "}\n";
}
return $output . self::compressCode($rawChunk);
}
/**
* This method is only useful for testing.
*/
public static function enableTokenizer($bool) {
self::$useTokenizer = (bool) $bool;
}
/**
* Strips leading & trailing ws, multiple EOL, multiple ws.
*
* @param string $code Original PHP code
*
* @return string compressed code
*/
private static function compressCode($code) {
return preg_replace(array(
'/^\\s+/m',
'/\\s+$/m',
'/([\\n\\r]+ *[\\n\\r]+)+/',
'/[ \\t]+/',
), array(
'',
'',
"\n",
' ',
), $code);
}
/**
* Writes a cache file.
*
* @param string $file Filename
* @param string $content Temporary file content
*
* @throws \RuntimeException when a cache file cannot be written
*/
private static function writeCacheFile($file, $content) {
$tmpFile = tempnam(dirname($file), basename($file));
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
@chmod($file, 0666 & ~umask());
return;
}
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
}
/**
* Gets an ordered array of passed classes including all their dependencies.
*
* @param array $classes
*
* @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed)
*
* @throws \InvalidArgumentException When a class can't be loaded
*/
private static function getOrderedClasses(array $classes) {
$map = array();
self::$seen = array();
foreach ($classes as $class) {
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
}
$map = array_merge($map, self::getClassHierarchy($reflectionClass));
}
return $map;
}
private static function getClassHierarchy(\ReflectionClass $class) {
if (isset(self::$seen[$class
->getName()])) {
return array();
}
self::$seen[$class
->getName()] = true;
$classes = array(
$class,
);
$parent = $class;
while (($parent = $parent
->getParentClass()) && $parent
->isUserDefined() && !isset(self::$seen[$parent
->getName()])) {
self::$seen[$parent
->getName()] = true;
array_unshift($classes, $parent);
}
$traits = array();
if (function_exists('get_declared_traits')) {
foreach ($classes as $c) {
foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) {
if ($trait !== $c) {
$traits[] = $trait;
}
}
}
}
return array_merge(self::getInterfaces($class), $traits, $classes);
}
private static function getInterfaces(\ReflectionClass $class) {
$classes = array();
foreach ($class
->getInterfaces() as $interface) {
$classes = array_merge($classes, self::getInterfaces($interface));
}
if ($class
->isUserDefined() && $class
->isInterface() && !isset(self::$seen[$class
->getName()])) {
self::$seen[$class
->getName()] = true;
$classes[] = $class;
}
return $classes;
}
private static function computeTraitDeps(\ReflectionClass $class) {
$traits = $class
->getTraits();
$deps = array(
$class
->getName() => $traits,
);
while ($trait = array_pop($traits)) {
if ($trait
->isUserDefined() && !isset(self::$seen[$trait
->getName()])) {
self::$seen[$trait
->getName()] = true;
$traitDeps = $trait
->getTraits();
$deps[$trait
->getName()] = $traitDeps;
$traits = array_merge($traits, $traitDeps);
}
}
return $deps;
}
/**
* Dependencies resolution.
*
* This function does not check for circular dependencies as it should never
* occur with PHP traits.
*
* @param array $tree The dependency tree
* @param \ReflectionClass $node The node
* @param \ArrayObject $resolved An array of already resolved dependencies
* @param \ArrayObject $unresolved An array of dependencies to be resolved
*
* @return \ArrayObject The dependencies for the given node
*
* @throws \RuntimeException if a circular dependency is detected
*/
private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null) {
if (null === $resolved) {
$resolved = new \ArrayObject();
}
if (null === $unresolved) {
$unresolved = new \ArrayObject();
}
$nodeName = $node
->getName();
$unresolved[$nodeName] = $node;
foreach ($tree[$nodeName] as $dependency) {
if (!$resolved
->offsetExists($dependency
->getName())) {
self::resolveDependencies($tree, $dependency, $resolved, $unresolved);
}
}
$resolved[$nodeName] = $node;
unset($unresolved[$nodeName]);
return $resolved;
}
}
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ClassCollectionLoader:: |
private static | property | ||
ClassCollectionLoader:: |
private static | property | ||
ClassCollectionLoader:: |
private static | property | ||
ClassCollectionLoader:: |
private static | function | Strips leading & trailing ws, multiple EOL, multiple ws. | |
ClassCollectionLoader:: |
private static | function | ||
ClassCollectionLoader:: |
public static | function | This method is only useful for testing. | |
ClassCollectionLoader:: |
public static | function | Adds brackets around each namespace if it's not already the case. | |
ClassCollectionLoader:: |
private static | function | ||
ClassCollectionLoader:: |
private static | function | ||
ClassCollectionLoader:: |
private static | function | Gets an ordered array of passed classes including all their dependencies. | |
ClassCollectionLoader:: |
public static | function | Loads a list of classes and caches them in one big file. | |
ClassCollectionLoader:: |
private static | function | Dependencies resolution. | |
ClassCollectionLoader:: |
private static | function | Writes a cache file. |