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 }