intraworlds / service-container
轻量且强大的自动装配依赖注入容器实现
v0.8.4
2024-02-26 08:38 UTC
Requires
- php: ^8.0
- psr/container: ^1.0||^2.0
Requires (Dev)
- doctrine/coding-standard: ^10.0
- infection/infection: ^0.26.16
- phpbench/phpbench: ^1.2
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.0
README
轻量且强大的自动装配依赖注入容器实现。
安装
使用 Composer 安装此包
composer require intraworlds/service-container
用法
该容器实现了标准PSR-11接口。您可以直接使用它进行自动装配。
假设我们正在构建一个缓存客户端。这个客户端不会直接实现缓存,而只是提供通用的API - 它将依赖于给定的适配器。
我们将从定义内存适配器开始
namespace Acme\Cache; class MemoryAdapter { function get(string $key): string {} function set(string $key, string $string): void {} }
缓存客户端
namespace Acme\Cache; class Client { private $adapter; function __construct(MemoryAdapter $adapter) { $this->adapter = $adapter; } function get(string $key) { $string = $this->adapter->get($key); return unserialize($string); } function set(string $key, $value): void { $string = serialize($value); $this->adapter->set($key, $string); } }
现在在主程序中,我们可以轻松地实例化缓存客户端 - 客户端的依赖关系将被自动解决。
namespace Acme; use IW\ServiceContainer; $container = new ServiceContainer; $client = $container->get(Cache\Client::class);
我们尚未自己实现序列化。让我们将其移到依赖中。
namespace Acme\Cache; class PhpSerializer { function serialize($value): string {} function unserialize(string $string) {} }
客户端有少许变化。
namespace Acme\Cache; class Client { private $adapter; function __construct(MemoryAdapter $adapter, PhpSerializer $serializer) { $this->adapter = $adapter; $this->serializer = $serializer; } function get(string $key) { $string = $this->adapter->get($key); return $this->serializer->unserialize($string); } function set(string $key, $value): void { $string = $this->serializer->serialize($value); $this->adapter->set($key, $string); } }
我们的主要代码保持不变。
$client = $container->get(Cache\Client::class);
resolve
方法对于解决调用者的任何依赖非常有用。特别是对于init
模板方法非常有用。请看以下示例。
abstract class Parent { function __construct(Dependency $dependency, ServiceContainer $container) { $this->dependency = $dependency; $container->resolve([$this, 'init']); } } class Child extends Parent { function init(AhotherDependency $another) { // ... } }
手动装配
有时您可能想手动配置容器。让我们考虑以下关于命令模式的示例。
interface OrderCommand { function execute(); } class OrderInvoker { function __construct(private OrderCommand ...$commands) {} function execute() : void { array_walk($this->commands, fn($command) => $command->execute()); } }
使用 IW\ServiceContainer
,您有几种方法可以解决 OrderInvoker
的依赖关系。
// an alias but that's no good for multiple commands $container->alias('OrderInvoker', 'ReserveItems'); // external factory $container->bind('OrderInvoker', function (IW\ServiceContainer $container) { return new OrderInvoker($container->get('ReserveItems'), $container->get('SendInvoice')); }); // internal factory $container->bind('OrderInvoker', 'OrderInvoker::create'); class OrderInvoker { static function create(ReserveItems $reserveItems, SendInvoice $sendInvoice) : OrderInvoker { return new OrderInvoker($reserveItems, $sendInvoice); } } // wiring factory $container->wire('OrderInvoker', 'ReserveItems', 'SendInvoice'); // using annotations (TBD PHP 8.0), used as a fallback (can be overridden by defining factory directly (eg. in tests) class OrderInvoker { #[IW\ServiceContainer\Bind('create')] function __construct(private OrderCommand ...$commands) {} static function create(ReserveItems $reserveItems, SendInvoice $sendInvoice) : OrderInvoker { return new OrderInvoker($reserveItems, $sendInvoice); } }
所有方法都有其优点。内部工厂方法对于静态分析很好。装配工厂对于通用应用程序模式和当依赖关系可能变化时非常有用。
待办事项 继续举例说明
待办事项
$exception->getOrigin(); // returns first exception outside the framework (useful for avoiding of tracing)
许可证
本包的所有内容均受MIT许可证许可。