依赖注入
最后更新: 2026-01-27 11:02:47依赖注入
Unicode Framework 提供了强大的依赖注入容器,支持自动依赖解析、服务绑定和单例模式。
容器基础
获取容器实例
use Unicode\Framework\App;
$app = App::getInstance();
$container = $app->getBootstrappedContainer();
基本操作
// 绑定服务
$container->bind('service', function($container) {
return new Service();
});
// 获取服务
$service = $container->get('service');
// 检查服务是否存在
if ($container->has('service')) {
$service = $container->get('service');
}
服务绑定
绑定(每次创建新实例)
$container->bind('logger', function($container) {
return new Logger();
});
// 每次获取都是新实例
$logger1 = $container->get('logger');
$logger2 = $container->get('logger');
// $logger1 !== $logger2
单例绑定(延迟加载)
$container->singleton('logger', function($container) {
return new Logger();
}, deferred: true);
// 首次获取时创建,之后返回同一实例
$logger1 = $container->get('logger');
$logger2 = $container->get('logger');
// $logger1 === $logger2
单例绑定(立即加载)
$container->singleton('logger', function($container) {
return new Logger();
}, deferred: false);
// 注册时立即创建
$logger = $container->get('logger');
实例绑定
$logger = new Logger();
$container->instance('logger', $logger);
// 直接使用已存在的实例
$logger = $container->get('logger');
别名
// 绑定服务
$container->bind('app.logger', function($container) {
return new Logger();
});
// 注册别名
$container->alias('logger', 'app.logger');
// 使用别名获取
$logger = $container->get('logger');
依赖解析
自动解析
容器可以自动解析类的依赖:
class UserService
{
public function __construct(
private Logger $logger,
private Cache $cache,
) {}
}
// 自动解析依赖
$userService = $container->get(UserService::class);
// 容器会自动创建 Logger 和 Cache 实例
类型提示
容器通过类型提示自动解析依赖:
class OrderService
{
public function __construct(
private UserService $userService, // 自动解析
private PaymentService $paymentService, // 自动解析
) {}
}
$orderService = $container->get(OrderService::class);
接口绑定
// 绑定接口到实现
$container->bind(LoggerInterface::class, function($container) {
return new FileLogger();
});
// 使用接口类型提示
class UserService
{
public function __construct(
private LoggerInterface $logger, // 自动解析为 FileLogger
) {}
}
原始类型参数
对于原始类型参数,需要手动绑定:
class ConfigService
{
public function __construct(
private string $configPath, // 原始类型,需要手动绑定
) {}
}
// 手动绑定
$container->bind(ConfigService::class, function($container) {
return new ConfigService('/path/to/config');
});
服务提供者
创建服务提供者
namespace App\Providers;
use Unicode\Framework\Interfaces\ServiceProviderInterface;
use Unicode\Framework\Container\Container;
use Unicode\Framework\Config\Config;
class AppServiceProvider implements ServiceProviderInterface
{
public function register(Container $container, Config $config): void
{
// 注册服务
$container->singleton('app.logger', function($container) {
return new Logger();
});
}
public function boot(Container $container, Config $config): void
{
// 启动服务(所有服务注册完成后调用)
}
}
注册服务提供者
use App\Providers\AppServiceProvider;
$app = App::getInstance();
$app->getProviderRegistry()->register(new AppServiceProvider());
延迟加载
服务提供者可以标记为延迟加载:
class LazyServiceProvider implements ServiceProviderInterface
{
public function isDeferred(): bool
{
return true; // 延迟加载
}
public function provides(): array
{
return ['lazy.service']; // 提供的服务列表
}
public function register(Container $container, Config $config): void
{
$container->singleton('lazy.service', function($container) {
return new LazyService();
});
}
}
容器方法
get()
获取服务实例:
$service = $container->get('service');
$service = $container->get(ServiceClass::class);
has()
检查服务是否存在:
if ($container->has('service')) {
$service = $container->get('service');
}
bind()
绑定服务(每次创建新实例):
$container->bind('service', function($container) {
return new Service();
});
singleton()
绑定单例服务:
// 延迟加载
$container->singleton('service', function($container) {
return new Service();
}, deferred: true);
// 立即加载
$container->singleton('service', function($container) {
return new Service();
}, deferred: false);
instance()
注册已存在的实例:
$service = new Service();
$container->instance('service', $service);
alias()
注册别名:
$container->alias('short.name', 'long.service.name');
$service = $container->get('short.name');
在控制器中使用
构造函数注入
namespace App\Index\Controller;
use App\Service\UserService;
class UserController
{
public function __construct(
private UserService $userService,
) {}
public function index(): array
{
$users = $this->userService->getAll();
return ['users' => $users];
}
}
方法注入
框架支持在控制器方法中注入依赖:
class UserController
{
public function show(Request $request, UserService $userService, int $id): array
{
$user = $userService->find($id);
return ['user' => $user];
}
}
最佳实践
1. 使用接口绑定
// ✅ 推荐:绑定接口到实现
$container->bind(LoggerInterface::class, function($container) {
return new FileLogger();
});
// 使用接口类型提示
class UserService
{
public function __construct(
private LoggerInterface $logger,
) {}
}
// ❌ 错误:直接绑定实现类
$container->bind(FileLogger::class, function($container) {
return new FileLogger();
});
2. 单例服务
// ✅ 推荐:无状态服务使用单例
$container->singleton('cache', function($container) {
return new RedisCache();
});
// ❌ 错误:有状态服务使用单例
$container->singleton('request', function($container) {
return new Request(); // 每个请求应该有不同的实例
});
3. 服务提供者组织
// ✅ 推荐:按功能模块组织服务提供者
class DatabaseServiceProvider implements ServiceProviderInterface
{
public function register(Container $container, Config $config): void
{
$container->singleton('db', function($container) {
return new DatabaseManager();
});
}
}
class CacheServiceProvider implements ServiceProviderInterface
{
public function register(Container $container, Config $config): void
{
$container->singleton('cache', function($container) {
return new RedisCache();
});
}
}
4. 避免循环依赖
// ❌ 错误:循环依赖
class ServiceA
{
public function __construct(private ServiceB $b) {}
}
class ServiceB
{
public function __construct(private ServiceA $a) {}
}
// ✅ 正确:使用接口或事件解耦
class ServiceA
{
public function __construct(private EventDispatcher $events) {}
}
class ServiceB
{
public function __construct(private EventDispatcher $events) {}
}
5. 延迟加载
// ✅ 推荐:大型服务使用延迟加载
class HeavyServiceProvider implements ServiceProviderInterface
{
public function isDeferred(): bool
{
return true; // 延迟加载
}
public function provides(): array
{
return ['heavy.service'];
}
}
常见问题
Q: 如何解决循环依赖?
A: 使用接口、事件或延迟加载来解耦服务。
Q: 单例和绑定的区别?
A:
bind(): 每次获取都创建新实例singleton(): 只创建一次,之后返回同一实例
Q: 如何替换已绑定的服务?
A: 重新绑定即可,新绑定会覆盖旧绑定。
Q: 容器可以解析哪些类型?
A: 可以解析类、接口(需要绑定)、以及通过类型提示推断的依赖。
Q: 如何测试依赖注入?
A: 在测试中可以使用 Mock 对象替换真实服务:
$mockService = $this->createMock(ServiceInterface::class);
$container->instance('service', $mockService);