控制台与命令行
Viswoole 基于 Symfony Console 组件构建了命令行工具,提供服务控制、代码优化、配置发布等常用命令,并支持自定义命令扩展。
基本用法
# 入口文件
./viswoole
# 查看所有可用命令
./viswoole list
# 查看命令帮助
./viswoole help server:start
./viswoole help --command server:start内置命令
服务控制命令
server:start - 启动服务
# 启动 HTTP 服务(前台运行)
./viswoole server:start
# 指定守护进程模式
./viswoole server:start -d
# 强制启动(忽略已有进程)
./viswoole server:start -f
# 启动指定服务(如 http、websocket)
./viswoole server:start websocketserver:close - 关闭服务
# 关闭默认服务(http)
./viswoole server:close
# 关闭指定服务
./viswoole server:close websocketserver:reload - 重载服务
# 平滑重载(热更新 Worker 进程)
./viswoole server:reload
# 只重载 Task Worker
./viswoole server:reload --task
# 强制重启(关闭所有服务进程后重新启动)
./viswoole server:reload --force
# 强制重启并以守护进程模式运行
./viswoole server:reload --force -dreload vs reload --force:
reload平滑重载不会断开已有连接;reload --force会关闭所有服务进程并重新启动,会短暂中断服务。框架没有独立的restart命令,强制重启请使用server:reload --force。
优化命令
optimize:facade - 优化门面缓存
# 优化指定门面(namespace 为必填参数,需传入 Facade 类的完全限定名称)
./viswoole optimize:facade "Viswoole\Core\Facade\Task"
./viswoole optimize:facade "Viswoole\Cache\Facade\Cache"门面优化会将 Facade 类的方法映射关系以 @method 注解形式写入到 Facade 类文件中,便于 IDE 识别并提供代码提示,减少运行时的反射开销。
发布命令
vendor:publish - 发布配置
# 扫描所有依赖包中 extra.viswoole.configs 声明的配置文件并发布到项目根目录
./viswoole vendor:publish
# 强制覆盖已有文件
./viswoole vendor:publish --force该命令会读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.configs 字段声明的文件或目录,按原目录结构复制到项目根目录。
发现命令
service:discover - 服务发现
# 扫描并发现所有注册的服务提供者
./viswoole service:discovercommand:discover - 命令发现
# 扫描依赖包中声明的命令并生成注册文件
./viswoole command:discover该命令会读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.commands 字段声明的命令类,并生成 vendor/commands.php 注册文件供框架自动加载。
自定义命令
创建命令类
继承 Symfony Console 的 Command 基类,并使用 #[AsCommand] 属性声明命令名称和描述:
namespace App\Console\Commands;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'user:create',
description: '创建新用户'
)]
class UserCreateCommand extends Command
{
/**
* 定义参数和选项
*/
protected function configure(): void
{
$this
// 必填参数
->addArgument('name', InputArgument::REQUIRED, '用户名')
->addArgument('email', InputArgument::REQUIRED, '邮箱地址')
// 可选参数
->addArgument('role', InputArgument::OPTIONAL, '角色', 'user')
// 选项(--flag 形式)
->addOption('password', 'p', InputOption::VALUE_OPTIONAL, '密码')
->addOption('active', null, InputOption::VALUE_NONE, '是否激活')
->addOption('send-email', null, InputOption::VALUE_NONE, '发送欢迎邮件');
}
/**
* 执行命令
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$email = $input->getArgument('email');
$role = $input->getArgument('role');
$password = $input->getOption('password') ?? bin2hex(random_bytes(8));
$isActive = $input->getOption('active');
$sendEmail = $input->getOption('send-email');
// 业务逻辑...
$user = User::create([
'name' => $name,
'email' => $email,
'password' => password_hash($password, PASSWORD_BCRYPT),
'role' => $role,
'is_active' => $isActive,
]);
// 输出结果(使用 SymfonyStyle)
$io->success("用户创建成功: ID={$user->id}");
$io->text("<comment>初始密码: {$password}</comment>");
if ($sendEmail) {
// 发送邮件...
$io->info('欢迎邮件已发送');
}
return Command::SUCCESS; // 返回状态码
}
}注册命令
方式一:配置文件注册
在 config/app.php 的 commands 键中注册命令类:
// config/app.php
return [
// 其他配置...
// 自定义命令注册
'commands' => [
\App\Console\Commands\UserCreateCommand::class,
\App\Console\Commands\GenerateReportCommand::class,
],
];方式二:依赖包自动发现
依赖包可通过 composer.json 的 extra.viswoole.commands 字段声明命令类,框架启动时会自动加载,也可手动执行以下命令重新生成注册文件:
./viswoole command:discover输出方法
框架基于 Symfony Console,推荐在 execute() 中使用 SymfonyStyle 进行格式化输出:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// 成功输出(绿色)
$io->success('操作成功');
// 错误输出(红色)
$io->error('发生错误');
// 警告输出(黄色)
$io->warning('注意事项');
// 普通文本
$io->text('普通文本');
// 注释输出(灰色)
$io->text('<comment>注释文本</comment>');
// 表格输出
$io->table(
['ID', '姓名', '邮箱'],
[
[1, '张三', 'zhangsan@example.com'],
[2, '李四', 'lisi@example.com'],
]
);
// 列表输出
$io->listing([
'步骤一: 准备数据',
'步骤二: 处理逻辑',
'步骤三: 输出结果',
]);
// 进度条
$io->progressStart(100);
for ($i = 0; $i < 100; $i++) {
usleep(10000); // 模拟工作
$io->progressAdvance();
}
$io->progressFinish();
return Command::SUCCESS;
}交互式输入
SymfonyStyle 也提供了一系列交互式输入方法:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// 确认提示
if (!$io->confirm('确定要删除吗?', false)) {
$io->warning('操作已取消');
return Command::INVALID;
}
// 文本输入
$name = $io->ask('请输入用户名', '默认值');
// 密码输入(隐藏显示)
$password = $io->askHidden('请输入密码');
// 选择输入
$role = $io->choice('选择用户角色', ['admin', 'editor', 'user'], 'user');
// 多选
$permissions = $io->choice(
'选择权限(多个用逗号分隔)',
['read', 'write', 'delete', 'admin'],
'read',
true // 允许多选
);
return Command::SUCCESS;
}调用其他命令
在一个命令中调用另一个命令,可通过 Application::find() 获取命令实例后传入新的输入输出执行:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// 调用其他命令
$command = $this->getApplication()->find('optimize:facade');
$returnCode = $command->run(
new \Symfony\Component\Console\Input\ArrayInput([
'namespace' => 'Viswoole\Core\Facade\Task',
]),
$output
);
$io->text("命令返回码: {$returnCode}");
return Command::SUCCESS;
}命令调度(定时任务)
框架内置命令行工具未提供定时任务调度功能。如需实现命令的定时执行,可借助系统 crontab 或 Swoole 的定时器(
Swoole\Timer)自行编排,例如:
# 使用系统 crontab 每天凌晨执行命令
0 0 * * * cd /path/to/project && ./viswoole server:reload --task实战示例
数据库迁移命令
namespace App\Console\Commands;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'db:migrate', description: '执行数据库迁移')]
class MigrateCommand extends Command
{
protected function configure(): void
{
$this
->addOption('path', null, InputOption::VALUE_REQUIRED, '迁移文件路径')
->addOption('fresh', 'f', InputOption::VALUE_NONE, '清空数据库后重建');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$path = $input->getOption('path') ?? base_path('database/migrations');
$fresh = $input->getOption('fresh');
if ($fresh && !$io->confirm('这将清空所有数据,确定继续吗?', false)) {
return Command::INVALID;
}
$files = glob($path . '*.php');
$io->progressStart(count($files));
foreach ($files as $file) {
require_once $file;
$migrationClass = basename($file, '.php');
$migration = new $migrationClass();
$io->text("<comment>正在执行: {$migrationClass}</comment>");
$migration->up();
$io->progressAdvance();
}
$io->progressFinish();
$io->success('迁移完成,共执行 ' . count($files) . ' 个文件');
return Command::SUCCESS;
}
}生成代码脚手架
namespace App\Console\Commands;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'make:controller', description: '创建新的控制器')]
class MakeControllerCommand extends Command
{
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, '控制器名称')
->addOption('resource', 'r', InputOption::VALUE_NONE, '生成资源控制器');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$resource = $input->getOption('resource');
$stubPath = $resource
? base_path('stubs/controller.resource.stub')
: base_path('stubs/controller.plain.stub');
$content = file_get_contents($stubPath);
$content = str_replace('{{ class }}', $name, $content);
$filePath = app_path("Controller/{$name}.php");
if (file_exists($filePath) && !$io->confirm("{$name} 已存在,是否覆盖?", false)) {
return Command::INVALID;
}
file_put_contents($filePath, $content);
$io->success("控制器已创建: {$filePath}");
return Command::SUCCESS;
}
}命令最佳实践
1. 状态码规范
return Command::SUCCESS; // 0 - 成功
return Command::FAILURE; // 1 - 一般性失败
return Command::INVALID; // 2 - 参数无效2. 异常处理
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
try {
$this->doWork();
return Command::SUCCESS;
} catch (ValidationException $e) {
$io->error('验证失败: ' . $e->getMessage());
return Command::INVALID;
} catch (\Throwable $e) {
$io->error('执行出错: ' . $e->getMessage());
if ($input->getOption('verbose')) {
$io->text('<comment>' . $e->getTraceAsString() . '</comment>');
}
return Command::FAILURE;
}
}3. 幂等性设计
命令应支持多次执行而不产生副作用:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// 检查是否已执行过
if ($this->hasAlreadyRun()) {
$io->info('已经执行过,跳过');
return Command::SUCCESS;
}
// 执行逻辑...
$this->markAsDone();
return Command::SUCCESS;
}