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