mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-02 14:25:38 +08:00
refactor InitCommand
This commit is contained in:
@@ -22,14 +22,14 @@
|
|||||||
"onebot/libonebot": "dev-develop",
|
"onebot/libonebot": "dev-develop",
|
||||||
"psr/container": "^2.0",
|
"psr/container": "^2.0",
|
||||||
"psy/psysh": "^0.11.8",
|
"psy/psysh": "^0.11.8",
|
||||||
"symfony/console": "^6.0 || ^5.0 || ^4.0",
|
"symfony/console": "^6.0",
|
||||||
"symfony/polyfill-ctype": "^1.19",
|
"symfony/polyfill-ctype": "^1.19",
|
||||||
"symfony/polyfill-mbstring": "^1.19",
|
"symfony/polyfill-mbstring": "^1.19",
|
||||||
"symfony/polyfill-php80": "^1.16",
|
"symfony/polyfill-php80": "^1.16",
|
||||||
"symfony/routing": "~6.0 || ~5.0 || ~4.0"
|
"symfony/routing": "~6.0 || ~5.0 || ~4.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"brainmaestro/composer-git-hooks": "^2.8",
|
"brainmaestro/composer-git-hooks": "^3.0",
|
||||||
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
||||||
"jetbrains/phpstorm-attributes": "^1.0",
|
"jetbrains/phpstorm-attributes": "^1.0",
|
||||||
"phpstan/extension-installer": "^1.1",
|
"phpstan/extension-installer": "^1.1",
|
||||||
|
|||||||
73
src/ZM/Command/Command.php
Normal file
73
src/ZM/Command/Command.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Exception\ZMException;
|
||||||
|
|
||||||
|
abstract class Command extends \Symfony\Component\Console\Command\Command
|
||||||
|
{
|
||||||
|
protected InputInterface $input;
|
||||||
|
|
||||||
|
protected OutputInterface $output;
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
$this->output = $output;
|
||||||
|
return $this->handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function handle(): int;
|
||||||
|
|
||||||
|
protected function write(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->output->write($message, $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function info(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->write("<info>{$message}</info>", $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function error(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->write("<error>{$message}</error>", $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function comment(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->write("<comment>{$message}</comment>", $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function question(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->write("<question>{$message}</question>", $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function detail(string $message, bool $newline = true): void
|
||||||
|
{
|
||||||
|
$this->write("<fg=gray>{$message}</>", $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function section(string $message, callable $callback): void
|
||||||
|
{
|
||||||
|
$output = $this->output;
|
||||||
|
if (!$output instanceof ConsoleOutputInterface) {
|
||||||
|
throw new \LogicException('Section 功能只能在 ConsoleOutputInterface 中使用');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info($message);
|
||||||
|
$section = $output->section();
|
||||||
|
try {
|
||||||
|
$callback($section);
|
||||||
|
} catch (ZMException $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,114 +4,173 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ZM\Command;
|
namespace ZM\Command;
|
||||||
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
|
use ZM\Exception\InitException;
|
||||||
|
|
||||||
class InitCommand extends Command
|
class InitCommand extends Command
|
||||||
{
|
{
|
||||||
// the name of the command (the part after "bin/console")
|
// the name of the command (the part after "bin/console")
|
||||||
protected static $defaultName = 'init';
|
protected static $defaultName = 'init';
|
||||||
|
|
||||||
private $extract_files = [
|
private string $base_path;
|
||||||
'/zhamao',
|
|
||||||
'/config/global.php',
|
|
||||||
'/.gitignore',
|
|
||||||
'/config/file_header.json',
|
|
||||||
'/config/console_color.json',
|
|
||||||
'/config/motd.txt',
|
|
||||||
'/src/Module/Example/Hello.php',
|
|
||||||
'/src/Module/Middleware/TimerMiddleware.php',
|
|
||||||
'/src/Custom/global_function.php',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function configure()
|
private bool $force = false;
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Initialize framework starter | 初始化框架运行的基础文件');
|
$this->setDescription('Initialize framework starter | 初始化框架运行的基础文件');
|
||||||
$this->setDefinition([
|
$this->setDefinition([
|
||||||
new InputOption('force', 'F', null, '强制重制,覆盖现有文件'),
|
new InputOption('force', 'F', null, '强制重制,覆盖现有文件'),
|
||||||
]);
|
]);
|
||||||
$this->setHelp("此命令将会解压以下文件到项目的根目录:\n" . implode("\n", $this->getExtractFiles()));
|
$this->setHelp('提取框架的基础文件到当前目录,以便于快速开始开发。');
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function handle(): int
|
||||||
{
|
{
|
||||||
if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式,最基本的需要初始化的模式
|
$this->setBasePath();
|
||||||
$output->writeln('<comment>Initializing files</comment>');
|
$this->force = $this->input->getOption('force');
|
||||||
$base_path = WORKING_DIR;
|
|
||||||
$args = $input->getOption('force');
|
$this->section('提取框架基础文件', function (ConsoleSectionOutput $section) {
|
||||||
foreach ($this->extract_files as $file) {
|
foreach ($this->getExtractFiles() as $file) {
|
||||||
if (!file_exists($base_path . $file) || $args) {
|
$section->write("<fg=gray>提取 {$file} ... </>");
|
||||||
$info = pathinfo($file);
|
if ($this->shouldExtractFile($file)) {
|
||||||
@mkdir($base_path . $info['dirname'], 0777, true);
|
try {
|
||||||
echo 'Copying ' . $file . PHP_EOL;
|
$this->extractFile($file);
|
||||||
$package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name'];
|
$section->write('<info>完成</info>');
|
||||||
copy($base_path . '/vendor/' . $package_name . $file, $base_path . $file);
|
} catch (InitException $e) {
|
||||||
|
$section->write('<error>失败</error>');
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
$section->writeln('');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL;
|
$section->writeln('<comment>跳过(已存在)</comment>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chmod($base_path . '/zhamao', 0755);
|
});
|
||||||
$autoload = [
|
|
||||||
'psr-4' => [
|
if (LOAD_MODE === 1) {
|
||||||
'Module\\' => 'src/Module',
|
$this->section('应用自动加载配置', function (ConsoleSectionOutput $section) {
|
||||||
'Custom\\' => 'src/Custom',
|
$autoload = [
|
||||||
],
|
'psr-4' => [
|
||||||
'files' => [
|
'Module\\' => 'src/Module',
|
||||||
'src/Custom/global_function.php',
|
'Custom\\' => 'src/Custom',
|
||||||
],
|
],
|
||||||
];
|
'files' => [
|
||||||
if (file_exists($base_path . '/composer.json')) {
|
'src/Custom/global_function.php',
|
||||||
$composer = json_decode(file_get_contents($base_path . '/composer.json'), true);
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!file_exists($this->base_path . '/composer.json')) {
|
||||||
|
throw new InitException('未找到 composer.json 文件', '请检查当前目录是否为项目根目录', 41);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$composer = json_decode(file_get_contents($this->base_path . '/composer.json'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
throw new InitException('解析 composer.json 文件失败', '请检查 composer.json 文件是否存在语法错误', 42, $e);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($composer['autoload'])) {
|
if (!isset($composer['autoload'])) {
|
||||||
$composer['autoload'] = $autoload;
|
$composer['autoload'] = $autoload;
|
||||||
} else {
|
} else {
|
||||||
foreach ($autoload['psr-4'] as $k => $v) {
|
$composer['autoload'] = array_merge_recursive($composer['autoload'], $autoload);
|
||||||
if (!isset($composer['autoload']['psr-4'][$k])) {
|
|
||||||
$composer['autoload']['psr-4'][$k] = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($autoload['files'] as $v) {
|
|
||||||
if (!in_array($v, $composer['autoload']['files'])) {
|
|
||||||
$composer['autoload']['files'][] = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
file_put_contents($base_path . '/composer.json', json_encode($composer, 64 | 128 | 256));
|
|
||||||
$output->writeln('<info>Executing composer command: `composer dump-autoload`</info>');
|
try {
|
||||||
|
file_put_contents($this->base_path . '/composer.json', json_encode($composer, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
throw new InitException('写入 composer.json 文件失败', '', 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$section->writeln('<fg=gray>执行 composer dump-autoload ...</>');
|
||||||
exec('composer dump-autoload');
|
exec('composer dump-autoload');
|
||||||
echo PHP_EOL;
|
|
||||||
} else {
|
$section->writeln('<info>完成</info>');
|
||||||
echo zm_internal_errcode('E00041') . "Error occurred. Please check your updates.\n";
|
});
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
$output->writeln('<info>Done!</info>');
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (LOAD_MODE === 2) { // 从phar启动的框架包,初始化的模式
|
|
||||||
$phar_link = new \Phar(__DIR__);
|
// 将命令行入口标记为可执行
|
||||||
$current_dir = pathinfo($phar_link->getPath())['dirname'];
|
chmod($this->base_path . '/zhamao', 0755);
|
||||||
chdir($current_dir);
|
return 0;
|
||||||
$phar_link = 'phar://' . $phar_link->getPath();
|
|
||||||
foreach ($this->extract_files as $file) {
|
|
||||||
if (!file_exists($current_dir . $file)) {
|
|
||||||
$info = pathinfo($file);
|
|
||||||
@mkdir($current_dir . $info['dirname'], 0777, true);
|
|
||||||
echo 'Copying ' . $file . PHP_EOL;
|
|
||||||
file_put_contents($current_dir . $file, file_get_contents($phar_link . $file));
|
|
||||||
} else {
|
|
||||||
echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$output->writeln(zm_internal_errcode('E00042') . 'initialization must be started with composer-project mode!');
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getExtractFiles(): array
|
private function getExtractFiles(): array
|
||||||
{
|
{
|
||||||
return $this->extract_files;
|
$patterns = [
|
||||||
|
'/zhamao',
|
||||||
|
'/.gitignore',
|
||||||
|
'/config/*',
|
||||||
|
'/src/Globals/*.php',
|
||||||
|
];
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
// TODO: 优化代码,避免在循环中使用 array_merge 以减少资源消耗
|
||||||
|
$files = array_merge($files, glob($this->getVendorPath($pattern)));
|
||||||
|
}
|
||||||
|
return array_map(function ($file) {
|
||||||
|
return str_replace($this->getVendorPath(''), '', $file);
|
||||||
|
}, $files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置基准目录
|
||||||
|
*/
|
||||||
|
private function setBasePath(): void
|
||||||
|
{
|
||||||
|
$base_path = WORKING_DIR;
|
||||||
|
if (file_exists($base_path . '/vendor/autoload.php')) {
|
||||||
|
$this->base_path = $base_path;
|
||||||
|
} else {
|
||||||
|
$phar_link = new \Phar(__DIR__);
|
||||||
|
$current_dir = pathinfo($phar_link->getPath())['dirname'];
|
||||||
|
chdir($current_dir);
|
||||||
|
$phar_link = 'phar://' . $phar_link->getPath();
|
||||||
|
if (file_exists($phar_link . '/vendor/autoload.php')) {
|
||||||
|
$this->base_path = $current_dir;
|
||||||
|
} else {
|
||||||
|
throw new InitException('框架启动模式不是 Composer 模式,无法进行初始化', '如果您是从 Github 下载的框架,请参阅文档进行源码模式启动', 42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取文件
|
||||||
|
*
|
||||||
|
* @param string $file 文件路径,相对于框架根目录
|
||||||
|
* @throws InitException 提取失败时抛出异常
|
||||||
|
*/
|
||||||
|
private function extractFile(string $file): void
|
||||||
|
{
|
||||||
|
$info = pathinfo($file);
|
||||||
|
// 确保目录存在
|
||||||
|
if (
|
||||||
|
!file_exists($this->base_path . $info['dirname'])
|
||||||
|
&& !mkdir($concurrent_dir = $this->base_path . $info['dirname'], 0777, true)
|
||||||
|
&& !is_dir($concurrent_dir)
|
||||||
|
) {
|
||||||
|
throw new InitException("无法创建目录 {$concurrent_dir}", '请检查目录权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy($this->getVendorPath($file), $this->base_path . $file) === false) {
|
||||||
|
throw new InitException("无法复制文件 {$file}", '请检查目录权限');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldExtractFile(string $file): bool
|
||||||
|
{
|
||||||
|
return !file_exists($this->base_path . $file) || $this->force;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getVendorPath(string $file): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true, 512, JSON_THROW_ON_ERROR)['name'];
|
||||||
|
} catch (\JsonException) {
|
||||||
|
throw new InitException('无法读取框架包的 composer.json', '请检查框架包完整性,或者重新安装框架包');
|
||||||
|
}
|
||||||
|
return $this->base_path . '/vendor/' . $package_name . $file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user