123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <?php
- // +----------------------------------------------------------------------
- // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
- // +----------------------------------------------------------------------
- // | Author: yunwuxin <448901948@qq.com>
- // +----------------------------------------------------------------------
- namespace think\console\command\optimize;
- use think\console\Command;
- use think\console\Input;
- use think\console\Output;
- use think\Container;
- class Autoload extends Command
- {
- protected function configure()
- {
- $this->setName('optimize:autoload')
- ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.');
- }
- protected function execute(Input $input, Output $output)
- {
- $classmapFile = <<<EOF
- <?php
- /**
- * 类库映射
- */
- return [
- EOF;
- $app = Container::get('app');
- $namespacesToScan = [
- $app->getNamespace() . '\\' => realpath(rtrim($app->getAppPath())),
- 'think\\' => $app->getThinkPath() . 'library/think',
- 'traits\\' => $app->getThinkPath() . 'library/traits',
- '' => realpath(rtrim($app->getRootPath() . 'extend')),
- ];
- krsort($namespacesToScan);
- $classMap = [];
- foreach ($namespacesToScan as $namespace => $dir) {
- if (!is_dir($dir)) {
- continue;
- }
- $namespaceFilter = '' === $namespace ? null : $namespace;
- $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap);
- }
- ksort($classMap);
- foreach ($classMap as $class => $code) {
- $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code;
- }
- $classmapFile .= "];\n";
- $runtimePath = $app->getRuntimePath();
- if (!is_dir($runtimePath)) {
- @mkdir($runtimePath, 0755, true);
- }
- file_put_contents($runtimePath . 'classmap.php', $classmapFile);
- $output->writeln('<info>Succeed!</info>');
- }
- protected function addClassMapCode($dir, $namespace, $classMap)
- {
- foreach ($this->createMap($dir, $namespace) as $class => $path) {
- $pathCode = $this->getPathCode($path) . ",\n";
- if (!isset($classMap[$class])) {
- $classMap[$class] = $pathCode;
- } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) {
- $this->output->writeln(
- '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
- ' was found in both "' . str_replace(["',\n"], [
- '',
- ], $classMap[$class]) . '" and "' . $path . '", the first will be used.</warning>'
- );
- }
- }
- return $classMap;
- }
- protected function getPathCode($path)
- {
- $baseDir = '';
- $app = Container::get('app');
- $appPath = $this->normalizePath(realpath($app->getAppPath()));
- $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library'));
- $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend'));
- $path = $this->normalizePath($path);
- if (strpos($path, $libPath . '/') === 0) {
- $path = substr($path, strlen($app->getThinkPath() . 'library'));
- $baseDir = "'" . $libPath . "/'";
- } elseif (strpos($path, $appPath . '/') === 0) {
- $path = substr($path, strlen($appPath) + 1);
- $baseDir = "'" . $appPath . "/'";
- } elseif (strpos($path, $extendPath . '/') === 0) {
- $path = substr($path, strlen($extendPath) + 1);
- $baseDir = "'" . $extendPath . "/'";
- }
- if (false !== $path) {
- $baseDir .= " . ";
- }
- return $baseDir . ((false !== $path) ? var_export($path, true) : "");
- }
- protected function normalizePath($path)
- {
- $parts = [];
- $path = strtr($path, '\\', '/');
- $prefix = '';
- $absolute = false;
- if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) {
- $prefix = $match[1];
- $path = substr($path, strlen($prefix));
- }
- if (substr($path, 0, 1) === '/') {
- $absolute = true;
- $path = substr($path, 1);
- }
- $up = false;
- foreach (explode('/', $path) as $chunk) {
- if ('..' === $chunk && ($absolute || $up)) {
- array_pop($parts);
- $up = !(empty($parts) || '..' === end($parts));
- } elseif ('.' !== $chunk && '' !== $chunk) {
- $parts[] = $chunk;
- $up = '..' !== $chunk;
- }
- }
- return $prefix . ($absolute ? '/' : '') . implode('/', $parts);
- }
- protected function createMap($path, $namespace = null)
- {
- if (is_string($path)) {
- if (is_file($path)) {
- $path = [new \SplFileInfo($path)];
- } elseif (is_dir($path)) {
- $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST);
- $path = [];
- /** @var \SplFileInfo $object */
- foreach ($objects as $object) {
- if ($object->isFile() && $object->getExtension() == 'php') {
- $path[] = $object;
- }
- }
- } else {
- throw new \RuntimeException(
- 'Could not scan for classes inside "' . $path .
- '" which does not appear to be a file nor a folder'
- );
- }
- }
- $map = [];
- /** @var \SplFileInfo $file */
- foreach ($path as $file) {
- $filePath = $file->getRealPath();
- if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') {
- continue;
- }
- $classes = $this->findClasses($filePath);
- foreach ($classes as $class) {
- if (null !== $namespace && 0 !== strpos($class, $namespace)) {
- continue;
- }
- if (!isset($map[$class])) {
- $map[$class] = $filePath;
- } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) {
- $this->output->writeln(
- '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
- ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'
- );
- }
- }
- }
- return $map;
- }
- protected function findClasses($path)
- {
- $extraTypes = '|trait';
- $contents = @php_strip_whitespace($path);
- if (!$contents) {
- if (!file_exists($path)) {
- $message = 'File at "%s" does not exist, check your classmap definitions';
- } elseif (!is_readable($path)) {
- $message = 'File at "%s" is not readable, check its permissions';
- } elseif ('' === trim(file_get_contents($path))) {
- return [];
- } else {
- $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
- }
- $error = error_get_last();
- if (isset($error['message'])) {
- $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
- }
- throw new \RuntimeException(sprintf($message, $path));
- }
- if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) {
- return [];
- }
- // strip heredocs/nowdocs
- $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
- // strip strings
- $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
- // strip leading non-php code if needed
- if (substr($contents, 0, 2) !== '<?') {
- $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
- if (0 === $replacements) {
- return [];
- }
- }
- // strip non-php blocks in the file
- $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
- // strip trailing non-php code if needed
- $pos = strrpos($contents, '?>');
- if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
- $contents = substr($contents, 0, $pos);
- }
- preg_match_all('{
- (?:
- \b(?<![\$:>])(?P<type>class|interface' . $extraTypes . ') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
- | \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*+ [\{;]
- )
- }ix', $contents, $matches);
- $classes = [];
- $namespace = '';
- for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
- if (!empty($matches['ns'][$i])) {
- $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\';
- } else {
- $name = $matches['name'][$i];
- if (':' === $name[0]) {
- $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
- } elseif ('enum' === $matches['type'][$i]) {
- $name = rtrim($name, ':');
- }
- $classes[] = ltrim($namespace . $name, '\\');
- }
- }
- return $classes;
- }
- }
|