传统web服务改造成hyperf cli服务的核心是切换运行模型:从fpm的每次请求fork进程,转向swoole常驻内存、协程调度、事件驱动;http上下文对象(request/response/session)在cli中不可用,路由、中间件、控制器注解须剥离,命令类需独立实现并显式注入依赖,禁用http-server组件以避免端口占用与资源浪费。

传统 Web 服务改造成 Hyperf 常驻 CLI 服务,核心不是“迁移代码”,而是切换运行模型:从每次请求 fork 进程/加载全部依赖的 FPM 模式,转向常驻内存、协程调度、事件驱动的 Swoole 模式。直接复用原有控制器或路由逻辑大概率失败——因为 CLI 场景下没有 HTTP 请求上下文,request、response、session 等对象不可用。
确认哪些代码能直接复用
Hyperf 的 CLI 服务和 HTTP 服务共享同一套 DI 容器、配置系统、数据库连接池、缓存客户端等基础设施。但业务逻辑层需按职责拆分:
- 纯数据处理逻辑(如计算订单金额、解析 Excel、生成 PDF)——可直接复用,只要不依赖
$this->request或Context::get()等 HTTP 上下文 - 数据库操作(
Db::query、Eloquent Model)——完全可用,前提是已启用hyperf/database并正确配置 - 日志、缓存、消息队列(
LoggerFactory、RedisFactory、AmqpProducer)——均可直接注入使用 - HTTP 路由、中间件、控制器注解(
@AutoController、@Middleware)——在 CLI 命令中无意义,必须剥离
CLI 命令类必须独立实现,不能复用 Controller
Hyperf 的 Command 类与 Controller 是两条平行线。试图把 IndexController 改成命令类,或在 handle() 中调用 IndexController::index(),会因缺失请求上下文而报错(如 InvalidArgumentException: Request not found in context)。
正确做法是新建命令类,显式注入所需依赖:
use Hyperf\Command\Command;
use Hyperf\Database\Connection;
use Psr\Container\ContainerInterface;
#[AsCommand(name: 'order:sync', description: 'Sync overdue orders to ERP')]
class OrderSyncCommand extends Command
{
protected Connection $db;
public function __construct(ContainerInterface $container)
{
parent::__construct();
$this->db = $container->get(Connection::class);
}
public function handle()
{
$rows = $this->db->table('orders')->where('status', 'overdue')->get();
foreach ($rows as $row) {
// 调用 ERP SDK 推送
}
$this->line('Done.');
}
}
注意:handle() 方法内禁止调用 $this->request 或任何基于 HTTP 上下文的方法。
避免在 CLI 中误用协程生命周期
CLI 命令默认在协程中执行,但常见误区是认为“启动了协程就自动并发”。实际需主动开启:
- 单次执行:
go(fn() => $this->doWork())启动一个协程,但主线程仍会等待它结束 - 批量并发:用
Co::create或Parallel组件(hyperf/parallel),否则循环里写go()可能因未 await 导致提前退出 - 数据库连接池:CLI 下仍受
max_connections限制,高并发批量任务需控制协程数,避免Too many connections - 全局状态污染:CLI 常驻进程若缓存了静态变量或单例状态(如
static $cache = []),多次执行命令时可能读到旧数据 —— 应在handle()开头重置或使用make()重新获取实例
部署时必须禁用 HTTP 服务器组件
传统 Web 项目通常同时启用 hyperf/http-server 和 hyperf/command。但在纯 CLI 场景下保留 http-server 会导致:
- 启动时监听 9501 端口,占用资源且无意义
- 若配置了健康探针(livenessProbe),K8s 会误判 CLI Pod 为“不可用”
- 内存持续增长(Swoole Worker 进程常驻)
解决方案是分离启动入口:在 bin/hyperf.php 中判断运行模式:
if (in_array('cli', $argv)) {
\Hyperf\Contract\ApplicationInterface::START_COMMAND_ONLY;
} else {
\Hyperf\Contract\ApplicationInterface::START_HTTP_SERVER;
}
或更稳妥的方式:构建两个不同镜像,CLI 镜像中 composer remove hyperf/http-server,彻底移除 HTTP 服务依赖。
真正容易被忽略的是 CLI 命令的退出码和错误传播——Hyperf 默认将异常转为 exit(1),但若命令内部调用外部 HTTP API 失败,需手动 exit(2) 区分网络错误;K8s Job 的 backoffLimit 依赖这个退出码做重试判断,不能只靠日志。











