Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\ClassLoader; Chris@0: Chris@14: if (\PHP_VERSION_ID >= 70000) { Chris@14: @trigger_error('The '.__NAMESPACE__.'\ClassCollectionLoader class is deprecated since Symfony 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); Chris@14: } Chris@14: Chris@0: /** Chris@0: * ClassCollectionLoader. Chris@0: * Chris@0: * @author Fabien Potencier Chris@14: * Chris@14: * @deprecated since version 3.3, to be removed in 4.0. Chris@0: */ Chris@0: class ClassCollectionLoader Chris@0: { Chris@0: private static $loaded; Chris@0: private static $seen; Chris@0: private static $useTokenizer = true; Chris@0: Chris@0: /** Chris@0: * Loads a list of classes and caches them in one big file. Chris@0: * Chris@0: * @param array $classes An array of classes to load Chris@0: * @param string $cacheDir A cache directory Chris@0: * @param string $name The cache name prefix Chris@0: * @param bool $autoReload Whether to flush the cache when the cache is stale or not Chris@0: * @param bool $adaptive Whether to remove already declared classes or not Chris@0: * @param string $extension File extension of the resulting file Chris@0: * Chris@0: * @throws \InvalidArgumentException When class can't be loaded Chris@0: */ Chris@0: public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') Chris@0: { Chris@0: // each $name can only be loaded once per PHP process Chris@0: if (isset(self::$loaded[$name])) { Chris@0: return; Chris@0: } Chris@0: Chris@0: self::$loaded[$name] = true; Chris@0: Chris@0: if ($adaptive) { Chris@0: $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); Chris@0: Chris@0: // don't include already declared classes Chris@0: $classes = array_diff($classes, $declared); Chris@0: Chris@0: // the cache is different depending on which classes are already declared Chris@17: $name .= '-'.substr(hash('sha256', implode('|', $classes)), 0, 5); Chris@0: } Chris@0: Chris@0: $classes = array_unique($classes); Chris@0: Chris@0: // cache the core classes Chris@0: if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { Chris@0: throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); Chris@0: } Chris@17: $cacheDir = rtrim(realpath($cacheDir) ?: $cacheDir, '/'.\DIRECTORY_SEPARATOR); Chris@0: $cache = $cacheDir.'/'.$name.$extension; Chris@0: Chris@0: // auto-reload Chris@0: $reload = false; Chris@0: if ($autoReload) { Chris@0: $metadata = $cache.'.meta'; Chris@0: if (!is_file($metadata) || !is_file($cache)) { Chris@0: $reload = true; Chris@0: } else { Chris@0: $time = filemtime($cache); Chris@0: $meta = unserialize(file_get_contents($metadata)); Chris@0: Chris@0: sort($meta[1]); Chris@0: sort($classes); Chris@0: Chris@0: if ($meta[1] != $classes) { Chris@0: $reload = true; Chris@0: } else { Chris@0: foreach ($meta[0] as $resource) { Chris@0: if (!is_file($resource) || filemtime($resource) > $time) { Chris@0: $reload = true; Chris@0: Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!$reload && file_exists($cache)) { Chris@0: require_once $cache; Chris@0: Chris@0: return; Chris@0: } Chris@0: if (!$adaptive) { Chris@0: $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); Chris@0: } Chris@0: Chris@0: $files = self::inline($classes, $cache, $declared); Chris@0: Chris@0: if ($autoReload) { Chris@0: // save the resources Chris@17: self::writeCacheFile($metadata, serialize([array_values($files), $classes])); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates a file where classes and their parents are inlined. Chris@0: * Chris@0: * @param array $classes An array of classes to load Chris@0: * @param string $cache The file where classes are inlined Chris@0: * @param array $excluded An array of classes that won't be inlined Chris@0: * Chris@0: * @return array The source map of inlined classes, with classes as keys and files as values Chris@0: * Chris@0: * @throws \RuntimeException When class can't be loaded Chris@0: */ Chris@0: public static function inline($classes, $cache, array $excluded) Chris@0: { Chris@17: $declared = []; Chris@0: foreach (self::getOrderedClasses($excluded) as $class) { Chris@0: $declared[$class->getName()] = true; Chris@0: } Chris@0: Chris@0: // cache the core classes Chris@17: $cacheDir = \dirname($cache); Chris@0: if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { Chris@0: throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); Chris@0: } Chris@0: Chris@0: $spacesRegex = '(?:\s*+(?:(?:\#|//)[^\n]*+\n|/\*(?:(?getName()])) { Chris@0: continue; Chris@0: } Chris@0: $declared[$class->getName()] = true; Chris@0: Chris@0: $files[$class->getName()] = $file = $class->getFileName(); Chris@0: $c = file_get_contents($file); Chris@0: Chris@0: if (preg_match($dontInlineRegex, $c)) { Chris@17: $file = explode('/', str_replace(\DIRECTORY_SEPARATOR, '/', $file)); Chris@0: Chris@0: for ($i = 0; isset($file[$i], $cacheDir[$i]); ++$i) { Chris@0: if ($file[$i] !== $cacheDir[$i]) { Chris@0: break; Chris@0: } Chris@0: } Chris@0: if (1 >= $i) { Chris@0: $file = var_export(implode('/', $file), true); Chris@0: } else { Chris@17: $file = \array_slice($file, $i); Chris@17: $file = str_repeat('../', \count($cacheDir) - $i).implode('/', $file); Chris@0: $file = '__DIR__.'.var_export('/'.$file, true); Chris@0: } Chris@0: Chris@0: $c = "\nnamespace {require $file;}"; Chris@0: } else { Chris@17: $c = preg_replace(['/^\s*<\?php/', '/\?>\s*$/'], '', $c); Chris@0: Chris@0: // fakes namespace declaration for global code Chris@0: if (!$class->inNamespace()) { Chris@0: $c = "\nnamespace\n{\n".$c."\n}\n"; Chris@0: } Chris@0: Chris@0: $c = self::fixNamespaceDeclarations('= 70000) { Chris@0: // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 Chris@0: unset($tokens, $rawChunk); Chris@0: gc_mem_caches(); Chris@0: } Chris@0: Chris@0: return $output; Chris@0: } Chris@0: Chris@0: /** Chris@0: * This method is only useful for testing. Chris@0: */ Chris@0: public static function enableTokenizer($bool) Chris@0: { Chris@0: self::$useTokenizer = (bool) $bool; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Strips leading & trailing ws, multiple EOL, multiple ws. Chris@0: * Chris@0: * @param string $code Original PHP code Chris@0: * Chris@0: * @return string compressed code Chris@0: */ Chris@0: private static function compressCode($code) Chris@0: { Chris@0: return preg_replace( Chris@17: ['/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'], Chris@17: ['', '', "\n", ' '], Chris@0: $code Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Writes a cache file. Chris@0: * Chris@0: * @param string $file Filename Chris@0: * @param string $content Temporary file content Chris@0: * Chris@0: * @throws \RuntimeException when a cache file cannot be written Chris@0: */ Chris@0: private static function writeCacheFile($file, $content) Chris@0: { Chris@17: $dir = \dirname($file); Chris@0: if (!is_writable($dir)) { Chris@0: throw new \RuntimeException(sprintf('Cache directory "%s" is not writable.', $dir)); Chris@0: } Chris@0: Chris@0: $tmpFile = tempnam($dir, basename($file)); Chris@0: Chris@0: if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { Chris@0: @chmod($file, 0666 & ~umask()); Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets an ordered array of passed classes including all their dependencies. Chris@0: * Chris@0: * @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed) Chris@0: * Chris@0: * @throws \InvalidArgumentException When a class can't be loaded Chris@0: */ Chris@0: private static function getOrderedClasses(array $classes) Chris@0: { Chris@17: $map = []; Chris@17: self::$seen = []; Chris@0: foreach ($classes as $class) { Chris@0: try { Chris@0: $reflectionClass = new \ReflectionClass($class); Chris@0: } catch (\ReflectionException $e) { Chris@0: throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); Chris@0: } Chris@0: Chris@0: $map = array_merge($map, self::getClassHierarchy($reflectionClass)); Chris@0: } Chris@0: Chris@0: return $map; Chris@0: } Chris@0: Chris@0: private static function getClassHierarchy(\ReflectionClass $class) Chris@0: { Chris@0: if (isset(self::$seen[$class->getName()])) { Chris@17: return []; Chris@0: } Chris@0: Chris@0: self::$seen[$class->getName()] = true; Chris@0: Chris@17: $classes = [$class]; Chris@0: $parent = $class; Chris@0: while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) { Chris@0: self::$seen[$parent->getName()] = true; Chris@0: Chris@0: array_unshift($classes, $parent); Chris@0: } Chris@0: Chris@17: $traits = []; Chris@0: Chris@0: foreach ($classes as $c) { Chris@0: foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) { Chris@0: if ($trait !== $c) { Chris@0: $traits[] = $trait; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return array_merge(self::getInterfaces($class), $traits, $classes); Chris@0: } Chris@0: Chris@0: private static function getInterfaces(\ReflectionClass $class) Chris@0: { Chris@17: $classes = []; Chris@0: Chris@0: foreach ($class->getInterfaces() as $interface) { Chris@0: $classes = array_merge($classes, self::getInterfaces($interface)); Chris@0: } Chris@0: Chris@0: if ($class->isUserDefined() && $class->isInterface() && !isset(self::$seen[$class->getName()])) { Chris@0: self::$seen[$class->getName()] = true; Chris@0: Chris@0: $classes[] = $class; Chris@0: } Chris@0: Chris@0: return $classes; Chris@0: } Chris@0: Chris@0: private static function computeTraitDeps(\ReflectionClass $class) Chris@0: { Chris@0: $traits = $class->getTraits(); Chris@17: $deps = [$class->getName() => $traits]; Chris@0: while ($trait = array_pop($traits)) { Chris@0: if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) { Chris@0: self::$seen[$trait->getName()] = true; Chris@0: $traitDeps = $trait->getTraits(); Chris@0: $deps[$trait->getName()] = $traitDeps; Chris@0: $traits = array_merge($traits, $traitDeps); Chris@0: } Chris@0: } Chris@0: Chris@0: return $deps; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Dependencies resolution. Chris@0: * Chris@0: * This function does not check for circular dependencies as it should never Chris@0: * occur with PHP traits. Chris@0: * Chris@0: * @param array $tree The dependency tree Chris@0: * @param \ReflectionClass $node The node Chris@0: * @param \ArrayObject $resolved An array of already resolved dependencies Chris@0: * @param \ArrayObject $unresolved An array of dependencies to be resolved Chris@0: * Chris@0: * @return \ArrayObject The dependencies for the given node Chris@0: * Chris@0: * @throws \RuntimeException if a circular dependency is detected Chris@0: */ Chris@0: private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null) Chris@0: { Chris@0: if (null === $resolved) { Chris@0: $resolved = new \ArrayObject(); Chris@0: } Chris@0: if (null === $unresolved) { Chris@0: $unresolved = new \ArrayObject(); Chris@0: } Chris@0: $nodeName = $node->getName(); Chris@0: Chris@0: if (isset($tree[$nodeName])) { Chris@0: $unresolved[$nodeName] = $node; Chris@0: foreach ($tree[$nodeName] as $dependency) { Chris@0: if (!$resolved->offsetExists($dependency->getName())) { Chris@0: self::resolveDependencies($tree, $dependency, $resolved, $unresolved); Chris@0: } Chris@0: } Chris@0: $resolved[$nodeName] = $node; Chris@0: unset($unresolved[$nodeName]); Chris@0: } Chris@0: Chris@0: return $resolved; Chris@0: } Chris@0: }