mathiasgrimm / di-container-php
具有自动依赖注入的IoC容器
Requires
- php: ~5.6|~7.0
- mathiasgrimm/arraypath: ^2.0
Requires (Dev)
- phpunit/phpunit: >=5.4.3
- satooshi/php-coveralls: ^1.0
- squizlabs/php_codesniffer: ^2.3
This package is auto-updated.
Last update: 2024-09-09 13:49:58 UTC
README
简单而有效的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::class
在 SomeInterfaceA::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()
的键,has
和loaded
都将返回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
}
}
上下文绑定是一种定义方式:当加载这个类时,请提供这个实现。