容器(Container)------ 对象的"智能工厂+调度官"
🌱 一、容器是"自动组装零件的工厂"
❓ 传统写法 vs 容器写法
传统写法(手动 new)
ini
// 手动创建依赖链
$db = new Database();
$cache = new Cache();
$orderService = new OrderService($db, $cache);
$controller = new OrderController($orderService);
容器写法(自动组装)
php
// 容器自动创建依赖链
$controller = Container::make(OrderController::class);
区别:
- 传统写法:需要手动创建每个依赖对象
- 容器写法:只需指定根类,容器自动解析并创建整个依赖链
🧠 二、容器的底层原理(精准参考 ThinkPHP 源码)
1. 调用 Container::make(OrderController::class) 的全过程
步骤 1:获取别名(处理类别名)
kotlin
// 容器源码关键逻辑
$abstract = $this->getAlias($abstract); // 处理别名(如 App 别名 think\App)
// 例如:OrderController 可能被绑定为 'order.controller'
// getAlias() 会递归解析别名链,直到得到真实类名
步骤 2:检查单例缓存(避免重复创建)
kotlin
// 容器源码关键逻辑
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract]; // 直接返回已存在的单例
}
步骤 3:检查闭包绑定(自定义创建逻辑)
kotlin
// 容器源码关键逻辑
if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
$object = $this->invokeFunction($this->bind[$abstract], $vars);
}
步骤 4:反射创建实例(核心逻辑)
php
// 容器源码关键逻辑
else {
$object = $this->invokeClass($abstract, $vars);
}
// invokeClass 源码关键逻辑:
protected function invokeClass(string $class, array $vars = [])
{
$reflector = new ReflectionClass($class);
$constructor = $reflector->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
return $reflector->newInstanceArgs($args);
}
步骤 5:递归解析依赖(核心!)
php
// bindParams 源码关键逻辑(处理构造函数参数)
protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
{
$params = $reflect->getParameters();
$args = [];
foreach ($params as $param) {
$name = $param->getName();
$type = $param->getType()?->getName();
// 1. 尝试从 $vars 获取参数值(如:$vars['db'])
// 2. 如果没有,递归创建依赖($this->make($type))
// 3. 如果没有默认值,报错
$args[] = $this->resolveParam($type, $name, $vars);
}
return $args;
}
// 递归创建依赖的关键方法
protected function resolveParam(string $type, string $name, array $vars): mixed
{
// 1. 优先从 $vars 中匹配(如 $vars['db'])
if (array_key_exists($name, $vars)) {
return $vars[$name];
}
// 2. 尝试下划线匹配(如 $vars['db_connection'])
if (array_key_exists(Str::snake($name), $vars)) {
return $vars[Str::snake($name)];
}
// 3. 递归创建依赖(核心!)
return $this->make($type);
}
📌 最终精准流程:
css
Container::make(OrderController::class)
→ getAlias() 处理别名
→ 检查单例缓存
→ 检查闭包绑定
→ invokeClass()
→ ReflectionClass 获取类
→ bindParams() 解析参数
→ 递归调用 make() 创建依赖
→ newInstanceArgs() 创建实例
→ 单例缓存($instances[$abstract] = $object)
→ 返回对象
🔍 三、关键原理小备注(精准匹配源码)
1. ThinkPHP 容器核心设计亮点
| 源码位置 | 功能 | 作用 |
|---|---|---|
getAlias() |
别名解析 | 处理 Container::bind('app', App::class) 中的别名链 |
bind[] |
绑定存储 | 存储类名、闭包、实例的映射关系 |
instances[] |
单例缓存 | 避免重复创建相同实例 |
bindParams() |
参数绑定 | 自动匹配参数名和传入变量 |
make() |
核心入口 | 递归解析依赖的入口方法 |
2. 为什么需要递归创建依赖?
php
// OrderController 依赖 OrderService
class OrderController {
public function __construct(OrderService $orderService) {}
}
// OrderService 依赖 Database 和 Cache
class OrderService {
public function __construct(Database $db, Cache $cache) {}
}
// 容器自动解析:
Container::make(OrderController::class)
→ 创建 OrderController 时
→ 递归创建 OrderService
→ 递归创建 Database 和 Cache
3. 关键代码对比(ThinkPHP 源码 vs 文章描述)
| ThinkPHP 源码 | 文章描述 | 优化说明 |
|---|---|---|
$this->bindParams($constructor, $vars) |
递归调用 make() |
精准描述参数绑定机制 |
优先匹配 $vars 中的参数 |
未提及参数匹配 | 补充关键细节 |
使用 Str::snake() 处理下划线 |
未提及 | 补充命名转换逻辑 |
| 递归创建依赖 | 仅提"递归" | 明确"递归调用 make" |
⚠️ 四、容器 vs 工厂模式(精准对比)
| 特性 | 容器 | 工厂模式 |
|---|---|---|
| 依赖解析 | ✅ 自动递归解析所有依赖(反射+递归) | ❌ 需手动实现每个依赖 |
| 灵活性 | ✅ 支持任意类(无需预定义) | ❌ 需为每个产品实现工厂 |
| 通用性 | ✅ 无需预定义 | ❌ 每个产品需工厂类 |
| 依赖注入 | ✅ 原生支持 | ❌ 需额外实现 |
| 单例管理 | ✅ 内置单例缓存 | ❌ 需手动实现 |
| 别名系统 | ✅ 支持别名(Container::bind('app', App::class)) |
❌ 无别名机制 |
📌 容器 = 超级工厂 + 依赖注入引擎 + 单例管理器
✅ 五、门面与容器的协同关系(精准补充)
门面如何利用容器?
php
// 门面类(Log)
class Log extends Facade {
protected static function getFacadeClass() {
return Logger::class; // 返回真实类名
}
}
// Facade 源码
abstract class Facade {
public static function __callStatic($method, $args) {
$instance = static::resolveFacadeInstance(); // 关键!
return $instance->$method(...$args);
}
protected static function resolveFacadeInstance() {
$serviceId = static::getFacadeClass(); // 获取真实类名
return Container::getInstance()->make($serviceId); // 容器创建实例
}
}
关键协同点:
- 门面调用
Log::log()触发__callStatic resolveFacadeInstance()通过容器创建Logger实例- 容器自动解析
Logger的依赖(如Database)
✅ 门面是容器的"静态代理" :通过静态方法调用,背后完全依赖容器创建实例
🎯 六、总结:容器的精准定位
容器是 ThinkPHP 的"依赖注入引擎"
门面是容器的"静态访问入口"
两者协同工作:门面 → 容器 → 实例
| 位置 | 作用 | 依赖 |
|---|---|---|
| 门面类 | 提供静态调用接口 | 容器 |
| 容器 | 解析依赖链、创建实例 | 无 |
| 控制器 | 接收依赖 | 容器(通过构造函数注入) |
容器核心价值 :
✅ 自动解析依赖链
✅ 管理单例生命周期
✅ 支持别名系统
✅ 无缝集成门面系统
容器不是简单的"工厂",而是依赖注入的智能引擎
它让 ThinkPHP 的"依赖注入"成为可能,而门面只是它优雅的静态接口