Autoload.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: yunwuxin <448901948@qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace think\console\command\optimize;
  12. use think\console\Command;
  13. use think\console\Input;
  14. use think\console\Output;
  15. use think\Container;
  16. class Autoload extends Command
  17. {
  18. protected function configure()
  19. {
  20. $this->setName('optimize:autoload')
  21. ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.');
  22. }
  23. protected function execute(Input $input, Output $output)
  24. {
  25. $classmapFile = <<<EOF
  26. <?php
  27. /**
  28. * 类库映射
  29. */
  30. return [
  31. EOF;
  32. $app = Container::get('app');
  33. $namespacesToScan = [
  34. $app->getNamespace() . '\\' => realpath(rtrim($app->getAppPath())),
  35. 'think\\' => $app->getThinkPath() . 'library/think',
  36. 'traits\\' => $app->getThinkPath() . 'library/traits',
  37. '' => realpath(rtrim($app->getRootPath() . 'extend')),
  38. ];
  39. krsort($namespacesToScan);
  40. $classMap = [];
  41. foreach ($namespacesToScan as $namespace => $dir) {
  42. if (!is_dir($dir)) {
  43. continue;
  44. }
  45. $namespaceFilter = '' === $namespace ? null : $namespace;
  46. $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap);
  47. }
  48. ksort($classMap);
  49. foreach ($classMap as $class => $code) {
  50. $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code;
  51. }
  52. $classmapFile .= "];\n";
  53. $runtimePath = $app->getRuntimePath();
  54. if (!is_dir($runtimePath)) {
  55. @mkdir($runtimePath, 0755, true);
  56. }
  57. file_put_contents($runtimePath . 'classmap.php', $classmapFile);
  58. $output->writeln('<info>Succeed!</info>');
  59. }
  60. protected function addClassMapCode($dir, $namespace, $classMap)
  61. {
  62. foreach ($this->createMap($dir, $namespace) as $class => $path) {
  63. $pathCode = $this->getPathCode($path) . ",\n";
  64. if (!isset($classMap[$class])) {
  65. $classMap[$class] = $pathCode;
  66. } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) {
  67. $this->output->writeln(
  68. '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
  69. ' was found in both "' . str_replace(["',\n"], [
  70. '',
  71. ], $classMap[$class]) . '" and "' . $path . '", the first will be used.</warning>'
  72. );
  73. }
  74. }
  75. return $classMap;
  76. }
  77. protected function getPathCode($path)
  78. {
  79. $baseDir = '';
  80. $app = Container::get('app');
  81. $appPath = $this->normalizePath(realpath($app->getAppPath()));
  82. $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library'));
  83. $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend'));
  84. $path = $this->normalizePath($path);
  85. if (strpos($path, $libPath . '/') === 0) {
  86. $path = substr($path, strlen($app->getThinkPath() . 'library'));
  87. $baseDir = "'" . $libPath . "/'";
  88. } elseif (strpos($path, $appPath . '/') === 0) {
  89. $path = substr($path, strlen($appPath) + 1);
  90. $baseDir = "'" . $appPath . "/'";
  91. } elseif (strpos($path, $extendPath . '/') === 0) {
  92. $path = substr($path, strlen($extendPath) + 1);
  93. $baseDir = "'" . $extendPath . "/'";
  94. }
  95. if (false !== $path) {
  96. $baseDir .= " . ";
  97. }
  98. return $baseDir . ((false !== $path) ? var_export($path, true) : "");
  99. }
  100. protected function normalizePath($path)
  101. {
  102. $parts = [];
  103. $path = strtr($path, '\\', '/');
  104. $prefix = '';
  105. $absolute = false;
  106. if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) {
  107. $prefix = $match[1];
  108. $path = substr($path, strlen($prefix));
  109. }
  110. if (substr($path, 0, 1) === '/') {
  111. $absolute = true;
  112. $path = substr($path, 1);
  113. }
  114. $up = false;
  115. foreach (explode('/', $path) as $chunk) {
  116. if ('..' === $chunk && ($absolute || $up)) {
  117. array_pop($parts);
  118. $up = !(empty($parts) || '..' === end($parts));
  119. } elseif ('.' !== $chunk && '' !== $chunk) {
  120. $parts[] = $chunk;
  121. $up = '..' !== $chunk;
  122. }
  123. }
  124. return $prefix . ($absolute ? '/' : '') . implode('/', $parts);
  125. }
  126. protected function createMap($path, $namespace = null)
  127. {
  128. if (is_string($path)) {
  129. if (is_file($path)) {
  130. $path = [new \SplFileInfo($path)];
  131. } elseif (is_dir($path)) {
  132. $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST);
  133. $path = [];
  134. /** @var \SplFileInfo $object */
  135. foreach ($objects as $object) {
  136. if ($object->isFile() && $object->getExtension() == 'php') {
  137. $path[] = $object;
  138. }
  139. }
  140. } else {
  141. throw new \RuntimeException(
  142. 'Could not scan for classes inside "' . $path .
  143. '" which does not appear to be a file nor a folder'
  144. );
  145. }
  146. }
  147. $map = [];
  148. /** @var \SplFileInfo $file */
  149. foreach ($path as $file) {
  150. $filePath = $file->getRealPath();
  151. if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') {
  152. continue;
  153. }
  154. $classes = $this->findClasses($filePath);
  155. foreach ($classes as $class) {
  156. if (null !== $namespace && 0 !== strpos($class, $namespace)) {
  157. continue;
  158. }
  159. if (!isset($map[$class])) {
  160. $map[$class] = $filePath;
  161. } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) {
  162. $this->output->writeln(
  163. '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
  164. ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'
  165. );
  166. }
  167. }
  168. }
  169. return $map;
  170. }
  171. protected function findClasses($path)
  172. {
  173. $extraTypes = '|trait';
  174. $contents = @php_strip_whitespace($path);
  175. if (!$contents) {
  176. if (!file_exists($path)) {
  177. $message = 'File at "%s" does not exist, check your classmap definitions';
  178. } elseif (!is_readable($path)) {
  179. $message = 'File at "%s" is not readable, check its permissions';
  180. } elseif ('' === trim(file_get_contents($path))) {
  181. return [];
  182. } else {
  183. $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
  184. }
  185. $error = error_get_last();
  186. if (isset($error['message'])) {
  187. $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
  188. }
  189. throw new \RuntimeException(sprintf($message, $path));
  190. }
  191. if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) {
  192. return [];
  193. }
  194. // strip heredocs/nowdocs
  195. $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
  196. // strip strings
  197. $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
  198. // strip leading non-php code if needed
  199. if (substr($contents, 0, 2) !== '<?') {
  200. $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
  201. if (0 === $replacements) {
  202. return [];
  203. }
  204. }
  205. // strip non-php blocks in the file
  206. $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
  207. // strip trailing non-php code if needed
  208. $pos = strrpos($contents, '?>');
  209. if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
  210. $contents = substr($contents, 0, $pos);
  211. }
  212. preg_match_all('{
  213. (?:
  214. \b(?<![\$:>])(?P<type>class|interface' . $extraTypes . ') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
  215. | \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
  216. )
  217. }ix', $contents, $matches);
  218. $classes = [];
  219. $namespace = '';
  220. for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
  221. if (!empty($matches['ns'][$i])) {
  222. $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\';
  223. } else {
  224. $name = $matches['name'][$i];
  225. if (':' === $name[0]) {
  226. $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
  227. } elseif ('enum' === $matches['type'][$i]) {
  228. $name = rtrim($name, ':');
  229. }
  230. $classes[] = ltrim($namespace . $name, '\\');
  231. }
  232. }
  233. return $classes;
  234. }
  235. }