nimayneb/yedi

足够的依赖注入器

dev-master 2020-06-25 11:04 UTC

README

Build Status

这是一个小巧且易于使用的依赖注入(也称为DI)框架。

特性
  • 流畅的接口
  • 容器与PSR-11兼容...
  • 使用数据结构映射(参见 https://php.ac.cn/manual/en/class.ds-map.php
  • 将类/接口或特性委托给另一个具体类(参见"别名容器的使用")
  • 使用配置的构造函数参数实例化依赖项(参见"解析容器的使用")
  • 不作为反模式使用单例行为(如果需要,每个类只能存在一次)
  • DI Getter 特性

使用注入器

每次你想注入一个依赖类时,都可以使用DI。

首先,这并不是真的很繁琐

$di = new JayBeeR\YEDI\DependencyInjector;
$object = $di->get(My\Own\Class::class);

每个类都将通过纯DI方法内部构建

class MyOwnClass
{
    protected My\Own\Interface $stuff;
    
    public function __construct(My\Own\OtherClass $stuff)
    {
        $this->stuff = $stuff;
    }
}

$di = new JayBeeR\YEDI\DependencyInjector;
$myClass = $di->get(MyOwnClass::class);

构造函数参数中给出的类类型 $stuff 将自动反射并在实例化请求的类之前进行DI委托。

你可以用任何形式使用这个类而不使用DI

$dependentClass = new My\Own\OtherClass;
$myClass = new MyOwnClass($dependentClass);

你可以在自己的类中使用getter特性(但要注意它)

class MyOwnClass
{
    use JayBeeR\YEDI\DependencyInjectorGetter;
    
    protected My\Own\Interface $stuff;
    
    public function injectDependencies()
    {
        $this->stuff = $this->get(My\Own\OtherClass::class);
    }
}

(!) YEDI类只能有特性构造函数。这是因为你不可以通过进一步实现轻松地在这个类中使用相同的DI。你必须向该类传递现有的DI。不愉快的是:额外的参数只会使构建类更困难。因此,我们只使用特性构造函数来传递DI。其他所有内容都通过名为"injectDependencies"的抽象方法注入。

你可以使用它,但...你只是不必要地依赖于DI。

别名容器的使用

别名容器应该用于映射将被覆盖的类。应该是(抽象)类、接口或特性。

$di = new JayBeeR\YEDI\DependencyInjector;
$di->delegate(ForeignClass::class)     ->to(MyExtendedClass::class);
$di->delegate(ForeignInterface::class) ->to(MyClassWithThisInterface::class);
$di->delegate(ForeignTrait::class)     ->to(MyClassWithThisTrait::class);

你可以这样使用它

$myExtendedClass = $di->get(ForeignClass::class);
$myClassWithThisInterface = $di->get(ForeignInterface::class);
$myClassWithThisTrait = $di->get(ForeignTrait::class);

从外部类,你现在可以控制内部依赖,而无需扩展此类。

然而,YEDI提供了更多的支持。继续阅读。

解析容器的使用

解析容器应该用于使用具体构造函数参数实例化依赖类。

class ForeignClass {
    public function __construct(
        string $name, 
        Foreign\HelperInterface $helper, 
        int $limit,
        bool $hidden,
        Foreign\Service $service
    );
}

$di = new JayBeeR\YEDI\DependencyInjector;
$di->for(ForeignClass::class)
    ->setArgument('limit')  ->to(123)
    ->setArgument('name')   ->to('value')
    ->setArgument('helper') ->asInjection(My\Own\Helper::class)
    ->setArgument('hidden') ->to(false)
    ->setArgument('service') ->asSingleton(My\Own\Service::class)
;

$foreignClass = $di->get(ForeignClass::class);

这会与以下实现相同

$dependentHelper = new My\Own\Helper;

// should return singleton instance via Factory
$service = Foreign\Service::get(); 

$object = new ForeignClass(123, 'value', $dependentHelper, false, $service);

但为什么你应该使用这个呢?

让我们想象你有两个类,它们为接口提供了不同的实现。

然而,使用YEDI的别名支持,你不能将此接口委托给这两个类。

这就是情况

class MyClassA
{
    public function __construct(ForeignInterface $interface);
}

class MyClassB
{
    public function __construct(ForeignInterface $interface);
}

这就是解决方案

$di = new JayBeeR\YEDI\DependencyInjector;
$di->for(MyClassA::class)
    ->setArgument('interface')
    ->asInjection(MyClassInterfaceA::class)
;

$di->for(MyClassB::class)
    ->setArgument('interface')
    ->asInjection(MyClassInterfaceB::class)
;

你现在可以用简单的类名实例化

$di = new JayBeeR\YEDI\DependencyInjector;

// constructor argument with MyClassInterfaceA
$myClassA = $di->get(MyClassA::class); 

// constructor argument with MyClassInterfaceB
$myClassB = $di->get(MyClassB::class); 

单例类使用

有时你想要确保某些类不能被实例化多次。这就是Singleton设计模式的意义所在。它也被称为反模式,因为存在几个缺点。

你可以使用单例实例而不使用此模式

$di = new JayBeeR\YEDI\DependencyInjector;

// Global Singleton delegation
$di->delegate(Foreign\Class:class)->asSingleton(); // via ReflectionType
$di->delegate(Foreign\Class:class)->asSingleton(MyOwn\Class::class);

// Local Singleton delegation
$di->for(Foreign\Class:class)->setArgument('service')->asSingleton(); // via ReflectionType
$di->for(Foreign\Class:class)->setArgument('service')->asSingleton(MyOwn\Service::class);

(!) 使用单例时应小心,因为这些不是立即生成的,而只是在第一次通过DI请求时生成。这里的顺序可能取决于其他实例将在何时创建。

愿望清单

  • 重构和重命名事物(清晰的接口)
  • 所有异常的描述 <3
  • 所有异常的测试
  • 抛出更多的异常?

PDepend

Build Status

  • NOP - 包的数量
  • NOC - 类的数量
  • NOM - 方法的数量
  • LOC – 代码行数
  • CYCLO - 节点复杂度
  • 函数和方法调用次数
  • 平均派生类数
  • 平均层次高度

Build Status