PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php

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