// +---------------------------------------------------------------------- declare(strict_types=1); namespace ywxapp\command; use think\console\Command; use think\console\Input; use think\console\Output; use think\console\input\Argument; use think\console\input\Option; /** * AppCommand 应用命令统一入口 * * 将 ywxapp/command/app/ 目录下的全部命令(app 脚手架 / 生成器)逻辑内联到本类, * 绑定为单一入口 `php think app <子命令> [参数]`,不再保留独立的命令类文件。 * 插件等其它命令(addon:* 系列)由各自独立注册,不在此处理。 * * 子命令: * build 构建应用目录(原 build:app) * clear 清理运行时文件(原 clear) * controller 生成控制器(原 app:controller,支持 --api/--plain) * model 生成模型(原 app:model) * event 生成事件(原 app:event) * listener 生成监听器(原 make:listener) * middleware 生成中间件(原 make:middleware) * service 生成服务(原 make:service) * subscribe 生成订阅器(原 make:subscribe) * validate 生成验证器(原 make:validate) * * 用法: * php think app # 列出所有可用子命令 * php think app controller app\index # 生成控制器 * php think app controller app\index --api # 生成 api 风格控制器 * php think app model app\demo # 生成模型 * php think app clear --cache # 清理缓存 * php think app build myapp # 构建应用目录 */ class AppCommand extends Command { /** * Make 型子命令配置 * type : 类型标签(用于成功/存在提示) * stub : stub 文件名(不含 .stub 扩展) * ns : 命名空间末尾段(如 controller/model/event ...) * suffix : 类名后缀(仅 controller 在开启 controller_suffix 时追加 Controller) */ private const MAKE_TYPES = [ 'controller' => ['type' => 'Controller', 'stub' => 'controller', 'ns' => 'controller', 'suffix' => 'Controller'], 'model' => ['type' => 'Model', 'stub' => 'model', 'ns' => 'model', 'suffix' => ''], 'event' => ['type' => 'Event', 'stub' => 'event', 'ns' => 'event', 'suffix' => ''], 'listener' => ['type' => 'Listener', 'stub' => 'listener', 'ns' => 'listener', 'suffix' => ''], 'middleware' => ['type' => 'Middleware', 'stub' => 'middleware', 'ns' => 'middleware', 'suffix' => ''], 'service' => ['type' => 'Service', 'stub' => 'service', 'ns' => 'service', 'suffix' => ''], 'subscribe' => ['type' => 'Subscribe', 'stub' => 'subscribe', 'ns' => 'subscribe', 'suffix' => ''], 'validate' => ['type' => 'Validate', 'stub' => 'validate', 'ns' => 'validate', 'suffix' => ''], ]; /** * 应用基础目录(Build 使用) * @var string */ private $basePath = ''; /** * stub 模板目录(已从 ywxapp/command/app/stubs 上移至此) */ private function stubDir(): string { return __DIR__ . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR; } /** * 配置命令 */ protected function configure() { $this->setName('app') ->addOption('list', 'l', Option::VALUE_NONE, '列出所有可用子命令') ->addOption('api', null, Option::VALUE_NONE, '生成 api 风格控制器(仅 controller)') ->addOption('plain', null, Option::VALUE_NONE, '生成空控制器(仅 controller)') ->addOption('cache', 'c', Option::VALUE_NONE, '清理缓存目录(仅 clear)') ->addOption('log', null, Option::VALUE_NONE, '清理日志目录(仅 clear)') ->addOption('dir', 'r', Option::VALUE_NONE, '清理空目录(仅 clear)') ->addArgument('subcommand', Argument::OPTIONAL, '子命令名(build/clear/controller/model/event/listener/middleware/service/subscribe/validate)') ->addArgument('name', Argument::OPTIONAL, '类名或 app 名(如 app\\index 或 myapp)') ->setDescription('YwxApp 应用命令入口:聚合 ywxapp/command/app 下的命令,php think app <子命令> [参数]'); } /** * 执行命令 */ protected function execute(Input $input, Output $output): int { if ($input->getOption('list') || empty($input->getArgument('subcommand'))) { $this->showList($output); return 0; } $sub = $input->getArgument('subcommand'); $name = (string) $input->getArgument('name'); if (isset(self::MAKE_TYPES[$sub])) { return $this->runMake($sub, $name, $input, $output); } if ($sub === 'build') { return $this->runBuild($name, $output); } if ($sub === 'clear') { return $this->runClear($name, $input, $output); } $output->writeln('未知子命令: ' . $sub . ''); $this->showList($output); return 1; } /** * 列出所有可用子命令 */ private function showList(Output $output): void { $output->writeln('YwxApp 应用命令(`php think app <子命令> [参数]`):'); $output->writeln(''); $width = max(array_map('strlen', array_keys(self::MAKE_TYPES + ['build' => 1, 'clear' => 1]))); foreach (array_keys(['build' => 1, 'clear' => 1] + self::MAKE_TYPES) as $name) { $desc = [ 'build' => '构建应用目录', 'clear' => '清理运行时文件', 'controller' => '生成控制器(--api/--plain)', 'model' => '生成模型', 'event' => '生成事件', 'listener' => '生成监听器', 'middleware' => '生成中间件', 'service' => '生成服务', 'subscribe' => '生成订阅器', 'validate' => '生成验证器', ][$name]; $output->writeln(' ' . str_pad($name, $width) . ' ' . $desc); } $output->writeln(''); $output->writeln('示例:php think app controller app\\index'); $output->writeln(' php think app clear --cache'); } /* ===================== Make 型生成器 ===================== */ /** * 运行 Make 型子命令 */ private function runMake(string $type, string $name, Input $input, Output $output): int { if ($name === '') { $output->writeln('缺少类名参数,用法:php think app ' . $type . ' <类名>'); return 1; } $cfg = self::MAKE_TYPES[$type]; $classname = $this->makeClassName($type, $name); $pathname = $this->makePathName($classname); if (is_file($pathname)) { $output->writeln('' . $cfg['type'] . ':' . $classname . ' already exists!'); return 1; } if (!is_dir(dirname($pathname))) { mkdir(dirname($pathname), 0755, true); } file_put_contents($pathname, $this->buildClass($classname, $type, $input)); $output->writeln('' . $cfg['type'] . ':' . $classname . ' created successfully.'); return 0; } /** * 解析 stub 路径(controller 支持 --api/--plain) */ private function makeStub(string $type, Input $input): string { if ($type === 'controller') { if ($input->getOption('api')) { return $this->stubDir() . 'controller.api.stub'; } if ($input->getOption('plain')) { return $this->stubDir() . 'controller.plain.stub'; } return $this->stubDir() . 'controller.stub'; } return $this->stubDir() . self::MAKE_TYPES[$type]['stub'] . '.stub'; } /** * 解析命名空间 */ private function makeNamespace(string $type, string $app): string { return 'app' . ($app ? '\\' . $app : '') . '\\' . self::MAKE_TYPES[$type]['ns']; } /** * 解析完整类名(等价于 think\console\command\Make::getClassName + Controller 后缀) */ private function makeClassName(string $type, string $name): string { if (str_contains($name, '\\')) { $class = $name; } else { $app = ''; if (str_contains($name, '@')) { [$app, $name] = explode('@', $name); } if (str_contains($name, '/')) { $name = str_replace('/', '\\', $name); } $class = $this->makeNamespace($type, $app) . '\\' . $name; } $suffix = self::MAKE_TYPES[$type]['suffix']; if ($suffix !== '' && $this->app->config->get('route.controller_suffix')) { $class .= $suffix; } return $class; } /** * 解析文件路径(等价于各 Make 命令重写的 getPathName) */ private function makePathName(string $name): string { return $this->app->getRootPath() . ltrim(str_replace('\\', '/', $name), '/') . '.php'; } /** * 用 stub 渲染类文件(等价于 think\console\command\Make::buildClass) */ private function buildClass(string $name, string $type, Input $input): string { $stub = file_get_contents($this->makeStub($type, $input)); $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); $class = str_replace($namespace . '\\', '', $name); return str_replace( ['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ $class, $this->app->config->get('route.action_suffix'), $namespace, $this->app->getNamespace(), ], $stub ); } /* ===================== Build 命令 ===================== */ /** * 运行 build 子命令(原 build:app) */ private function runBuild(string $app, Output $output): int { $this->basePath = $this->app->getRootPath() . 'app' . DIRECTORY_SEPARATOR; $app = $app ?: ''; if (is_file($this->basePath . 'build.php')) { $list = include $this->basePath . 'build.php'; } else { $list = [ '__dir__' => ['controller', 'model', 'view'], ]; } $this->buildApp($app, $list); $output->writeln('Successed'); return 0; } /** * 创建应用 */ private function buildApp(string $app, array $list = []): void { if (!is_dir($this->basePath . $app)) { mkdir($this->basePath . $app); } $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); $namespace = 'app' . ($app ? '\\' . $app : ''); $this->buildCommon($app); $this->buildHello($app, $namespace); $this->buildapp1($app, $namespace); $this->buildInfo($app); foreach ($list as $path => $file) { if ('__dir__' == $path) { foreach ($file as $dir) { $this->checkDirBuild($appPath . $dir); } } elseif ('__file__' == $path) { foreach ($file as $fname) { if (!is_file($appPath . $fname)) { file_put_contents($appPath . $fname, 'php' == pathinfo($fname, PATHINFO_EXTENSION) ? 'app->config->get('route.controller_suffix')) { $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php'; $class = $val . 'Controller'; } $content = 'checkDirBuild(dirname($filename)); $content = ''; break; default: $content = 'app->config->get('route.controller_suffix') ? 'Controller' : ''; $filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php'; if (!is_file($filename)) { $content = file_get_contents($this->stubDir() . 'controller.stub'); $content = str_replace( ['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], ['Index', $suffix, $namespace . '\\controller', $this->app->getNamespace()], $content ); $this->checkDirBuild(dirname($filename)); file_put_contents($filename, $content); } } private function buildapp1(string $app, string $namespace): void { $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); if (!is_file($appPath . 'app.php')) { $content = file_get_contents($this->stubDir() . 'app.stub'); $content = str_replace(['{%namespace%}', '{%app%}', '{%className%}'], [$namespace, $app, 'app'], $content); file_put_contents($appPath . 'app.php', $content); } } private function buildInfo(string $app): void { $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); if (!is_file($appPath . 'info.php')) { $array = [ 'name' => '', 'title' => '', 'intro' => '', 'author' => '', 'website' => '', 'version' => '1.0.0', 'state' => 1, 'url' => '', 'license' => '', 'licenseto' => 0, ]; $array = array_merge($array, ['name' => $app, 'url' => '/' . $app]); $output = ""; file_put_contents($appPath . 'info.php', $output); } } /** * 创建应用的公共文件 */ private function buildCommon(string $app): void { $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); foreach (['event', 'middleware', 'common', 'config'] as $nm) { if (!is_file($appPath . $nm . '.php')) { file_put_contents($appPath . $nm . '.php', 'app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : ''); if ($input->getOption('cache')) { $path = $runtimePath . 'cache'; } elseif ($input->getOption('log')) { $path = $runtimePath . 'log'; } else { $path = $runtimePath; } $rmdir = $input->getOption('dir') ? true : false; $this->clearRuntime(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); $output->writeln('Clear Successed'); return 0; } /** * 递归清理目录 */ private function clearRuntime(string $path, bool $rmdir): void { $files = is_dir($path) ? scandir($path) : []; foreach ($files as $file) { if ('.' != $file && '..' != $file && is_dir($path . $file)) { array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); if ($rmdir) { rmdir($path . $file); } } elseif ('.gitignore' != $file && is_file($path . $file)) { unlink($path . $file); } } } }