| | <?php |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | namespace think\console\command\optimize; |
| |
|
| | use think\App; |
| | use think\Config; |
| | use think\console\Command; |
| | use think\console\Input; |
| | use think\console\Output; |
| |
|
| | 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; |
| |
|
| | $namespacesToScan = [ |
| | App::$namespace . '\\' => realpath(rtrim(APP_PATH)), |
| | 'think\\' => LIB_PATH . 'think', |
| | 'behavior\\' => LIB_PATH . 'behavior', |
| | 'traits\\' => LIB_PATH . 'traits', |
| | '' => realpath(rtrim(EXTEND_PATH)), |
| | ]; |
| |
|
| | $root_namespace = Config::get('root_namespace'); |
| | foreach ($root_namespace as $namespace => $dir) { |
| | $namespacesToScan[$namespace . '\\'] = realpath($dir); |
| | } |
| |
|
| | 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"; |
| |
|
| | if (!is_dir(RUNTIME_PATH)) { |
| | @mkdir(RUNTIME_PATH, 0755, true); |
| | } |
| |
|
| | file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $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 = ''; |
| | $libPath = $this->normalizePath(realpath(LIB_PATH)); |
| | $appPath = $this->normalizePath(realpath(APP_PATH)); |
| | $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); |
| | $rootPath = $this->normalizePath(realpath(ROOT_PATH)); |
| | $path = $this->normalizePath($path); |
| |
|
| | if ($libPath !== null && strpos($path, $libPath . '/') === 0) { |
| | $path = substr($path, strlen(LIB_PATH)); |
| | $baseDir = 'LIB_PATH'; |
| | } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { |
| | $path = substr($path, strlen($appPath) + 1); |
| | $baseDir = 'APP_PATH'; |
| | } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { |
| | $path = substr($path, strlen($extendPath) + 1); |
| | $baseDir = 'EXTEND_PATH'; |
| | } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { |
| | $path = substr($path, strlen($rootPath) + 1); |
| | $baseDir = 'ROOT_PATH'; |
| | } |
| |
|
| | if ($path !== false) { |
| | $baseDir .= " . "; |
| | } |
| |
|
| | return $baseDir . (($path !== false) ? var_export($path, true) : ""); |
| | } |
| |
|
| | protected function normalizePath($path) |
| | { |
| | if ($path === false) { |
| | return; |
| | } |
| | $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 = []; |
| |
|
| | |
| | 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 = []; |
| |
|
| | |
| | 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 []; |
| | } |
| |
|
| | |
| | $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); |
| | |
| | $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); |
| | |
| | if (substr($contents, 0, 2) !== '<?') { |
| | $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements); |
| | if ($replacements === 0) { |
| | return []; |
| | } |
| | } |
| | |
| | $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents); |
| | |
| | $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 ($matches['type'][$i] === 'enum') { |
| | $name = rtrim($name, ':'); |
| | } |
| | $classes[] = ltrim($namespace . $name, '\\'); |
| | } |
| | } |
| |
|
| | return $classes; |
| | } |
| |
|
| | } |
| |
|