composite-php / container
支持自动装配的PSR-11实现。
1.2.2
2023-12-15 20:58 UTC
Requires
- psr/container: ^2
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
- squizlabs/php_codesniffer: ^3.6
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
实例。