服务提供者

服务提供者(Service Provider)是框架的组织枢纽:所有框架能力(日志、缓存、路由、HTTP、数据库、任务等)都以服务提供者的形式装载进应用。当你编写可复用的业务模块或第三方扩展包时,也应遵循同样的模式——本篇介绍 Provider 的编写规范、注册方式与依赖包的自动发现机制。

服务装载流程

应用启动时,App 容器按以下顺序合并服务提供者列表:

  1. 框架默认服务LogServiceCacheServiceMiddlewareServiceRouterServiceHttpServiceDbService
  2. 项目配置config/app.phpservices[]
  3. 依赖包vendor/services.php(由 service:discover 命令生成,见下文)。

合并后的每个服务提供者按两阶段装载:

  • 第一阶段:register()——遍历执行所有 Provider 的 register(),把绑定写入容器;同时 Provider 的 $bindings 属性也会被合并进容器绑定映射;
  • 第二阶段:boot()——所有 Provider 注册完成后,再依次执行各 Provider 的 boot(),用于解析或启动已注册的服务(如任务管理器在 boot 中被 make 出来以挂载事件)。
php
// 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 容器实例。

php
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') 启动任务管理器:

php
#[Override] public function boot(): void
{
  // 启动任务管理器(挂载任务分发事件)
  $this->app->make('task');
}

boot 中不要做重活或异步任务

boot() 在进程启动的关键路径上同步执行。无状态的业务服务应将 boot() 留空;需要「Worker 启动时执行一次」的逻辑(注册定时器、预热缓存等)请挂到 workerStart 钩子,见 生命周期钩子

注册与生效

将 Provider 类加入 config/app.phpservices[] 即完成注册。

修改服务提供者后需完整重启

服务提供者在服务启动时实例化并常驻进程内存,server:reload 平滑重载不会重新执行 register() / boot()。修改服务提供者代码后,请先 php viswoole server:closephp viswoole server:start 完整重启,命令用法见 命令行

依赖包集成(服务发现)

扩展包作者可以把服务提供者与配置资源声明在包的 composer.json 中,由项目侧的发现命令批量落地:

json
{
  "extra": {
    "viswoole": {
      "services": ["\Vendor\Package\PackageServiceProvider"],
      "configs": ["config"]
    }
  }
}

service:discover —— 发现服务

bash
php viswoole service:discover

读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.services 字段声明服务提供者类,生成项目根目录的 vendor/services.php 注册文件。应用启动时该文件会被自动加载合并,无需手动编辑。

执行时机

执行 composer update / composer require 安装新依赖包后,需要重新执行 service:discover 更新注册文件。

vendor:publish —— 发布资源

bash
php viswoole vendor:publish          # 已存在的目标文件默认跳过
php viswoole vendor:publish --force  # 强制覆盖(-f 简写)

读取 vendor/composer/installed.json,扫描各依赖包 extra.viswoole.configs 字段声明的文件或目录,按原目录层级复制到项目根目录。框架自身的默认配置(config/ 目录)就是通过该机制发布的——在项目根执行一次即可得到全部默认配置文件。

完整示例

以订单模块为例,组织「服务 + 服务提供者 + 注册」三件套:

php
namespace App\Service;

/**
 * 订单服务:无状态,进程级单例
 */
final readonly class OrderService
{
  /**
   * 创建订单号
   */
  public function makeOrderNo(): string
  {
    return date('YmdHis') . mt_rand(1000, 9999);
  }
}
php
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
  {
  }
}
php
// config/app.php
'services' => [
  \Viswoole\Core\Service\TaskService::class,
  \App\Service\UserServiceProvider::class,
  \App\Service\OrderServiceProvider::class,
],

注册后在控制器、任务处理器等任意位置通过容器解析使用:

php
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()];
  }
}

下一步

  • 命令行service:discovervendor:publish 等命令的完整参数说明
  • 容器bind() / make() / 请求级隔离的底层机制
  • 配置文件:服务提供者依赖的各类配置的组织方式