|
| 1 | +<?php |
| 2 | + |
| 3 | +/* |
| 4 | + * This file is part of the Symfony package. |
| 5 | + * |
| 6 | + * (c) Fabien Potencier <fabien@symfony.com> |
| 7 | + * |
| 8 | + * For the full copyright and license information, please view the LICENSE |
| 9 | + * file that was distributed with this source code. |
| 10 | + */ |
| 11 | + |
| 12 | +namespace Symfony\Component\ClassLoader; |
| 13 | + |
| 14 | +/** |
| 15 | + * ClassCollectionLoader. |
| 16 | + * |
| 17 | + * @author Fabien Potencier <fabien@symfony.com> |
| 18 | + */ |
| 19 | +class ClassCollectionLoader |
| 20 | +{ |
| 21 | + static private $loaded; |
| 22 | + |
| 23 | + /** |
| 24 | + * Loads a list of classes and caches them in one big file. |
| 25 | + * |
| 26 | + * @param array $classes An array of classes to load |
| 27 | + * @param string $cacheDir A cache directory |
| 28 | + * @param string $name The cache name prefix |
| 29 | + * @param Boolean $autoReload Whether to flush the cache when the cache is stale or not |
| 30 | + * @param Boolean $adaptive Whether to remove already declared classes or not |
| 31 | + * @param string $extension File extension of the resulting file |
| 32 | + * |
| 33 | + * @throws \InvalidArgumentException When class can't be loaded |
| 34 | + */ |
| 35 | + static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') |
| 36 | + { |
| 37 | + // each $name can only be loaded once per PHP process |
| 38 | + if (isset(self::$loaded[$name])) { |
| 39 | + return; |
| 40 | + } |
| 41 | + |
| 42 | + self::$loaded[$name] = true; |
| 43 | + |
| 44 | + if ($adaptive) { |
| 45 | + // don't include already declared classes |
| 46 | + $classes = array_diff($classes, get_declared_classes(), get_declared_interfaces()); |
| 47 | + |
| 48 | + // the cache is different depending on which classes are already declared |
| 49 | + $name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5); |
| 50 | + } |
| 51 | + |
| 52 | + $cache = $cacheDir.'/'.$name.$extension; |
| 53 | + |
| 54 | + // auto-reload |
| 55 | + $reload = false; |
| 56 | + if ($autoReload) { |
| 57 | + $metadata = $cacheDir.'/'.$name.$extension.'.meta'; |
| 58 | + if (!is_file($metadata) || !is_file($cache)) { |
| 59 | + $reload = true; |
| 60 | + } else { |
| 61 | + $time = filemtime($cache); |
| 62 | + $meta = unserialize(file_get_contents($metadata)); |
| 63 | + |
| 64 | + if ($meta[1] != $classes) { |
| 65 | + $reload = true; |
| 66 | + } else { |
| 67 | + foreach ($meta[0] as $resource) { |
| 68 | + if (!is_file($resource) || filemtime($resource) > $time) { |
| 69 | + $reload = true; |
| 70 | + |
| 71 | + break; |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + if (!$reload && is_file($cache)) { |
| 79 | + require_once $cache; |
| 80 | + |
| 81 | + return; |
| 82 | + } |
| 83 | + |
| 84 | + $files = array(); |
| 85 | + $content = ''; |
| 86 | + foreach ($classes as $class) { |
| 87 | + if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) { |
| 88 | + throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); |
| 89 | + } |
| 90 | + |
| 91 | + $r = new \ReflectionClass($class); |
| 92 | + $files[] = $r->getFileName(); |
| 93 | + |
| 94 | + $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName())); |
| 95 | + |
| 96 | + // add namespace declaration for global code |
| 97 | + if (!$r->inNamespace()) { |
| 98 | + $c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n"; |
| 99 | + } else { |
| 100 | + $c = self::fixNamespaceDeclarations('<?php '.$c); |
| 101 | + $c = preg_replace('/^\s*<\?php/', '', $c); |
| 102 | + } |
| 103 | + |
| 104 | + $content .= $c; |
| 105 | + } |
| 106 | + |
| 107 | + // cache the core classes |
| 108 | + if (!is_dir(dirname($cache))) { |
| 109 | + mkdir(dirname($cache), 0777, true); |
| 110 | + } |
| 111 | + self::writeCacheFile($cache, '<?php '.$content); |
| 112 | + |
| 113 | + if ($autoReload) { |
| 114 | + // save the resources |
| 115 | + self::writeCacheFile($metadata, serialize(array($files, $classes))); |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * Adds brackets around each namespace if it's not already the case. |
| 121 | + * |
| 122 | + * @param string $source Namespace string |
| 123 | + * |
| 124 | + * @return string Namespaces with brackets |
| 125 | + */ |
| 126 | + static public function fixNamespaceDeclarations($source) |
| 127 | + { |
| 128 | + if (!function_exists('token_get_all')) { |
| 129 | + return $source; |
| 130 | + } |
| 131 | + |
| 132 | + $output = ''; |
| 133 | + $inNamespace = false; |
| 134 | + $tokens = token_get_all($source); |
| 135 | + |
| 136 | + for ($i = 0, $max = count($tokens); $i < $max; $i++) { |
| 137 | + $token = $tokens[$i]; |
| 138 | + if (is_string($token)) { |
| 139 | + $output .= $token; |
| 140 | + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { |
| 141 | + // strip comments |
| 142 | + continue; |
| 143 | + } elseif (T_NAMESPACE === $token[0]) { |
| 144 | + if ($inNamespace) { |
| 145 | + $output .= "}\n"; |
| 146 | + } |
| 147 | + $output .= $token[1]; |
| 148 | + |
| 149 | + // namespace name and whitespaces |
| 150 | + while (($t = $tokens[++$i]) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) { |
| 151 | + $output .= $t[1]; |
| 152 | + } |
| 153 | + if (is_string($t) && '{' === $t) { |
| 154 | + $inNamespace = false; |
| 155 | + --$i; |
| 156 | + } else { |
| 157 | + $output .= "\n{"; |
| 158 | + $inNamespace = true; |
| 159 | + } |
| 160 | + } else { |
| 161 | + $output .= $token[1]; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + if ($inNamespace) { |
| 166 | + $output .= "}\n"; |
| 167 | + } |
| 168 | + |
| 169 | + return $output; |
| 170 | + } |
| 171 | + |
| 172 | + /** |
| 173 | + * Writes a cache file. |
| 174 | + * |
| 175 | + * @param string $file Filename |
| 176 | + * @param string $content Temporary file content |
| 177 | + * |
| 178 | + * @throws \RuntimeException when a cache file cannot be written |
| 179 | + */ |
| 180 | + static private function writeCacheFile($file, $content) |
| 181 | + { |
| 182 | + $tmpFile = tempnam(dirname($file), basename($file)); |
| 183 | + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { |
| 184 | + chmod($file, 0644); |
| 185 | + |
| 186 | + return; |
| 187 | + } |
| 188 | + |
| 189 | + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); |
| 190 | + } |
| 191 | + |
| 192 | + /** |
| 193 | + * Removes comments from a PHP source string. |
| 194 | + * |
| 195 | + * We don't use the PHP php_strip_whitespace() function |
| 196 | + * as we want the content to be readable and well-formatted. |
| 197 | + * |
| 198 | + * @param string $source A PHP string |
| 199 | + * |
| 200 | + * @return string The PHP string with the comments removed |
| 201 | + */ |
| 202 | + static private function stripComments($source) |
| 203 | + { |
| 204 | + if (!function_exists('token_get_all')) { |
| 205 | + return $source; |
| 206 | + } |
| 207 | + |
| 208 | + $output = ''; |
| 209 | + foreach (token_get_all($source) as $token) { |
| 210 | + if (is_string($token)) { |
| 211 | + $output .= $token; |
| 212 | + } elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { |
| 213 | + $output .= $token[1]; |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + // replace multiple new lines with a single newline |
| 218 | + $output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output); |
| 219 | + |
| 220 | + return $output; |
| 221 | + } |
| 222 | +} |
0 commit comments