Files
YwxAppThink/ywxapp/command/AppCommand.php
T

472 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
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('<error>未知子命令: ' . $sub . '</error>');
$this->showList($output);
return 1;
}
/**
* 列出所有可用子命令
*/
private function showList(Output $output): void
{
$output->writeln('<info>YwxApp 应用命令(`php think app <子命令> [参数]`):</info>');
$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('<error>缺少类名参数,用法:php think app ' . $type . ' &lt;类名&gt;</error>');
return 1;
}
$cfg = self::MAKE_TYPES[$type];
$classname = $this->makeClassName($type, $name);
$pathname = $this->makePathName($classname);
if (is_file($pathname)) {
$output->writeln('<error>' . $cfg['type'] . ':' . $classname . ' already exists!</error>');
return 1;
}
if (!is_dir(dirname($pathname))) {
mkdir(dirname($pathname), 0755, true);
}
file_put_contents($pathname, $this->buildClass($classname, $type, $input));
$output->writeln('<info>' . $cfg['type'] . ':' . $classname . ' created successfully.</info>');
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('<info>Successed</info>');
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) ? '<?php' . PHP_EOL : '');
}
}
} else {
foreach ($file as $val) {
$val = trim($val);
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.php';
$space = $namespace . '\\' . $path;
$class = $val;
switch ($path) {
case 'controller':
if ($this->app->config->get('route.controller_suffix')) {
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php';
$class = $val . 'Controller';
}
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'class ' . $class . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
break;
case 'model':
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'use think\Model;' . PHP_EOL . PHP_EOL . 'class ' . $class . ' extends Model' . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
break;
case 'view':
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.html';
$this->checkDirBuild(dirname($filename));
$content = '';
break;
default:
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'class ' . $class . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
}
if (!is_file($filename)) {
file_put_contents($filename, $content);
}
}
}
}
}
/**
* 创建应用的欢迎页面
*/
private function buildHello(string $app, string $namespace): void
{
$suffix = $this->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 = "<?php\n return " . var_export($array, true) . ";\n?>";
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', '<?php' . PHP_EOL . '// 这是系统自动生成的' . $nm . '定义文件' . PHP_EOL . 'return [' . PHP_EOL . PHP_EOL . '];' . PHP_EOL);
}
}
}
/**
* 创建目录
*/
private function checkDirBuild(string $dirname): void
{
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
}
/* ===================== Clear 命令 ===================== */
/**
* 运行 clear 子命令(原 clear
*/
private function runClear(string $app, Input $input, Output $output): int
{
$runtimePath = $this->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('<info>Clear Successed</info>');
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);
}
}
}
}