Autoload.php 9.9 KB

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