talesoft/tale-di

PSR-15 兼容的 HTTP 工具库

0.1.1 2017-02-13 17:09 UTC

This package is auto-updated.

Last update: 2024-08-29 04:19:51 UTC


README

Packagist License CI Coverage

Tale DI

什么是 Tale DI?

Tale DI 是一个轻量级的 PSR-11 依赖注入规范实现,支持完全自动装配。

API 可能会有所变化。

安装

composer req talesoft/tale-di

使用方法

ContainerBuilder

use Tale\Di\ContainerBuilder;

使用 ContainerBuilder,您可以自动装配一个完整的 DI 容器,包括完整的 PSR-6 缓存支持。

返回的容器将是一个 Tale\Di\Container,下面将进行解释。

$cachePool = new RedisCachePool();

$builder = new ContainerBuilder($cachePool);

$builder->add(ViewRenderer::class);

$builder->add(ControllerDispatcher::class);

$builder->addInstance(new PDO(...));

$container = $builder->build();

$pdo = $container->get(PDO::class);

服务定位器

use Tale\Di\ServiceLocator\FileServiceLocator;
use Tale\Di\ServiceLocator\DirectoryServiceLocator;
use Tale\Di\ServiceLocator\GlobServiceLocator;

如果您不想手动添加每个文件,也可以使用 Tale DI 中的三个服务定位器之一。

$builder->addLocator(
    new FileServiceLocator('src/Classes/MyClass.php')
);

$builder->addLocator(
    new DirectoryServiceLocator('../src')
);

$builder->addLocator(
    new GlobServiceLocator('../src/{Controller,Model}/**/*.php')
);

$container = $builder->build();

注入和哲学

Tale DI 通过设计,只允许构造函数注入。没有构造函数中未涵盖的可选依赖项,也没有 XyzAware 接口,也没有实现它的可能性。

这避免了代码中的许多魔法和防御性 null 检查。如果您不喜欢这种方式,Tale DI 可能不是您想要的。但我建议您仍然试一试。

注入通过类名或实现的接口进行

class OrderProvider
{
    public function __construct(OrderRepository $repository)
}

$orderProvider = $container->get(OrderProvider::class);

无论您是否已将依赖项添加到容器中,它都将自动装配任何具有可读类型的(甚至可以处理的)外部(或内部)依赖项。这是可能的,因为 Tale DI 仅基于 PHP 的现有类机制、接口和反射。

interface ViewRendererInterface
{
}

class ViewRenderer implements ViewRendererInterface
{
}

//...

$renderer = $container->get(ViewRendererInterface::class);
// $renderer is instanceof ViewRenderer

可选依赖项按预期工作,应默认为默认/空实现,因此不需要防御性 null 检查。

class AvatarGenerator
{
    /**
     * @var CacheInterface
     */
    private $cache;
    
    public function __construct(CacheInterface $cache = null)
    {
        $this->cache = $cache ?? new RuntimeCache();
    }
}

迭代器和接口数组

Tale DI 有一个通过普通 PHP 接口指定的 标签 概念。您可以使用接口进行注入

class EntityManager
{
    public function __construct(DbalInterface $dbal)
}

并且通过在文档注释中适当的类型提示,甚至可以注入特定接口的所有实例

class Importer
{
    /**
     * @param iterable<\App\Service\Importer\WorkerInterface>
     */
    public function __construct(iterable $workers)
    {
        foreach ($workers as $worker) {
            $this->initializeWorker($worker);
        }
    }
    
    /**
     * @param array<\App\Service\Importer\WorkerInterface>
     */
    public function __construct(array $workers)
    {
        $this->workers = array_filter($workers, fn($worker) => $worker->canImport());
    }
}

这将把所有已知类型为 WorkerInterface 的依赖项注入到 workers 参数中。

参数

Tale DI 不仅可以注入类和实例,您还可以指定基于名称的固定参数,这些参数将被注入。请注意,它们应该是可序列化的,以便利用缓存机制。

$builder->setParameter('someParameter', 'some value');

//Any class with a parameter 'someParameter' will get 'some value' injected as a string

第二个参数允许您将参数限制为仅用于特定类或接口。

这也允许创建外部类型的实例,而无需为其指定特定的工厂

class UserManager
{
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo; // Fully working PDO instance!
    }
}

$builder->setParameters([
    // These are the parameter names PDO internally uses in its constructor!
    'dsn' => 'mysql:host=localhost',
    'username' => 'root', 
    'passwd' => '', 
    'options' => [PDO::ATTR_ERRMODE => PDO_ERRMODE_EXCEPTION]
], PDO::class);

$builder->add(UserManager::class);
$container = $builder->build();

$userManager = $container->get(UserManager::class);
// PDO will be fully wired in UserManager

手动构建容器

容器的基设置很简单。大多数内容都用于自动装配机制,但您也可以完全避免使用 ContainerBuilder 并直接使用容器。

Tale DI 带来了三个基本容器,您可以使用它们来满足特定需求。

容器

use Tale\Container;

Tale 容器是 DI 系统的核心,它通过特定的 Tale\Di\DependencyInterface 实例解析值。这是其工作原理

$dependencies = [
    //Value Dependency is just a value. Can be any kind of value.
    'test value' => new ValueDependency('some value'),
    
    //A reference dependency references another value in the container
    'reference' => new ReferenceDependency('test value'),
    
    //A callback dependency only gets resolved when it's requested
    'factory' => new CallbackDependency(function (ContainerInterface $container) {
        return new Something($container->get(SomethingElse::class));
    }),
    
    //Same as CallbackDependency, but will cache the result between each ->get() call
    'lazy factory' => new PersistentCallbackDependency(function () {
        return (new SomeHeavyWorker())->getResult();
    })
];

$container = new Container($dependencies);

$container->get('test value'); //"some value"
$container->get('reference'); //"some value"
// etc.

您始终可以定义自己的依赖类型及其解析方式,通过实现 Tale\Di\DependencyInterface

final class PdoDependency implements DependencyInterface
{
    private $pdo;
    
    public function __construct()
    {
        $this->pdo = new PDO(...);
    }
    
    public function get(ContainerInterface $container)
    {
        return $this->pdo;
    }
}

$container = new Container(['pdo' => new PdoDependency()]);
$pdo = $container->get('pdo');

$stmt = $pdo->prepare(...);

ArrayContainer

use Tale\Di\Container\ArrayContainer;

主要用于测试,这是一个非常基本的 PSR-11 实现,它只是将固定的名称映射到值

$container = new ArrayContainer([
    SomeClass::class => new SomeClass(),
    'test key' => 15
]);

$container->get(SomeClass::class); //SomeClass instance
$container->get('test key'); //15

NullContainer

use Tale\Di\Container\NullContainer;

总是返回 false,当调用 ->has() 时,并且总是抛出 NotFoundException,当调用 ->get() 时。

这对于希望将装饰器作为可选依赖项并需要默认实现以避免防御性空检查的类作为默认容器非常有用。

final class AdapterFactory
{
    public function __construct(ContainerInterface $container = null)
    {
        $this->container = $container ?? new NullContainer();
    }
    
    public function createAdapter(): AdapterInterface
    {
        $adapter = null;
        if ($this->container->has(SomeAdapter::class)) {
            $adapter = $this->container->get(SomeAdapter::class);
        } else if ($this->container->has(SomeOtherAdapter::class)) {
            $adapter = $this->container->get(SomeOtherAdapter::class);
        } else {
            $adapter = new SomeDefaultAdapter();
        }
        return $adapter;
    }
}

类型信息

使用 Tale\Di\TypeInfoFactory\PersistentTypeInfoFactory;

注意,这可能在某个时候成为一个独立的库。

$typeInfoFactory = new PersistentTypeInfoFactory();

$typeInfo = $typeInfoFactory->get('array<int>');

$typeInfo->isGeneric() //true

$typeInfo->getGenericType()->getName() //array

$typeInfo->getGenericTypeParameters()[0]->getName() //int

参数读取器

使用 Tale\Di\ParameterReader\DocCommentParameterReader;

一个参数读取器,同时考虑文档注释 @param 标记。

$typeInfoFactory = new PersistentTypeInfoFactory();

$paramReader = new DocCommentParameterReader($typeInfoFactory);

$reflClass = new ReflectionClass(SomeClass::class);

$params = $paramReader->read($reflClass->getMethod('__construct');

var_dump($params);