mbretter/stk-di

依赖注入变得简单

3.1.0 2022-08-15 12:25 UTC

This package is auto-updated.

Last update: 2024-09-06 18:22:11 UTC


README

License PHP 8 Latest Stable Version Total Downloads CI

一个简单的依赖注入系统,可用于任何实现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;
}