3fd75c24544dbe266e4737403a4412190e19ef65
[yaffs-website] / vendor / symfony / class-loader / ClassCollectionLoader.php
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 .= '-'.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 }