Mercurial > hg > cmmr2012-drupal-site
comparison vendor/symfony/class-loader/ClassCollectionLoader.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
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 if (\PHP_VERSION_ID >= 70000) { | |
15 @trigger_error('The '.__NAMESPACE__.'\ClassCollectionLoader class is deprecated since Symfony 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); | |
16 } | |
17 | |
18 /** | |
19 * ClassCollectionLoader. | |
20 * | |
21 * @author Fabien Potencier <fabien@symfony.com> | |
22 * | |
23 * @deprecated since version 3.3, to be removed in 4.0. | |
24 */ | |
25 class ClassCollectionLoader | |
26 { | |
27 private static $loaded; | |
28 private static $seen; | |
29 private static $useTokenizer = true; | |
30 | |
31 /** | |
32 * Loads a list of classes and caches them in one big file. | |
33 * | |
34 * @param array $classes An array of classes to load | |
35 * @param string $cacheDir A cache directory | |
36 * @param string $name The cache name prefix | |
37 * @param bool $autoReload Whether to flush the cache when the cache is stale or not | |
38 * @param bool $adaptive Whether to remove already declared classes or not | |
39 * @param string $extension File extension of the resulting file | |
40 * | |
41 * @throws \InvalidArgumentException When class can't be loaded | |
42 */ | |
43 public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') | |
44 { | |
45 // each $name can only be loaded once per PHP process | |
46 if (isset(self::$loaded[$name])) { | |
47 return; | |
48 } | |
49 | |
50 self::$loaded[$name] = true; | |
51 | |
52 if ($adaptive) { | |
53 $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); | |
54 | |
55 // don't include already declared classes | |
56 $classes = array_diff($classes, $declared); | |
57 | |
58 // the cache is different depending on which classes are already declared | |
59 $name = $name.'-'.substr(hash('sha256', implode('|', $classes)), 0, 5); | |
60 } | |
61 | |
62 $classes = array_unique($classes); | |
63 | |
64 // cache the core classes | |
65 if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { | |
66 throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); | |
67 } | |
68 $cacheDir = rtrim(realpath($cacheDir) ?: $cacheDir, '/'.DIRECTORY_SEPARATOR); | |
69 $cache = $cacheDir.'/'.$name.$extension; | |
70 | |
71 // auto-reload | |
72 $reload = false; | |
73 if ($autoReload) { | |
74 $metadata = $cache.'.meta'; | |
75 if (!is_file($metadata) || !is_file($cache)) { | |
76 $reload = true; | |
77 } else { | |
78 $time = filemtime($cache); | |
79 $meta = unserialize(file_get_contents($metadata)); | |
80 | |
81 sort($meta[1]); | |
82 sort($classes); | |
83 | |
84 if ($meta[1] != $classes) { | |
85 $reload = true; | |
86 } else { | |
87 foreach ($meta[0] as $resource) { | |
88 if (!is_file($resource) || filemtime($resource) > $time) { | |
89 $reload = true; | |
90 | |
91 break; | |
92 } | |
93 } | |
94 } | |
95 } | |
96 } | |
97 | |
98 if (!$reload && file_exists($cache)) { | |
99 require_once $cache; | |
100 | |
101 return; | |
102 } | |
103 if (!$adaptive) { | |
104 $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); | |
105 } | |
106 | |
107 $files = self::inline($classes, $cache, $declared); | |
108 | |
109 if ($autoReload) { | |
110 // save the resources | |
111 self::writeCacheFile($metadata, serialize(array(array_values($files), $classes))); | |
112 } | |
113 } | |
114 | |
115 /** | |
116 * Generates a file where classes and their parents are inlined. | |
117 * | |
118 * @param array $classes An array of classes to load | |
119 * @param string $cache The file where classes are inlined | |
120 * @param array $excluded An array of classes that won't be inlined | |
121 * | |
122 * @return array The source map of inlined classes, with classes as keys and files as values | |
123 * | |
124 * @throws \RuntimeException When class can't be loaded | |
125 */ | |
126 public static function inline($classes, $cache, array $excluded) | |
127 { | |
128 $declared = array(); | |
129 foreach (self::getOrderedClasses($excluded) as $class) { | |
130 $declared[$class->getName()] = true; | |
131 } | |
132 | |
133 // cache the core classes | |
134 $cacheDir = dirname($cache); | |
135 if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { | |
136 throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); | |
137 } | |
138 | |
139 $spacesRegex = '(?:\s*+(?:(?:\#|//)[^\n]*+\n|/\*(?:(?<!\*/).)++)?+)*+'; | |
140 $dontInlineRegex = <<<REGEX | |
141 '(?: | |
142 ^<\?php\s.declare.\(.strict_types.=.1.\).; | |
143 | \b__halt_compiler.\(.\) | |
144 | \b__(?:DIR|FILE)__\b | |
145 )'isx | |
146 REGEX; | |
147 $dontInlineRegex = str_replace('.', $spacesRegex, $dontInlineRegex); | |
148 | |
149 $cacheDir = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $cacheDir)); | |
150 $files = array(); | |
151 $content = ''; | |
152 foreach (self::getOrderedClasses($classes) as $class) { | |
153 if (isset($declared[$class->getName()])) { | |
154 continue; | |
155 } | |
156 $declared[$class->getName()] = true; | |
157 | |
158 $files[$class->getName()] = $file = $class->getFileName(); | |
159 $c = file_get_contents($file); | |
160 | |
161 if (preg_match($dontInlineRegex, $c)) { | |
162 $file = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $file)); | |
163 | |
164 for ($i = 0; isset($file[$i], $cacheDir[$i]); ++$i) { | |
165 if ($file[$i] !== $cacheDir[$i]) { | |
166 break; | |
167 } | |
168 } | |
169 if (1 >= $i) { | |
170 $file = var_export(implode('/', $file), true); | |
171 } else { | |
172 $file = array_slice($file, $i); | |
173 $file = str_repeat('../', count($cacheDir) - $i).implode('/', $file); | |
174 $file = '__DIR__.'.var_export('/'.$file, true); | |
175 } | |
176 | |
177 $c = "\nnamespace {require $file;}"; | |
178 } else { | |
179 $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', $c); | |
180 | |
181 // fakes namespace declaration for global code | |
182 if (!$class->inNamespace()) { | |
183 $c = "\nnamespace\n{\n".$c."\n}\n"; | |
184 } | |
185 | |
186 $c = self::fixNamespaceDeclarations('<?php '.$c); | |
187 $c = preg_replace('/^\s*<\?php/', '', $c); | |
188 } | |
189 | |
190 $content .= $c; | |
191 } | |
192 self::writeCacheFile($cache, '<?php '.$content); | |
193 | |
194 return $files; | |
195 } | |
196 | |
197 /** | |
198 * Adds brackets around each namespace if it's not already the case. | |
199 * | |
200 * @param string $source Namespace string | |
201 * | |
202 * @return string Namespaces with brackets | |
203 */ | |
204 public static function fixNamespaceDeclarations($source) | |
205 { | |
206 if (!function_exists('token_get_all') || !self::$useTokenizer) { | |
207 if (preg_match('/(^|\s)namespace(.*?)\s*;/', $source)) { | |
208 $source = preg_replace('/(^|\s)namespace(.*?)\s*;/', "$1namespace$2\n{", $source)."}\n"; | |
209 } | |
210 | |
211 return $source; | |
212 } | |
213 | |
214 $rawChunk = ''; | |
215 $output = ''; | |
216 $inNamespace = false; | |
217 $tokens = token_get_all($source); | |
218 | |
219 for ($i = 0; isset($tokens[$i]); ++$i) { | |
220 $token = $tokens[$i]; | |
221 if (!isset($token[1]) || 'b"' === $token) { | |
222 $rawChunk .= $token; | |
223 } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { | |
224 // strip comments | |
225 continue; | |
226 } elseif (T_NAMESPACE === $token[0]) { | |
227 if ($inNamespace) { | |
228 $rawChunk .= "}\n"; | |
229 } | |
230 $rawChunk .= $token[1]; | |
231 | |
232 // namespace name and whitespaces | |
233 while (isset($tokens[++$i][1]) && in_array($tokens[$i][0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) { | |
234 $rawChunk .= $tokens[$i][1]; | |
235 } | |
236 if ('{' === $tokens[$i]) { | |
237 $inNamespace = false; | |
238 --$i; | |
239 } else { | |
240 $rawChunk = rtrim($rawChunk)."\n{"; | |
241 $inNamespace = true; | |
242 } | |
243 } elseif (T_START_HEREDOC === $token[0]) { | |
244 $output .= self::compressCode($rawChunk).$token[1]; | |
245 do { | |
246 $token = $tokens[++$i]; | |
247 $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; | |
248 } while (T_END_HEREDOC !== $token[0]); | |
249 $output .= "\n"; | |
250 $rawChunk = ''; | |
251 } elseif (T_CONSTANT_ENCAPSED_STRING === $token[0]) { | |
252 $output .= self::compressCode($rawChunk).$token[1]; | |
253 $rawChunk = ''; | |
254 } else { | |
255 $rawChunk .= $token[1]; | |
256 } | |
257 } | |
258 | |
259 if ($inNamespace) { | |
260 $rawChunk .= "}\n"; | |
261 } | |
262 | |
263 $output .= self::compressCode($rawChunk); | |
264 | |
265 if (\PHP_VERSION_ID >= 70000) { | |
266 // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 | |
267 unset($tokens, $rawChunk); | |
268 gc_mem_caches(); | |
269 } | |
270 | |
271 return $output; | |
272 } | |
273 | |
274 /** | |
275 * This method is only useful for testing. | |
276 */ | |
277 public static function enableTokenizer($bool) | |
278 { | |
279 self::$useTokenizer = (bool) $bool; | |
280 } | |
281 | |
282 /** | |
283 * Strips leading & trailing ws, multiple EOL, multiple ws. | |
284 * | |
285 * @param string $code Original PHP code | |
286 * | |
287 * @return string compressed code | |
288 */ | |
289 private static function compressCode($code) | |
290 { | |
291 return preg_replace( | |
292 array('/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'), | |
293 array('', '', "\n", ' '), | |
294 $code | |
295 ); | |
296 } | |
297 | |
298 /** | |
299 * Writes a cache file. | |
300 * | |
301 * @param string $file Filename | |
302 * @param string $content Temporary file content | |
303 * | |
304 * @throws \RuntimeException when a cache file cannot be written | |
305 */ | |
306 private static function writeCacheFile($file, $content) | |
307 { | |
308 $dir = dirname($file); | |
309 if (!is_writable($dir)) { | |
310 throw new \RuntimeException(sprintf('Cache directory "%s" is not writable.', $dir)); | |
311 } | |
312 | |
313 $tmpFile = tempnam($dir, basename($file)); | |
314 | |
315 if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { | |
316 @chmod($file, 0666 & ~umask()); | |
317 | |
318 return; | |
319 } | |
320 | |
321 throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); | |
322 } | |
323 | |
324 /** | |
325 * Gets an ordered array of passed classes including all their dependencies. | |
326 * | |
327 * @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed) | |
328 * | |
329 * @throws \InvalidArgumentException When a class can't be loaded | |
330 */ | |
331 private static function getOrderedClasses(array $classes) | |
332 { | |
333 $map = array(); | |
334 self::$seen = array(); | |
335 foreach ($classes as $class) { | |
336 try { | |
337 $reflectionClass = new \ReflectionClass($class); | |
338 } catch (\ReflectionException $e) { | |
339 throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); | |
340 } | |
341 | |
342 $map = array_merge($map, self::getClassHierarchy($reflectionClass)); | |
343 } | |
344 | |
345 return $map; | |
346 } | |
347 | |
348 private static function getClassHierarchy(\ReflectionClass $class) | |
349 { | |
350 if (isset(self::$seen[$class->getName()])) { | |
351 return array(); | |
352 } | |
353 | |
354 self::$seen[$class->getName()] = true; | |
355 | |
356 $classes = array($class); | |
357 $parent = $class; | |
358 while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) { | |
359 self::$seen[$parent->getName()] = true; | |
360 | |
361 array_unshift($classes, $parent); | |
362 } | |
363 | |
364 $traits = array(); | |
365 | |
366 foreach ($classes as $c) { | |
367 foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) { | |
368 if ($trait !== $c) { | |
369 $traits[] = $trait; | |
370 } | |
371 } | |
372 } | |
373 | |
374 return array_merge(self::getInterfaces($class), $traits, $classes); | |
375 } | |
376 | |
377 private static function getInterfaces(\ReflectionClass $class) | |
378 { | |
379 $classes = array(); | |
380 | |
381 foreach ($class->getInterfaces() as $interface) { | |
382 $classes = array_merge($classes, self::getInterfaces($interface)); | |
383 } | |
384 | |
385 if ($class->isUserDefined() && $class->isInterface() && !isset(self::$seen[$class->getName()])) { | |
386 self::$seen[$class->getName()] = true; | |
387 | |
388 $classes[] = $class; | |
389 } | |
390 | |
391 return $classes; | |
392 } | |
393 | |
394 private static function computeTraitDeps(\ReflectionClass $class) | |
395 { | |
396 $traits = $class->getTraits(); | |
397 $deps = array($class->getName() => $traits); | |
398 while ($trait = array_pop($traits)) { | |
399 if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) { | |
400 self::$seen[$trait->getName()] = true; | |
401 $traitDeps = $trait->getTraits(); | |
402 $deps[$trait->getName()] = $traitDeps; | |
403 $traits = array_merge($traits, $traitDeps); | |
404 } | |
405 } | |
406 | |
407 return $deps; | |
408 } | |
409 | |
410 /** | |
411 * Dependencies resolution. | |
412 * | |
413 * This function does not check for circular dependencies as it should never | |
414 * occur with PHP traits. | |
415 * | |
416 * @param array $tree The dependency tree | |
417 * @param \ReflectionClass $node The node | |
418 * @param \ArrayObject $resolved An array of already resolved dependencies | |
419 * @param \ArrayObject $unresolved An array of dependencies to be resolved | |
420 * | |
421 * @return \ArrayObject The dependencies for the given node | |
422 * | |
423 * @throws \RuntimeException if a circular dependency is detected | |
424 */ | |
425 private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null) | |
426 { | |
427 if (null === $resolved) { | |
428 $resolved = new \ArrayObject(); | |
429 } | |
430 if (null === $unresolved) { | |
431 $unresolved = new \ArrayObject(); | |
432 } | |
433 $nodeName = $node->getName(); | |
434 | |
435 if (isset($tree[$nodeName])) { | |
436 $unresolved[$nodeName] = $node; | |
437 foreach ($tree[$nodeName] as $dependency) { | |
438 if (!$resolved->offsetExists($dependency->getName())) { | |
439 self::resolveDependencies($tree, $dependency, $resolved, $unresolved); | |
440 } | |
441 } | |
442 $resolved[$nodeName] = $node; | |
443 unset($unresolved[$nodeName]); | |
444 } | |
445 | |
446 return $resolved; | |
447 } | |
448 } |