jasny / container
Requires
- php: >=7.2.0
- ext-ctype: *
- improved/type: ^0.1.1
- jasny/autowire: ^1.2
- psr/container: ^1.0
Requires (Dev)
- bnf/phpstan-psr-container: 0.12
- jasny/php-code-quality: ^2.3
README
此包包含一个简单的依赖注入容器,与 PSR-11 兼容。
该容器支持(显式)自动装配 和 子容器。 Container
对象是不可变的。
容器用于帮助进行 依赖注入,创建松散耦合的应用程序。DI 帮助提高应用程序的可测试性和可维护性。
以下类型的条目通常会被添加到容器中;
- 服务 是在整个应用程序中通常只有一个实例的对象。这里 DI 替代了全局对象、单例和服务定位器的使用。
- 抽象工厂 是用于创建新实例的特定服务。使用工厂而不是在类中使用
new
。工厂允许模拟对象或使用类的不同/定制实现。 - 原型对象 是使用工厂的替代方案,其中原型对象是不可变的。当使用时,对象将被克隆。
- 配置值 可以直接由容器返回,这比使用全局配置数组、全局常量或直接获取环境变量更可取。
小贴士: 有时候将依赖项传递给(深层嵌套的)对象可能会更困难。在这种情况下,你可能想通过使用 Service Locator、Facade 或 Singleton 通过全局作用域来退而求其次。 不要这样做! 这会使你的代码更难测试、维护和重用。相反,通过为嵌套对象创建一个抽象工厂来解决问题,并将服务注入到工厂中。
此库基于 Picontainer。
安装
Jasny Container 包可在 packagist 上找到。使用 composer 进行安装
composer require jasny/container
该包遵循 SemVer 规范,并且次要版本之间将保持完全向后兼容。
在容器中声明条目
创建容器是创建一个 Container
实例,传递条目的列表,作为 匿名函数的数组。
use Jasny\Container\Container; use Psr\Container\ContainerInterface; $container = new Container([ Foo::class => static function() { return new Foo(); }, BarInterface::class => static function(ContainerInterface $container) { $foo = $container->get(Foo::class); return new Bar($foo); }, "bar" => static function(ContainerInterface $container) { return $container->get('bar'); // Alias for BarInterface }, "APPLICATION_ENV" => static function(ContainerInterface $container) { return getenv('APPLICATION_ENV'); } ]);
条目列表是一个关联数组。条目的顺序无关紧要。
- 键是容器中条目的名称。
- 值是一个 匿名函数(闭包),它将返回条目。
条目可以是任何内容(对象、标量值、资源等...)。匿名函数必须接受一个参数:将从中获取依赖项的容器。
可以向容器传递任何 可迭代对象
,而不仅仅是普通数组。一旦创建容器,其不可变条目就无法添加、删除或替换。
委托查找
如果将委托查找容器作为构造函数的第二个参数传递,它将被传递给匿名函数。
$otherContainer = new Container([ ZooInterface::class => static function(ContainerInterface $container) { $foo = $container->get(Foo::class); // $container is the $rootContainer return new Zoo($foo); } }, $rootContainer);
条目加载器
可以使用 EntryLoader
从目录中的 PHP 文件加载条目。这对于大型应用程序组织服务声明很有用。
use Jasny\Container\Container; use Jasny\Container\Loader\EntryLoader; $files = new \GlobIterator( 'path/to/declarations/*.php', \GlobIterator::CURRENT_AS_PATHNAME | \GlobIterator::SKIP_DOTS ); $loader = new EntryLoader($files); $container = new Container($loader);
EntryLoader
接受一个迭代器。这可以是简单的 ArrayIterator
,但更常见的是 GlobIterator
或 RecursiveDirectoryIterator
。请参阅 SPL 迭代器。
类加载器
ClassLoader
是条目加载器的替代方案,根据类列表创建条目。加载器接受一个包含完全限定名称(FQCN)的 Iterator
。
use Jasny\Container\Container; use Jasny\Container\ClassLoader; $loader = new ClassLoader(new \ArrayIterator(['App\Foo', 'App\Bar', 'App\Qux'])); $container = new Container($loader);
默认情况下,条目键是类名,并使用自动装配来实例化服务。
自定义实例化
第二个(可选)参数是应用于每个类的回调,以创建容器条目。此函数必须返回一个闭包数组。
use Jasny\Container\Container; use Jasny\Container\ClassLoader; use Psr\Container\ContainerInterface; $callback = static function(string $class): array { $baseClass = preg_replace('/^.+\\/', '', $class); // Remove namespace $id = strtolower($baseClass); return [ $id => static function(ContainerInterface $container) use ($class) { $colors = $container->get('colors'); return new $class($colors); } ]; }; $loader = new ClassLoader(new \ArrayIterator(['App\Foo', 'App\Bar', 'App\Qux']), $callback); $container = new Container($loader);
加载文件夹中的所有文件
除了只提供类列表之外,您可能还想扫描文件夹并添加该文件夹中的所有类。这可以通过 jasny/fqcn-reader 中的 FQCNIterator
完成。
use Jasny\Container\Container; use Jasny\Container\Loader\ClassLoader; use Jasny\FQCN\FQCNIterator; $directoryIterator = new \RecursiveDirectoryIterator('path/to/project/services'); $recursiveIterator = new \RecursiveIteratorIterator($directoryIterator); $sourceIterator = new \RegexIterator($recursiveIterator, '/^.+\.php$/i', \RegexIterator::GET_MATCH); $fqcnIterator = new FQCNIterator($sourceIterator); $loader = new ClassLoader($fqcnIterator); $container = new Container($loader);
这也可以与回调结合使用。
修改后的副本
容器是不可变的。创建后无法添加或更改条目。然而,可以使用 with()
方法获取具有修改后条目的容器副本。
$testContainer = $container->with([ Foo::class => static function() { return new DummyFoo(); }, ]);
with()
方法接受一个具有回调的迭代器,类似于容器构造函数。这意味着可以使用加载器加载替换条目。
从容器获取条目
使用 get()
方法从容器获取条目。
$bar = $container->get(BarInterface::class);
对 get
方法的调用应仅返回条目,如果条目是容器的一部分。如果条目不是容器的一部分,将抛出 Jasny\Container\NotFoundException
。
类型检查
如果条目标识符是接口或类名,如果条目不实现接口或扩展类,则抛出 TypeError
。
任何以大写字母开头且不包含 .
的标识符被视为潜在的接口或类名,这通过 class_exists
检查。
// No type checking is done $container->get('foo'); $container->get('datetime'); $container->get('My.thing'); // No checking is done because class doesn't exist $container->get('FooBar'); // Checking is done $container->get('Psr\Http\Message\ServerRequestInterface');
建议将非类/接口标识符保持为小写。
子容器
容器条目也可能是容器本身。在这种情况下,您可以使用entry.subentry
从子容器中获取条目。子容器需要实现Psr\Container\ContainerInterface
,不需要是Jasny\Container
对象。
use Jasny\Container; use Psr\Container\ContainerInterface; $container = new Container([ 'config' => static function(ContainerInterface $container) { return new Container([ 'secret' => static function() { return getenv('APPLICATION_SECRET'); } ]); } ]); $secret = $container->get('config.secret');
如果容器包含config.secret
条目,则不会查询config
容器。使用多级,如config.db.settings.host
,容器会按照以下顺序尝试查找条目:config.db.settings.host
、config.db.settings
、config.db
、config
。
检查条目
要检查容器是否有条目,可以使用has
方法。如果条目是容器的一部分,则返回true
,否则返回false
。
空对象
提示:与其使用has
方法,不如创建一个空对象。空对象正确实现了接口,但什么都不做。例如,一个不实际缓存值的NoCache
对象。移除if
语句可以减少复杂性。函数调用通常不比if语句更昂贵,因此不会影响性能。
自动装配
容器可以用来实例化对象(而不是使用new
),自动确定依赖关系。当你发现自己经常修改特定条目时,这会很有用。
要使用自动装配,向容器中添加一个Jasny\Autowire\AutowireInterface
条目。
use Jasny\Container\Container; use Jasny\Container\AutowireContainerInterface; use Jasny\Autowire\AutowireInterface; use Jasny\Autowire\ReflectionAutowire; use Psr\Container\ContainerInterface; $container = new Container([ AutowireInterface::class => static function(ContainerInterface $container) { return new ReflectionAutowire($container); }, Foo::class => static function(ContainerInterface $container) { return new Foo(); }, BarInterface::class => static function(AutowireContainerInterface $container) { return $container->autowire(Bar::class); } ]);
Container
类实现了AutowireContainerInterface
,该接口定义了一个autowire
方法。第一个参数是类名。
还可以提供其他参数,这些参数将直接传递给构造函数。不会对这些参数应用自动装配。
有关更多信息,请参阅jasny/autowire。
提示:自动装配会增加耦合度,因此请尽量少用。
读者注意事项
如果您正在使用PHPStorm IDE,请安装dynamic return type plugin,以便在进行$container->get(SomeInterface::class)
时获得正确的类型提示。