composite-php/container

支持自动装配的PSR-11实现。

1.2.2 2023-12-15 20:58 UTC

This package is not auto-updated.

Last update: 2024-09-21 01:06:46 UTC


README

PSR-11的另一个实现,支持自动构造函数注入。需要PHP 8.1或更高版本。

功能

  • 实现了PSR-11
  • 支持自动构造函数注入(通过Reflection)。
  • 检测循环依赖。

安装

使用composer安装

composer require composite-php/container

使用

自动解析

容器可以自动进行构造函数注入。假设您的项目中存在以下类

<?php

// Your simple logger.
class Logger
{
    public function log(string $message): void
    {
        echo $message;
    }
}

// Your users store: responsible for containing usernames.
class UsersStore
{
    private array $users = [];
    
    public function add(string $username): void
    {
        $this->users[$username] = true;
    }
}

// Your service that is responsible for registering user: persisting username and writing something to log.
class UserRegistrationService
{
    public function __construct(
        private Logger $logger,
        private UsersStore $store
    ) {
    }
    
    public function registerUser(string $name): void
    {
        $this->store->add($name);
        $this->logger->log('User was registered.');
    }
}

为了创建UserRegistrationService的实例,您需要将其依赖项传递给构造函数

<?php

$regService = new UserRegistrationService(
    new Logger(),
    new UsersStore()
);

$regService->register('Foo');

容器能够自行完成这个操作

<?php

use Composite\Container\Container;

$container = new Container();
// Ask the container to get instance of UserRegistrationService.
// The container will create instances of Logger and UsersStore,
// then it will return the UserRegistrationService with the required dependencies.
$regService = $container->get(UserRegistrationService::class);

$regService->register('Foo');

只要这些参数是具体的类或具有默认值的内置类型,容器就能够自动注入参数

<?php

// Instance of this class can be instantiated automatically, because there are no constructor arguments.
class A
{
    
}

// Instance of this class can be instantiated automatically, because the parameter is an instance of a concrete class.
class B
{
    public function __construct(
        public A $a
    ) {
    }
}

// Instance of this class can be instantiated automatically, because the parameter is an instance of concrete class.
// When B (being an argument) is instantiated, it gets injected with A.
// So, resolution of dependencies is recursive.
class C
{
    public function __construct(
        public B $b
    ) {
    }
}

// The following will be resolved with default "/tmp/default" value.
class FileLogger
{
    public function __construct(
        private string $targetFile = '/tmp/default'
    ) {}
}

// The following cannot be instantiated automatically,
// because the container doesn't know what to pass as constructor argument.
class FileLogger
{
    public function __construct(
        private string $targetFile
    ) {
    }
}

interface LoggerInterface
{

}

// Instance of this service cannot be instantiated automatically,
// because constructor argument is type hinted against interface, which cannot be instantiated.
class MyService
{
    public function __construct(
        public LoggerInterface $logger
    )
}

解析后条目会被重用

容器会缓存解析后的条目(这也意味着,容器会保留已解析条目的引用),因此在编写有状态代码时要小心

<?php

use Composite\Container\Container;

$container = new Container();
// Resolve your service from the container.
$regService = $container->get(UserRegistrationService::class);
// Register user.
$regService->register('Foo');
// Resolve your service from the container again - the container will return previously created instance.
$regService = $container->get(UserRegistrationService::class);
$regService->register('Foo'); // Will throw an exception, because of the logic in UserStore::add.

另一个演示此功能的例子

<?php

class MyLogger
{
    public function __construct() {
        echo 'Constructor invoked!';
    }
}

$container = new Container();

// The following line outputs 'Constructor invoked!'
$container->get(MyLogger::class);

// The following line outputs nothing, because the container will return instance of `MyLogger` that was created before (and constructor of which had been called).
$container->get(MyLogger::class);

自定义定义

您可以通过传递给容器的构造函数来指定条目的定义。每个定义都必须是一个可识别的条目ID的调用操作。最简单的情况是一个数组

<?php

use Composite\Container\Container;

// Define that whenever an instance of LoggerInterface is required,
// the container should return instance of FileLogger.
$definitions = [
    LoggerInterface::class => static function (Container $container) {
        return $container->get(FileLogger::class);
    }
];
// Create container instance, passing the definitions.
$container = new Composite\Container\Container($definitions);

// Returns instance of FileLogger.
$hotelsProvider = $container->get(LoggerInterface::class);

然而,任何iterable都被构造函数接受。有些人可能更喜欢这种表示法

<?php

use Psr\Container\ContainerInterface;
use Composite\Container\Container;

$definitions = static function (): iterable {
    yield ContainerInterface::class
        => static fn (Container $container)
        => $container;
    yield UsersRepository::class
        => static fn (Container $container)
        => $container->get(DatabaseUserRepository::class);
    yield Config::class
        => static fn ()
        => new Config();
};

$container = new Container($definitions());

当然,您不仅限于类/对象

<?php

use Composite\Container\Container;

$definitions = [
    'projectRoot' => static function () {
        return '/opt/xres';
    }
];

$container = new Container($definitions);

// Returns '/opt/xres' string.
$projectRoot = $container->get('projectRoot');

容器实例化后,其定义不能修改。

自定义条目优先于现有类。

使用方法简单:创建容器实例,可选地传递您的定义。

<?php

declare(strict_types=1);

use Composite\Container;

// Create container instance without custom definitions.
$container = new Container();

构造函数接受您以可迭代的形式(array/Generator/Traversable)提供的定义。

<?php

use Psr\Container\ContainerInterface;

class ContainerDefinitions implements IteratorAggregate
{
    public function getIterator() : Traversable
    {
        // When someone needs instance of `ContainerInterface`, return the container itself.
        yield ContainerInterface::class
            => fn (Container $container) => $container;
    
        // When someone gets FactoryInterface, return concrete factory implementation.
        yield FactoryInterface::class 
            => fn (Container $container) 
            => $container->get(MyConcreteFactory::class);
    }
}

// Create container with the definitions.
$container = new Container(new ContainerDefitions());

// When requested for FactoryInterface instance, container will return MyConcreteFactory according
// to your definition.
/** @var MyConcreteFactory $myFactory */
$myFactory = $container->get(FactoryInterface::class);

键必须是项目名称,值必须是一个返回项目callable。这个可调用的参数将是Container实例。