服务提供者
服务提供者(Service Provider)是框架的组织枢纽:所有框架能力(日志、缓存、路由、HTTP、数据库、任务等)都以服务提供者的形式装载进应用。当你编写可复用的业务模块或第三方扩展包时,也应遵循同样的模式——本篇介绍 Provider 的编写规范、注册方式与依赖包的自动发现机制。
服务装载流程
应用启动时,App 容器按以下顺序合并服务提供者列表:
- 框架默认服务:
LogService、CacheService、MiddlewareService、RouterService、HttpService、DbService; - 项目配置:
config/app.php的services[]; - 依赖包:
vendor/services.php(由service:discover命令生成,见下文)。
合并后的每个服务提供者按两阶段装载:
- 第一阶段:
register()——遍历执行所有 Provider 的register(),把绑定写入容器;同时 Provider 的$bindings属性也会被合并进容器绑定映射; - 第二阶段:
boot()——所有 Provider 注册完成后,再依次执行各 Provider 的boot(),用于解析或启动已注册的服务(如任务管理器在 boot 中被make出来以挂载事件)。
// config/app.php
return [
// ...其他配置
'services' => [
// 框架默认注册的异步任务服务,不使用任务功能可以删除
\Viswoole\Core\Service\TaskService::class,
// 新增的项目服务
\App\Service\UserServiceProvider::class,
],
];有依赖的服务排前面
services[] 的数组顺序即注册顺序,且所有 register() 都先于 boot() 执行。如果服务 A 在 register() 或 boot() 中依赖服务 B 的绑定,请把 B 排在 A 前面。
编写服务提供者
自定义 Provider 继承抽象基类 Viswoole\Core\Service\Provider,实现 register() 与 boot() 两个抽象方法。Provider 构造函数由框架注入 App 容器实例。
namespace App\Service;
use Override;
use Viswoole\Core\Service\Provider;
/**
* 用户模块服务:全模块共享的无状态服务
*/
final readonly class UserService
{
public function getNickname(int $uid): string
{
return 'user-' . $uid;
}
}
final class UserServiceProvider extends Provider
{
/**
* 注册绑定:无状态服务显式 new 后绑定实例
*/
#[Override] public function register(): void
{
$this->app->bind(UserService::class, new UserService());
}
/**
* 无状态服务无需启动逻辑,boot 留空
*/
#[Override] public function boot(): void
{
}
}register():只做绑定
register() 的职责是把服务绑定到容器,要点:
- 无状态服务显式
new后绑定实例:如上例$this->app->bind(UserService::class, new UserService()),避免依赖自动解析的不确定性; - 服务声明为
final readonly——绑定实例后它将作为进程级单例被所有请求共享,因此服务内部不得保存请求级状态(协程环境下容器按请求隔离的说明见 容器); - 需要请求级状态的服务,绑定类名而非实例(
$this->app->bind(Foo::class, Foo::class)),由容器在使用时按请求解析; - 批量绑定可使用 Provider 的
public array $bindings属性(键为服务标识,值为实现类名),框架会在register()之前合并它。
boot():只做启动
boot() 在所有服务注册完成后执行,用于初始化或解析已注册的服务,例如任务服务就是在这里 $this->app->make('task') 启动任务管理器:
#[Override] public function boot(): void
{
// 启动任务管理器(挂载任务分发事件)
$this->app->make('task');
}boot 中不要做重活或异步任务
boot() 在进程启动的关键路径上同步执行。无状态的业务服务应将 boot() 留空;需要「Worker 启动时执行一次」的逻辑(注册定时器、预热缓存等)请挂到 workerStart 钩子,见 生命周期钩子。
注册与生效
将 Provider 类加入 config/app.php 的 services[] 即完成注册。
修改服务提供者后需完整重启
服务提供者在服务启动时实例化并常驻进程内存,server:reload 平滑重载不会重新执行 register() / boot()。修改服务提供者代码后,请先 php viswoole server:close 再 php viswoole server:start 完整重启,命令用法见 命令行。
依赖包集成(服务发现)
扩展包作者可以把服务提供者与配置资源声明在包的 composer.json 中,由项目侧的发现命令批量落地:
{
"extra": {
"viswoole": {
"services": ["\Vendor\Package\PackageServiceProvider"],
"configs": ["config"]
}
}
}service:discover —— 发现服务
php viswoole service:discover读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.services 字段声明服务提供者类,生成项目根目录的 vendor/services.php 注册文件。应用启动时该文件会被自动加载合并,无需手动编辑。
执行时机
执行 composer update / composer require 安装新依赖包后,需要重新执行 service:discover 更新注册文件。
vendor:publish —— 发布资源
php viswoole vendor:publish # 已存在的目标文件默认跳过
php viswoole vendor:publish --force # 强制覆盖(-f 简写)读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.configs 字段声明的文件或目录,按原目录层级复制到项目根目录。框架自身的默认配置(config/ 目录)就是通过该机制发布的——在项目根执行一次即可得到全部默认配置文件。
完整示例
以订单模块为例,组织「服务 + 服务提供者 + 注册」三件套:
namespace App\Service;
/**
* 订单服务:无状态,进程级单例
*/
final readonly class OrderService
{
/**
* 创建订单号
*/
public function makeOrderNo(): string
{
return date('YmdHis') . mt_rand(1000, 9999);
}
}namespace App\Service;
use Override;
use Viswoole\Core\Service\Provider;
final class OrderServiceProvider extends Provider
{
#[Override] public function register(): void
{
$this->app->bind(OrderService::class, new OrderService());
}
#[Override] public function boot(): void
{
}
}// config/app.php
'services' => [
\Viswoole\Core\Service\TaskService::class,
\App\Service\UserServiceProvider::class,
\App\Service\OrderServiceProvider::class,
],注册后在控制器、任务处理器等任意位置通过容器解析使用:
use App\Service\OrderService;
use Viswoole\Core\Router\Annotation\RouteMapping;
class OrderController
{
#[RouteMapping(method: 'POST', title: '创建订单')]
public function create(OrderService $service): array
{
// 类型注入:容器按绑定解析出进程级单例
return ['order_no' => $service->makeOrderNo()];
}
}