2010-02-17 13:54:36 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2011-01-15 13:29:43 +00:00
|
|
|
* This file is part of the Symfony package.
|
2010-02-17 13:54:36 +00:00
|
|
|
*
|
2011-03-06 11:40:06 +00:00
|
|
|
* (c) Fabien Potencier <fabien@symfony.com>
|
2010-02-17 13:54:36 +00:00
|
|
|
*
|
2011-01-15 13:29:43 +00:00
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
2010-02-17 13:54:36 +00:00
|
|
|
*/
|
|
|
|
|
2011-01-27 13:11:54 +00:00
|
|
|
namespace Symfony\Component\ClassLoader;
|
2011-01-15 13:29:43 +00:00
|
|
|
|
2010-02-17 13:54:36 +00:00
|
|
|
/**
|
2010-04-07 01:51:29 +01:00
|
|
|
* ClassCollectionLoader.
|
2010-02-17 13:54:36 +00:00
|
|
|
*
|
2011-03-06 11:40:06 +00:00
|
|
|
* @author Fabien Potencier <fabien@symfony.com>
|
2010-02-17 13:54:36 +00:00
|
|
|
*/
|
|
|
|
class ClassCollectionLoader
|
|
|
|
{
|
2011-03-08 19:55:22 +00:00
|
|
|
static private $loaded;
|
2010-08-11 18:35:07 +01:00
|
|
|
|
2010-05-06 12:25:53 +01:00
|
|
|
/**
|
2010-08-18 12:43:32 +01:00
|
|
|
* 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
|
2011-02-26 22:12:46 +00:00
|
|
|
* @param string $extension File extension of the resulting file
|
2010-08-18 12:43:32 +01:00
|
|
|
*
|
2010-05-06 12:25:53 +01:00
|
|
|
* @throws \InvalidArgumentException When class can't be loaded
|
|
|
|
*/
|
2011-02-26 22:12:46 +00:00
|
|
|
static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php')
|
2010-02-17 13:54:36 +00:00
|
|
|
{
|
2010-08-11 18:35:07 +01:00
|
|
|
// each $name can only be loaded once per PHP process
|
|
|
|
if (isset(self::$loaded[$name])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-08-20 12:12:06 +01:00
|
|
|
self::$loaded[$name] = true;
|
|
|
|
|
2011-01-29 16:58:40 +00:00
|
|
|
if ($adaptive) {
|
|
|
|
// don't include already declared classes
|
|
|
|
$classes = array_diff($classes, get_declared_classes(), get_declared_interfaces());
|
|
|
|
|
|
|
|
// the cache is different depending on which classes are already declared
|
|
|
|
$name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5);
|
|
|
|
}
|
|
|
|
|
2011-02-26 22:12:46 +00:00
|
|
|
$cache = $cacheDir.'/'.$name.$extension;
|
2010-05-06 12:25:53 +01:00
|
|
|
|
|
|
|
// auto-reload
|
|
|
|
$reload = false;
|
2010-05-07 15:09:11 +01:00
|
|
|
if ($autoReload) {
|
2011-03-14 17:46:05 +00:00
|
|
|
$metadata = $cacheDir.'/'.$name.$extension.'.meta';
|
2010-05-07 15:09:11 +01:00
|
|
|
if (!file_exists($metadata) || !file_exists($cache)) {
|
2010-05-06 12:25:53 +01:00
|
|
|
$reload = true;
|
2010-05-07 15:09:11 +01:00
|
|
|
} else {
|
2010-05-06 12:25:53 +01:00
|
|
|
$time = filemtime($cache);
|
|
|
|
$meta = unserialize(file_get_contents($metadata));
|
|
|
|
|
2010-05-07 15:09:11 +01:00
|
|
|
if ($meta[1] != $classes) {
|
2010-05-06 12:25:53 +01:00
|
|
|
$reload = true;
|
2010-05-07 15:09:11 +01:00
|
|
|
} else {
|
2010-05-08 14:32:30 +01:00
|
|
|
foreach ($meta[0] as $resource) {
|
2010-05-07 15:09:11 +01:00
|
|
|
if (!file_exists($resource) || filemtime($resource) > $time) {
|
2010-05-06 12:25:53 +01:00
|
|
|
$reload = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-07 15:09:11 +01:00
|
|
|
if (!$reload && file_exists($cache)) {
|
2010-05-06 12:25:53 +01:00
|
|
|
require_once $cache;
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-05-06 12:25:53 +01:00
|
|
|
return;
|
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-05-06 12:25:53 +01:00
|
|
|
$files = array();
|
|
|
|
$content = '';
|
2010-05-07 15:09:11 +01:00
|
|
|
foreach ($classes as $class) {
|
2011-09-25 12:13:28 +01:00
|
|
|
if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
|
2010-05-06 12:25:53 +01:00
|
|
|
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
|
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-05-06 12:25:53 +01:00
|
|
|
$r = new \ReflectionClass($class);
|
|
|
|
$files[] = $r->getFileName();
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2011-01-16 09:16:20 +00:00
|
|
|
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName()));
|
|
|
|
|
|
|
|
// add namespace declaration for global code
|
|
|
|
if (!$r->inNamespace()) {
|
2011-06-17 13:14:54 +01:00
|
|
|
$c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n";
|
2011-01-16 09:16:20 +00:00
|
|
|
} else {
|
|
|
|
$c = self::fixNamespaceDeclarations('<?php '.$c);
|
|
|
|
$c = preg_replace('/^\s*<\?php/', '', $c);
|
|
|
|
}
|
|
|
|
|
|
|
|
$content .= $c;
|
2010-05-06 12:25:53 +01:00
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-05-06 12:25:53 +01:00
|
|
|
// cache the core classes
|
2010-05-07 15:09:11 +01:00
|
|
|
if (!is_dir(dirname($cache))) {
|
2010-05-06 12:25:53 +01:00
|
|
|
mkdir(dirname($cache), 0777, true);
|
|
|
|
}
|
2011-06-17 13:14:54 +01:00
|
|
|
self::writeCacheFile($cache, '<?php '.$content);
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-05-07 15:09:11 +01:00
|
|
|
if ($autoReload) {
|
2010-05-06 12:25:53 +01:00
|
|
|
// save the resources
|
|
|
|
self::writeCacheFile($metadata, serialize(array($files, $classes)));
|
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
}
|
|
|
|
|
2011-01-16 09:16:20 +00:00
|
|
|
/**
|
|
|
|
* Adds brackets around each namespace if it's not already the case.
|
2011-02-06 21:31:19 +00:00
|
|
|
*
|
2011-02-06 22:27:05 +00:00
|
|
|
* @param string $source Namespace string
|
2011-02-06 21:31:19 +00:00
|
|
|
*
|
|
|
|
* @return string Namespaces with brackets
|
2011-01-16 09:16:20 +00:00
|
|
|
*/
|
|
|
|
static public function fixNamespaceDeclarations($source)
|
|
|
|
{
|
|
|
|
if (!function_exists('token_get_all')) {
|
|
|
|
return $source;
|
|
|
|
}
|
|
|
|
|
|
|
|
$output = '';
|
|
|
|
$inNamespace = false;
|
|
|
|
$tokens = token_get_all($source);
|
|
|
|
|
2011-06-17 13:11:55 +01:00
|
|
|
for ($i = 0, $max = count($tokens); $i < $max; $i++) {
|
|
|
|
$token = $tokens[$i];
|
2011-01-16 09:16:20 +00:00
|
|
|
if (is_string($token)) {
|
|
|
|
$output .= $token;
|
2011-06-17 13:14:54 +01:00
|
|
|
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
|
|
|
|
// strip comments
|
|
|
|
continue;
|
2011-01-16 09:16:20 +00:00
|
|
|
} elseif (T_NAMESPACE === $token[0]) {
|
|
|
|
if ($inNamespace) {
|
|
|
|
$output .= "}\n";
|
|
|
|
}
|
|
|
|
$output .= $token[1];
|
|
|
|
|
|
|
|
// namespace name and whitespaces
|
2011-06-17 13:11:55 +01:00
|
|
|
while (($t = $tokens[++$i]) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) {
|
2011-01-16 09:16:20 +00:00
|
|
|
$output .= $t[1];
|
|
|
|
}
|
|
|
|
if (is_string($t) && '{' === $t) {
|
|
|
|
$inNamespace = false;
|
2011-06-17 13:11:55 +01:00
|
|
|
--$i;
|
2011-01-16 09:16:20 +00:00
|
|
|
} else {
|
|
|
|
$output .= "\n{";
|
|
|
|
$inNamespace = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$output .= $token[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($inNamespace) {
|
|
|
|
$output .= "}\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2011-02-06 21:31:19 +00:00
|
|
|
/**
|
2011-02-06 23:58:54 +00:00
|
|
|
* Writes a cache file.
|
2011-02-06 21:31:19 +00:00
|
|
|
*
|
2011-02-06 22:27:05 +00:00
|
|
|
* @param string $file Filename
|
|
|
|
* @param string $content Temporary file content
|
2011-02-06 21:31:19 +00:00
|
|
|
*
|
|
|
|
* @throws \RuntimeException when a cache file cannot be written
|
|
|
|
*/
|
2011-03-08 19:55:22 +00:00
|
|
|
static private function writeCacheFile($file, $content)
|
2010-02-17 13:54:36 +00:00
|
|
|
{
|
2010-05-06 12:25:53 +01:00
|
|
|
$tmpFile = tempnam(dirname($file), basename($file));
|
2010-08-12 20:58:19 +01:00
|
|
|
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
|
|
|
|
chmod($file, 0644);
|
2010-02-17 13:54:36 +00:00
|
|
|
|
2010-08-12 20:58:19 +01:00
|
|
|
return;
|
2010-06-23 06:35:46 +01:00
|
|
|
}
|
|
|
|
|
2010-08-12 20:58:19 +01:00
|
|
|
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
|
2010-05-06 12:25:53 +01:00
|
|
|
}
|
2011-01-27 13:11:54 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes comments from a PHP source string.
|
|
|
|
*
|
|
|
|
* We don't use the PHP php_strip_whitespace() function
|
|
|
|
* as we want the content to be readable and well-formatted.
|
|
|
|
*
|
|
|
|
* @param string $source A PHP string
|
|
|
|
*
|
|
|
|
* @return string The PHP string with the comments removed
|
|
|
|
*/
|
2011-03-08 19:55:22 +00:00
|
|
|
static private function stripComments($source)
|
2011-01-27 13:11:54 +00:00
|
|
|
{
|
|
|
|
if (!function_exists('token_get_all')) {
|
|
|
|
return $source;
|
|
|
|
}
|
|
|
|
|
|
|
|
$output = '';
|
|
|
|
foreach (token_get_all($source) as $token) {
|
|
|
|
if (is_string($token)) {
|
|
|
|
$output .= $token;
|
|
|
|
} elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
|
|
|
|
$output .= $token[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace multiple new lines with a single newline
|
|
|
|
$output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output);
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
2010-02-17 13:54:36 +00:00
|
|
|
}
|