mathiasgrimm/di-container-php

具有自动依赖注入的IoC容器

dev-master / 1.0.x-dev 2018-02-23 22:36 UTC

This package is auto-updated.

Last update: 2024-09-09 13:49:58 UTC


README

Build Status Coverage Status

简单而有效的IoC容器,具有自动依赖解析

基本用法

单例

当使用单例时,Container->get()方法将始终返回相同对象引用,因为它将被存储在容器内部。

$container = new Container();
$container->bindSingleton(SomeComponentInterface::class, function (Container $c, $params = []) {
    return new SomeComponentImplementation();
});

$aComponent = $container->get(SomeComponentInterface::class);

您还可以使用任意键绑定非对象

$container = new Container();
$container->bindSingleton('db.credentials', [
    'user'     => getenv('db.username'),
    'password' => getenv('db.password')
]);

$dbCredentials = $container->get('db.credentials');

// OR
$container->bindSingleton('some-key', 'some-value');
$container->get('some-key');

工厂

当使用工厂绑定时,Container->get()将始终返回所绑定内容的新实例。bindFactory期望一个可调用对象(闭包,或实现了 __invoke 魔术方法的类)

$container = new Container();
$container->bindFactory(SomeComponentInterface::class, function (Container $c, $params = []) {
    return new SomeComponentImplementation();
});

$aComponent = $container->get(SomeComponentInterface::class);

实例

当使用实例绑定时,其行为与单例绑定相同,除了它只接受实例

$anObject  = new SomeObject();
$container = new Container();
$container->bindInstance(SomeInterface::class, $anObject);

$anObjectInstance = $container->get(SomeInterface::class);
echo $anObjectInstance === $anObject; // will always be true

// OR
$container->bindInstance('some-object', $anObject);
$anObjectInstance = $container->get('some-object');
echo $anObjectInstance === $anObject; // will always be true

扩展

扩展会用新值替换原始值,并通过参数传递旧值。这样,您就可以装饰您的组件。

新的绑定类型将与原始类型相同。因此,如果它是一个工厂,它仍然是一个工厂;如果是单例,它仍然是一个单例,依此类推。因此,您不能扩展尚未定义的组件。如果您尝试这样做,您将得到一个 ComponentNotRegisteredException 异常。


$container->bindSingleton(SomeInterface::class, function (Container $container, $params = []) {
    return new FileLogger();
});

$container->extend(SomeInterface::class, function (Container $container, $oldValue) {
    // oldValue is FileLogger
    $logger = new DecoratorLogger($oldValue);
    return $logger;
});

所有绑定的通用规则

  • 您不能绑定已经使用过的组件
  • 您不能解除已经使用过的组件的绑定
  • 您不能扩展尚未定义的组件
  • 您不能扩展已经使用过的组件

与容器提供者一起使用

为了集中和/或使您的应用程序的绑定更有组织,您可以使用容器提供者。要使用它们,您需要将它们注册到容器中。每个容器提供者都必须实现 ContainerProviderInterface

class MyContainerProvider implements ContainerProviderInterface
{
    public function register(Container $container)
    {
        $container->bindSingleton(MailerInterface::class, function (Container $container, $params) {
            return new LocalMailer();
        });    
    }
    
    
    public function boot(Container $container)
    {
    
    }
}

$container->register(new MyContainerProvider());

因此,您的应用程序可以有多个容器提供者,这有助于扩展您的应用程序,尤其是第三方供应商可以提供一些提供者。

register 方法

您可以在其中注册您的绑定,并可能不执行任何其他操作。如果您尝试在此方法中实现其他功能,则可能是另一个容器提供者尚未注册。

boot 方法

这仅在所有容器提供者都已注册且安全在此处实现一些逻辑时调用,因为此时容器提供者已加载。

请参阅下面的更完整的示例

class MyContainerProvider implements ContainerProviderInterface
{
    public function register(Container $container)
    {
        $container->bindSingleton(MailerInterface::class, function (Container $container, $params) {
            return new LocalMailer();
        });    
    }
    
    
    public function boot(Container $container)
    {

    }
}

interface MailerInterface
{
    public function send(Mail $mail);
}

class LocalMailer implements MailerInterface
{
    public function send(Mail $mail)
    {
        file_put_contents('somefolder/mailer.log');
    }
}

class MyController
{
    protected $mailer;
    
    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer; // this will be the LocalMailer instance
    }
    
    public function emailUser($userId)
    {
        // ...
        $mail = new Mail();
        $mail->setTo($user->getEmail());
        // ...
        $this->mailer->send($mail);
    }
}

class HttpHandler
{
    protected $container;
    
    public function __construct(Container $container) 
    {
        $this->container = $container;
    }

    public function handle()
    {
        // gets controllerName, method and params based on the route
        // $controllerName = MyController::class;
        // $mthod          = emailUser
        // $params         = ['user' => 1];
        
        $controller = $this->container->get($controllerName);
        
        $response = call_user_func_array([$controller, $method], $params);
        // same as $controller->emailUser(1);
    }
}

boot 方法

您的应用程序必须调用容器的 boot 方法,以便所有容器提供者都将启动。

$container = new Container();
// $container->register(...);
// $container->register(...);
// $container->register(...);
$container->boot();

这仅仅是通过遍历所有已注册的容器提供者并在每个提供者上调用 boot

定义顺序

定义绑定的顺序不应很重要,因为它们是在需要时才被延迟的。

例如

$container->bindSingleton(SomeInterfaceA::class, function (Container $c, $params = []) {
    $b = $container->get(SomeInterfaceB::class);
    $a = new SomeImplA($b);
    return $a;
});

$container->bindSingleton(SomeInterfaceB::class, function (Container $c, $params = []) {
    return new SomeImplB();
}); 

$container->get(SomeInterfaceA::class);

即使 SomeInterfaceA::class 依赖于 SomeInterfaceB::class,并且 SomeInterfaceB::classSomeInterfaceA::class 之后定义,它仍然可以正常工作。

实用方法

get()

Get会尝试返回一个实例/值,即使接口/类未定义。它将通过反射来实现。它还会使用混合方法,即如果部分依赖图已注册,则使用它,否则将尝试动态加载。

class SomeComponent
{
    
}

$container->get(SomeComponent::class); // will return a SomeComponent instance.

当对象动态加载时,它将始终是单例。

具体依赖可以自动解决。

class A {}
class B {}

class SomeComponent
{
    public function __construct(A $a, B $b)
    {
    
    }
}

$container->get(SomeComponent::class); // will return a SomeComponent instance and provide the dependencies
automatically

如果请求的类/接口不存在,将抛出NotResolvedDependencyException异常。

限制

在定义类依赖关系时,只能使用类和接口。标量类型将导致抛出ParameterNotInstantiable异常。

has()

has检查是否存在为给定键注册的绑定。

$container->has(SomeInterface::class); // returns true or false

如果has返回false,并不意味着get(SomeInterface::class)会抛出异常,因为它仍然可以动态加载。

loaded()

检查键是否已加载到容器中,这通常发生在您为单例或实例绑定发出get请求之后。

$container->bindSingleton('some-key', 'some-key');
$container->loaded('some-key'); // return false
$container->get('some-key');
$container->loaded('some-key'); // returns true

对于工厂绑定,它总是返回false。

如果键从未注册,它也会返回false

unbind()

unbind移除对给定键的所有内部引用。

对于已经被unbind()的键,hasloaded都将返回false。

如果键不存在,它不会抛出任何异常。

frozen()

检查键是否被冻结。

frozen() 与 loaded()

键可以冻结而不被加载。这是工厂的情况。

booted()

容器是否已被启动。

上下文绑定

每个实用方法,包括get,都有一个传递上下文的可能性。

有一些情况,您有依赖于同一接口但实际使用两种不同实现的组件。

$container->bindSingleton(LoggerInterface::class, function () {
    return new SlackLogger();
}, ControllerA::class);

$container->bindSingleton(LoggerInterface::class, function () {
    return new FileLogger();
}, ControllerB::class);

// you can explicitly inform the context to get the loggers
$container->get(LoggerInterface::class, [], ControllerA::class); // will return SlackLogger
$container->get(LoggerInterface::class, [], ControllerB::class); // will return FileLogger

// and you can also call the controller classes and the dependencies will be injected as you need
$container->get(ControllerA::class);
$container->get(ControllerB::class);

// ------------------------------------------------------
// controllers
// ------------------------------------------------------ 
class ControllerA 
{
    public function __construct(LoggerInterface $logger)
    {
        // logger will be the SlackLogger
    }
}

class ControllerB 
{
    public function __construct(LoggerInterface $logger)
    {
        // logger will be the FileLogger
    }
}


使用上下文绑定和默认绑定

// default context
$container->bindSingleton(LoggerInterface::class, function () {
    return new SlackLogger();
});

// ControllerB context
$container->bindSingleton(LoggerInterface::class, function () {
    return new FileLogger();
}, ControllerB::class);

$container->get(ControllerA::class);
$container->get(ControllerB::class);

class ControllerA 
{
    public function __construct(LoggerInterface $logger)
    {
        // logger will be the SlackLogger
    }
}

class ControllerB 
{
    public function __construct(LoggerInterface $logger)
    {
        // logger will be the FileLogger
    }
}

上下文绑定是一种定义方式:当加载这个类时,请提供这个实现。