mbretter / stk-di
依赖注入变得简单
Requires
- php: ^8
- psr/container: ^2.0.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^v2.19.3
- phpstan/phpstan: ^1.8.2
- phpunit/phpunit: ^9.5.21
README
一个简单的依赖注入系统,可用于任何实现Psr\Container\ContainerInterface接口的容器。
服务工厂支持实现了Injectable接口的服务进行构造函数注入和参数注入。
作为一个特殊功能,支持按需服务,它们支持在运行时而不是创建时创建服务,这避免了由于复杂依赖而导致的每次请求的依赖加载膨胀。作为副作用,按需服务可以用来注入不实现Injectable接口的对象,如第三方服务。
该库提供了一个简单的容器实现,这个容器主要用于测试和演示目的。
Injectable
Injectable接口是一个空接口,用于定位可注入服务。服务应该实现此接口。
use Stk\Service\Injectable; class MyService implements Injectable { ... }
注册服务
作为第一步,您必须将所有服务放入容器中,并且建议将服务工厂也放入容器中。
use Stk\Service\DumbContainer; use Stk\Service\Factory; class ServiceA implements Injectable { } $container = new DumbContainer(); $container['config'] = [ 'param1' => 'foo', 'param2' => 'bar' ]; $container['factory'] = new Factory($container); // put the service factory into the container $container['serviceA'] = function (ContainerInterface $c) { return $c['factory']->get(ServiceA::class); };
参数注入
服务工厂扫描每个服务,查找具有Injectable作为参数的私有方法,并通过从容器中获取服务并使用参数名称作为键来注入服务。
class ServiceA implements Injectable { } class ServiceB implements Injectable { protected $serviceA; // tell the factory to inject serviceA private function setServiceA(Injectable $serviceA) { $this->serviceA = $serviceA; } } $container['serviceA'] = function (ContainerInterface $c) { return $c['factory']->get(ServiceA::class); }; $container['serviceB'] = function (ContainerInterface $c) { return $c['factory']->get(ServiceB::class); }; $service = $this->container->get('serviceB');
构造函数注入
在实例化服务时,工厂会扫描构造函数中的参数名称,如果它们在容器中找到,则注入服务,支持默认值(如果容器中没有具有给定名称的服务)。不需要注入的服务实现injectable接口,任何容器值都可以注入。
class ServiceC implements Injectable { protected $service; protected $whatever; public function __construct($serviceA, $whatever = []) { $this->service = $serviceA; $this->whatever = $whatever; } }
带有参数的构造函数注入
如果需要在声明时传递一些类型的静态参数(例如配置设置)以及在实例化时传递一些额外的参数,则服务工厂会跳过传递的参数,并且只注入剩余的参数。
class ServiceK implements Injectable { protected $config; public $param1; // $config should be passed at service declaration, $param1 at creation time public function __construct($config, $param1) { $this->config = $config; $this->param1 = $param1; } } // the container declaration for ServiceK, wrapped inside a Closure $container['serviceK'] = function ($c) { return function ($param2) use ($c) { return $c['factory']->get(ServiceK::class, $c->get('config'), $param2); }; }; // accessing the service /** @var Closure $serviceK */ $factory = $container->get('serviceK'); /** @var ServiceK $serviceK */ $serviceK = $factory('val2'); $serviceK->param1 === 'val2';
按需服务
按需服务是围绕服务包装的包装器,以避免立即实例化依赖服务。在拥有大量服务的大项目中,很可能遇到依赖膨胀的问题,ServiceA依赖于ServiceB,ServiceB需要ServiceC...最后,您会实例化所有服务,无论它们是否被使用。
class ServiceH implements Injectable { public $arg1 = null; public $arg2 = null; public function __construct($arg1 = null, $arg2 = null) { $this->arg1 = $arg1; $this->arg2 = $arg2; } } class ServiceE implements Injectable { /** @var OnDemand */ public $onDemand; // trigger DI of OnDemand service H private function setService(OnDemand $serviceH) { $this->onDemand = $serviceH; } public function getService() { return $this->onDemand->getInstance(); } public function newService() { return $this->onDemand->newInstance(); } } $container['serviceE'] = function ($c) { return $c['factory']->get(ServiceE::class); }; $container['onDemandServiceH'] = function ($c) { // the protect method wraps the service inside into the OnDemand injectable return $c['factory']->protect(ServiceH::class); }; /** @var OnDemand $serviceH */ $serviceH = $container->get('serviceH'); $inst = $serviceH->newInstance('foo', 'bar'); // or if you want to treat them as singleton $inst = $serviceH->getInstance('foo', 'bar'); /** @var ServiceE $serviceE */ $serviceE = $container->get('serviceE'); $svc = $serviceE->getService();
不可注入的(第三方服务)
如果您想注入不实现Injectable接口的服务(http客户端等),可以使用按需服务进行注册。
class ServiceJ implements Injectable { /** @var OnDemand */ protected $foreignService = null; private function setForeignServices(OnDemand $foreignService) { $this->foreignService = $foreignService; } public function getForeignService() { return $this->foreignService->getInstance(); } } $container['foreignService'] = function ($c) { return $c['factory']->protect(stdClass::class); }; $container['serviceJ'] = function ($c) { return $c['factory']->get(ServiceJ::class); }; /** @var ServiceJ $serviceJ */ $serviceJ = $container->get('serviceJ'); $std = $serviceJ->getForeignService();
使用特质实现可重用性
特质非常方便,如果您不想在重复注入相同服务时重复代码。
编写一个包含属性、获取器和设置器的Trait
use Stk\Service\OnDemand; trait DependsOnServiceB { /** @var OnDemand */ protected $_serviceB; private function setServiceB(OnDemand $serviceB) { $this->_serviceB = $serviceB; return $this; } /** * @return ServiceB */ protected function serviceB() { return $this->_serviceB->getInstance(); } } ... class ServiceJ implements Injectable { use DependsOnServiceB; }