Webman 容器依赖注入
Webman 容器依赖注入学习笔记
项目:网盘(Webman)
实例:VideoController←VideoService
1. 先记住四句话
- 容器在
config/container.php里登记对象创建规则。 - 第一次需要某个对象时,容器执行规则并创建对象。
- 容器把创建好的依赖传给构造函数。
- 控制器保存依赖,在处理 HTTP 请求时使用它。
简化版:
$videoService = $container->get(VideoService::class);
$controller = new VideoController($videoService);
$response = $controller->videos($request);容器没有改变业务逻辑,它解决的是:对象由谁创建、在哪里组装、如何交给使用者。
2. 为什么控制器需要 VideoService
路由 GET /api/videos 最终会进入:
public function videos(Request $request): Response
{
$result = $this->videoService->paginate($page, $pageSize);
return $this->jsonResponse(0, '获取成功', $result);
}职责划分:
| 对象 | 职责 |
|---|---|
VideoController | 接收 HTTP 请求、校验参数、返回 HTTP 响应 |
VideoService | 视频分页等业务逻辑 |
依赖 = 一个对象完成工作时需要另一个对象。
VideoController → 依赖 → VideoService3. 核心区别:谁创建依赖
传统写法:控制器自己创建
final class VideoController
{
private VideoService $videoService;
public function __construct()
{
$this->videoService = new VideoService(
new VideoFileValidator(),
new ObjectKeyFactory(),
new AliyunStsCredentialIssuer(),
new UploadCredentialGrant(...),
new AliyunOssStorage(),
new EloquentVideoRepository()
);
}
}控制器同时负责:
- 创建
VideoService - 组装
VideoService的全部依赖 - 使用
VideoService
问题:控制器必须知道 OSS 用哪个实现、数据库仓库怎么建、配置从哪读——耦合紧、难测试、难替换。
容器写法:外部创建,控制器只使用
// 控制器只声明需要什么
public function __construct(
private VideoService $videoService,
?Closure $logWriter = null
) {
$this->logWriter = $logWriter ?? static fn (...) => Log::error(...);
}// 容器负责怎么创建
VideoController::class => static fn (Container $container): VideoController =>
new VideoController(
$container->get(VideoService::class)
),职责变成:
Container
├── 创建 VideoService 及其依赖
└── 传给 VideoController
VideoController
└── 只使用 VideoService一句话区别:
| 写法 | 含义 |
|---|---|
控制器自己 new | 我要用它,所以我也要负责创建它 |
| 容器依赖注入 | 我只声明需要它,外部创建好以后传给我 |
4. 容器配置在哪
文件:config/container.php
$container = new Container();
$container->addDefinitions([
ObjectStorage::class =>
static fn (Container $container): ObjectStorage => new AliyunOssStorage(),
VideoRepository::class =>
static fn (Container $container): VideoRepository => new EloquentVideoRepository(),
VideoService::class => static fn (Container $container): VideoService => new VideoService(
new VideoFileValidator(),
new ObjectKeyFactory(),
$container->get(TemporaryCredentialIssuer::class),
new UploadCredentialGrant((string) config('oss.access_key_secret')),
$container->get(ObjectStorage::class),
$container->get(VideoRepository::class)
),
VideoController::class => static fn (Container $container): VideoController =>
new VideoController(
$container->get(VideoService::class)
),
]);
return $container;要点:
addDefinitions()只登记规则,不会立刻创建所有对象。- 程序启动 → 创建容器 → 登记「说明书」→ 等第一次
$container->get(...)时才真正创建。 - 同一类再次
get()时,容器通常返回已缓存的同一实例。
4.1 webman 什么时候创建容器
- Webman 是基于 Workerman 的常驻内存框架。容器不是每次 HTTP 请求到达时才创建,而是在 Worker 进程启动时创建。
项目入口:
// start.php
require_once __DIR__ . '/vendor/autoload.php';
support\App::run();Webman 启动服务器后,会为每个 Worker 执行启动代码:
$worker->onWorkerStart = function ($worker) {
require_once base_path() . '/support/bootstrap.php';
$app = new \Webman\App(...);
$worker->onMessage = [$app, 'onMessage'];
};support/bootstrap.php 会加载项目配置:
Config::clear();
support\App::loadAllConfig(['route']); // 批量加载配置,但跳过 route.php(路由会在容器就绪后单独加载)
这里排除了 route.php,但没有排除 container.php,所以 Webman 的配置加载器会执行:
config/container.php执行这个文件时,才真正运行:
$container = new Container();
这一步创建容器对象。
然后执行:
$container->addDefinitions([
// 对象创建规则
]);- 创建完成的对象会保存在当前 Worker 的容器中,后续请求可以复用。
route.php 会在 App::run() 中通过 loadRouteConfig() 单独加载,确保此时容器已经初始化完成。
5. 两个 => 分别是什么
VideoService::class => static fn (Container $container): VideoService => new VideoService(...)| 位置 | 语法 | 含义 |
|---|---|---|
第一个 => | 数组键值 | VideoService::class 对应右边的创建函数 |
第二个 => | 箭头函数 | static fn (参数): 返回类型 => 返回结果 |
等价展开:
VideoService::class => static function (Container $container): VideoService {
return new VideoService(...);
},static 不是提前加载,不是立即创建对象。它表示匿名函数不绑定 $this。真正创建发生在容器执行这个函数的时候。
6. 一次请求的完整流程
以 GET /api/videos 为例:
浏览器请求 GET /api/videos
↓
路由匹配 [VideoController::class, 'videos']
↓
Webman: $container->get(VideoController::class)
↓
容器查规则 → 需要先 get(VideoService::class)
↓
容器执行 VideoService 创建规则(及其子依赖)
↓
$videoService = 已创建的 VideoService 对象
↓
容器执行: new VideoController($videoService)
↓
PHP 自动调用 __construct(),保存到 $this->videoService
↓
容器把 VideoController 对象返回给 Webman
↓
路由从 [VideoController::class, 'videos'] 变成 [$controller, 'videos']
↓
Webman 调用 $controller->videos($request)
↓
$this->videoService->paginate(...)
↓
控制器返回 Response → Webman 返回给浏览器6.1 容器如何把服务传给控制器
容器配置展开后等价于:
static function (Container $container): VideoController {
$videoService = $container->get(VideoService::class);
$controller = new VideoController($videoService);
return $controller;
}| 谁负责 | 做什么 |
|---|---|
| 容器 | 取得 VideoService → 执行 new VideoController($videoService) → 返回控制器 |
| PHP | 自动调用 __construct() → 把参数对应到 $videoService → 保存到 $this->videoService |
构造函数不会自己去容器取对象,它只接收参数。是容器配置里写了 $container->get(VideoService::class)。
6.2 PHP 如何保存传入的服务(构造函数属性提升)
当前写法(PHP 8):
public function __construct(
private VideoService $videoService,
?Closure $logWriter = null
) { ... }等价于传统写法:
private VideoService $videoService;
public function __construct(VideoService $videoService, ?Closure $logWriter = null)
{
$this->videoService = $videoService;
}这是 PHP 8 语法,不是容器语法。控制器保存的是同一个 VideoService 对象引用,不是复制。(控制器属性和容器中的变量指向同一个 VideoService 对象,并没有复制出第二个对象。)
6.3 控制器创建后如何继续执行
Webman 拿到控制器对象后:
[$controller, 'videos']($request);
// 等价于
$controller->videos($request);方法内 $this->videoService 就是构造函数里保存的那个对象。
7. 容器依赖注入的价值
| 维度 | 控制器自己 new | 容器注入 |
|---|---|---|
| 创建责任 | 控制器创建全部依赖 | 容器集中创建和组装 |
| 耦合 | 直接依赖具体实现类 | 可依赖接口,实现集中在容器配置 |
| 换实现 | 改控制器 | 只改 container.php 映射 |
| 测试 | 难以替换真实服务 | new VideoController($mockService) |
| 配置位置 | 可能散落在各处 | 集中在 config/container.php |
代价:多一层容器配置,初次阅读时调用链需要多跟一步。
8. 常见误区
| 误区 | 正解 |
|---|---|
| 构造函数会自动去容器取对象? | 不会。是容器配置里显式 $container->get(...) 后传给构造函数 |
private VideoService $videoService 是容器语法? | 不是。是 PHP 8 构造函数属性提升 |
static fn 表示初始化时就创建? | 不是。创建函数被容器执行时才创建对象 |
return new VideoService(...) 直接给浏览器? | 不是。先给 $container->get(),再作为构造参数;只有控制器方法返回的 Response 才给浏览器 |
9. 和本项目相关的文件
| 文件 | 作用 |
|---|---|
config/container.php | 对象创建规则 |
config/route.php | 路由到 [VideoController::class, 'videos'] |
app/controller/VideoController.php | 声明依赖、处理 HTTP |
app/service/VideoService.php | 业务逻辑 |
app/contract/ObjectStorage.php 等 | 接口,容器可绑定不同实现 |
10. 总结对照图
传统写法:
VideoController
├── 创建 VideoService
├── 创建 OSS / 仓库 / 凭证等全部依赖
└── 使用 VideoService容器依赖注入:
Container
├── 创建并组装 VideoService
└── 传给 VideoController
VideoController
└── 使用 VideoService核心结论:
- 容器负责:创建对象、组装依赖、传给构造函数。
- PHP 负责:PHP 负责调用构造函数;对于构造函数属性提升参数,PHP 会自动声明属性并完成赋值。
- 控制器负责:处理 HTTP 请求,调用业务服务。
版权所有
版权归属:念宇
