Autoload.php 10 KB

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