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