依赖注入

最后更新: 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);
    

    相关文档

  • 服务提供者 - 服务提供者详细说明
  • 容器 API - 容器 API 参考