chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace ywxapp\command;
|
||||
|
||||
use think\console\command\Make;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* Addon 统一脚手架 / 生成器命令
|
||||
*
|
||||
* 合并原 addon:build / addon:clear / addon:controller / addon:model /
|
||||
* addon:middleware / addon:validate / addon:event / addon:listener /
|
||||
* addon:service / addon:subscribe 十条命令为单入口:
|
||||
* php think addon:make <插件名> -a <action> [--name=<类>] [动作选项]
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class AddonMake extends Make
|
||||
{
|
||||
/**
|
||||
* 当前生成动作对应的类类型(首字母大写),供 getStub/buildClass 使用
|
||||
* @var string
|
||||
*/
|
||||
protected $currentType = '';
|
||||
|
||||
/**
|
||||
* 当前生成动作对应的 stub 关键字
|
||||
* @var string
|
||||
*/
|
||||
protected $currentStub = '';
|
||||
|
||||
/**
|
||||
* 应用基础目录
|
||||
* @var string
|
||||
*/
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('addon:make')
|
||||
->addArgument('addon', Argument::OPTIONAL, 'addon name, e.g. demo (required for build & generators)')
|
||||
->addOption('action', 'a', Option::VALUE_REQUIRED, 'build|clear|controller|model|middleware|validate|event|listener|service|subscribe')
|
||||
->addOption('name', 'n', Option::VALUE_OPTIONAL, 'class name for generator actions, e.g. User')
|
||||
->addOption('api', null, Option::VALUE_NONE, 'controller: generate an api controller class.')
|
||||
->addOption('plain', null, Option::VALUE_NONE, 'controller: generate an empty controller class.')
|
||||
->addOption('cache', 'c', Option::VALUE_NONE, 'clear: clear cache file')
|
||||
->addOption('log', 'l', Option::VALUE_NONE, 'clear: clear log file')
|
||||
->addOption('dir', 'r', Option::VALUE_NONE, 'clear: clear empty dir')
|
||||
->setDescription('Unified addon scaffold & class generator (build / clear / controller / model / middleware / validate / event / listener / service / subscribe)');
|
||||
}
|
||||
|
||||
|
||||
protected function execute(Input $input, Output $output): int
|
||||
{
|
||||
$addon = trim((string) $input->getArgument('addon'));
|
||||
$action = strtolower(trim((string) $input->getOption('action')));
|
||||
|
||||
if ($action === '') {
|
||||
$output->writeln('<error>Please specify an action via -a/--action, e.g. -a build, -a controller.</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// clear 允许插件名为空(清理根 runtime)
|
||||
if ($action === 'clear') {
|
||||
return $this->doClear($addon, $input, $output);
|
||||
}
|
||||
|
||||
// build 与生成器均要求合法的插件名
|
||||
if ($addon === '' || ! preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $addon)) {
|
||||
$output->writeln('<error>Please provide a valid addon name, e.g. php think addon demo -a ' . $action . '</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'build':
|
||||
return $this->doBuild($addon, $output);
|
||||
case 'controller':
|
||||
case 'model':
|
||||
case 'middleware':
|
||||
case 'validate':
|
||||
case 'event':
|
||||
case 'listener':
|
||||
case 'service':
|
||||
case 'subscribe':
|
||||
return $this->doGenerate($action, $addon, $input, $output);
|
||||
default:
|
||||
$output->writeln('<error>Unknown action: ' . $action . '</error>');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- 生成器动作 ----------------------------- */
|
||||
|
||||
/**
|
||||
* 生成各类插件类文件(controller/model/middleware/validate/event/listener/service/subscribe)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function doGenerate(string $action, string $addon, Input $input, Output $output): int
|
||||
{
|
||||
$this->currentType = ucfirst($action);
|
||||
$this->currentStub = $action;
|
||||
|
||||
$class = trim((string) ($input->getOption('name') ?: ''));
|
||||
if ($class === '' || ! preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $class)) {
|
||||
$output->writeln('<error>Please provide a valid class name via --name, e.g. --name=User</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$name = 'addon' . '\\' . $addon . '\\' . $action . '\\' . $class;
|
||||
$pathname = $this->app->getRootPath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
|
||||
|
||||
if (is_file($pathname)) {
|
||||
$output->writeln('<error>' . $this->currentType . ':' . $name . ' already exists!</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (! is_dir(dirname($pathname))) {
|
||||
mkdir(dirname($pathname), 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($pathname, $this->buildClass($name));
|
||||
|
||||
$output->writeln('<info>' . $this->currentType . ':' . $name . ' created successfully.</info>');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 stub 构建类内容(覆盖 Make::buildClass 以使用插件命名空间)
|
||||
*
|
||||
* @param string $name 完整类名 addon\<addon>\<type>\<Class>
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass(string $name): string
|
||||
{
|
||||
$stub = file_get_contents($this->getStub());
|
||||
|
||||
$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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前动作与 --api/--plain 选项返回 stub 路径
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub(): string
|
||||
{
|
||||
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR;
|
||||
|
||||
if ($this->currentStub === 'controller') {
|
||||
if ($this->input->getOption('api')) {
|
||||
return $stubPath . 'controller.api.stub';
|
||||
}
|
||||
if ($this->input->getOption('plain')) {
|
||||
return $stubPath . 'controller.plain.stub';
|
||||
}
|
||||
return $stubPath . 'controller.stub';
|
||||
}
|
||||
|
||||
return $stubPath . $this->currentStub . '.stub';
|
||||
}
|
||||
|
||||
/* ------------------------------- build -------------------------------- */
|
||||
|
||||
/**
|
||||
* 脚手架生成插件骨架
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function doBuild(string $app, Output $output): int
|
||||
{
|
||||
$this->basePath = $this->app->getRootPath() . 'addon' . DIRECTORY_SEPARATOR;
|
||||
|
||||
if (is_dir($this->basePath . $app)) {
|
||||
$output->writeln("<comment>Addon '{$app}' already exists, ensuring skeleton only.</comment>");
|
||||
}
|
||||
|
||||
$this->buildAddon($app, []);
|
||||
$output->writeln("<info>Addon '{$app}' scaffold ready at addon/{$app}/</info>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建应用
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @param array $list 目录结构
|
||||
* @return void
|
||||
*/
|
||||
protected function buildAddon(string $app, array $list = []): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
$namespace = 'addon' . ($app ? '\\' . $app : '');
|
||||
if (! is_dir($appPath)) {
|
||||
mkdir($appPath);
|
||||
}
|
||||
$this->buildAddonFile($app, $namespace);
|
||||
$this->buildInfoFile($app);
|
||||
$this->buildCommonFile($app);
|
||||
$this->buildConfigFile($app);
|
||||
if (! is_dir($appPath . 'controller')) {
|
||||
mkdir($appPath . 'controller');
|
||||
mkdir($appPath . 'controller/backend');
|
||||
mkdir($appPath . 'controller/member');
|
||||
}
|
||||
$this->buildIndexController($app, $namespace);
|
||||
if (! is_dir($appPath . 'lang')) {
|
||||
mkdir($appPath . 'lang');
|
||||
}
|
||||
if (! is_dir($appPath . 'model')) {
|
||||
mkdir($appPath . 'model');
|
||||
}
|
||||
if (! is_dir($appPath . 'view')) {
|
||||
mkdir($appPath . 'view');
|
||||
mkdir($appPath . 'view/frontend');
|
||||
mkdir($appPath . 'view/backend');
|
||||
mkdir($appPath . 'view/member');
|
||||
}
|
||||
if (! is_dir($appPath . 'route')) {
|
||||
mkdir($appPath . 'route');
|
||||
}
|
||||
$this->buildRouteFile($app, $namespace);
|
||||
$this->buildMenuJson($app);
|
||||
$appPublicPath = public_path() . 'static' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
if (! is_dir($appPublicPath)) {
|
||||
mkdir($appPublicPath, 0755, true);
|
||||
mkdir($appPublicPath . 'img', 0755, true);
|
||||
mkdir($appPublicPath . 'css', 0755, true);
|
||||
mkdir($appPublicPath . 'js', 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建插件主类文件
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @param string $namespace 类库命名空间
|
||||
* @return void
|
||||
*/
|
||||
protected function buildAddonFile(string $app, string $namespace): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
if (! is_file($appPath . 'Addon.php')) {
|
||||
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'addon.stub');
|
||||
$content = str_replace(['{%namespace%}', '{%addon%}', '{%className%}'], [$namespace, $app, 'Addon'], $content);
|
||||
file_put_contents($appPath . 'Addon.php', $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建插件信息文件
|
||||
*
|
||||
* @param string $app 目录
|
||||
* @return void
|
||||
*/
|
||||
protected function buildInfoFile(string $app): void
|
||||
{
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'info.php';
|
||||
if (! is_file($filename)) {
|
||||
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'info.stub');
|
||||
$content = str_replace(['{%app%}'], [$app], $content);
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建菜单 JSON
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @return void
|
||||
*/
|
||||
protected function buildMenuJson(string $app): void
|
||||
{
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'menu.json';
|
||||
if (! is_file($filename)) {
|
||||
$menu = ["frontend" => [], "member" => [], "backend" => []];
|
||||
$content = json_encode($menu, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建公共文件
|
||||
*
|
||||
* @param string $app 目录
|
||||
* @return void
|
||||
*/
|
||||
public function buildCommonFile(string $app): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
if (! is_file($appPath . 'common.php')) {
|
||||
file_put_contents($appPath . 'common.php', "<?php" . PHP_EOL . "// 这是系统自动生成的公共文件" . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建插件配置文件(后台「配置」表单数据源)
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @return void
|
||||
*/
|
||||
protected function buildConfigFile(string $app): void
|
||||
{
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'config.php';
|
||||
if (! is_file($filename)) {
|
||||
file_put_contents($filename, "<?php" . PHP_EOL
|
||||
. "// 插件配置项(后台「配置」表单数据源)" . PHP_EOL
|
||||
. "return [];" . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建首页控制器
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @param string $namespace 类库命名空间
|
||||
* @return void
|
||||
*/
|
||||
public function buildIndexController(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(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'controller.stub');
|
||||
$content = str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], ['Index', $suffix, $namespace . '\\controller', $namespace], $content);
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建路由文件
|
||||
*
|
||||
* @param string $app 应用名
|
||||
* @param string $namespace 类库命名空间
|
||||
* @return void
|
||||
*/
|
||||
public function buildRouteFile(string $app, string $namespace): void
|
||||
{
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR . 'app.php';
|
||||
if (! is_file($filename)) {
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, "<?php" . PHP_EOL . "use think\\facade\\Route;" . PHP_EOL . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录
|
||||
*
|
||||
* @param string $dirname 目录名称
|
||||
* @return void
|
||||
*/
|
||||
public function checkDirBuild(string $dirname): void
|
||||
{
|
||||
if (! is_dir($dirname)) {
|
||||
mkdir($dirname, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------- clear -------------------------------- */
|
||||
|
||||
/**
|
||||
* 清理 runtime 文件
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function doClear(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->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
|
||||
|
||||
$output->writeln("<info>Clear Successed</info>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归清理目录
|
||||
*
|
||||
* @param string $path 目录
|
||||
* @param bool $rmdir 是否删除空目录
|
||||
* @return void
|
||||
*/
|
||||
protected function clear(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user