1d53c482f98f4cc2b083e3bdfa5fa44f2339b08d
[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 /**
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 }