jasny/container

此包已被废弃且不再维护。作者建议使用 php-di/php-di 包。

简单依赖注入容器,支持自动装配

v2.1.2 2020-04-19 06:18 UTC

This package is auto-updated.

Last update: 2023-05-29 23:48:49 UTC


README

Build Status Code Coverage Scrutinizer Code Quality Packagist Stable Version Packagist License

此包包含一个简单的依赖注入容器,与 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,但更常见的是 GlobIteratorRecursiveDirectoryIterator。请参阅 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.hostconfig.db.settingsconfig.dbconfig

检查条目

要检查容器是否有条目,可以使用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)时获得正确的类型提示。