mindplay/boxy

此包已被废弃且不再维护。作者建议使用mindplay/unbox包代替。

开源、简单、类型提示的服务容器

2.0.0 2015-05-15 23:04 UTC

This package is auto-updated.

Last update: 2021-06-05 13:46:47 UTC


README

PHP 5.5及以上版本的开放、简单、类型提示(并类型检查)的依赖注入容器。

⚠️ 警告:此项目不再维护,已被Unbox取代。

灵感来自Pimple,但优化了现代IDE(如Php Storm ✌️)的全IDE支持,包括设计时和运行时类型检查,在提供者和消费者端均如此。

Build Status

Code Coverage

Scrutinizer Code Quality

基本用法

创建容器实例

use mindplay\boxy\Container;

$container = new Container();

可以将服务对象直接(提前)插入到容器中

$container->insertService(new Database());

或者您可以注册工厂函数以在尽可能晚的时候创建服务

$container->registerService(
    Mapper::class,
    function (Database $db) {
        // type-hinted argument gets resolved and Database instance gets provided

        return new Mapper($db); // return type will be checked
    }
);

消费者可以通过提供一个要调用的函数来请求服务

$container->invoke(function (Database $db, Mapper $mapper) {
    // type-hinted arguments are resolved - the Mapper and Database instance
    // are constructed as needed and provided for the consumer function.
});

您还可以通过使用可选参数定义可选依赖

$container->invoke(function (Optional $stuff = null) {
    if ($stuff) {
        // ...
    }
});

在这个例子中,如果类Optional未在容器中注册,函数仍然会被调用,但会传递一个null参数 - 请务必检查可选参数的存在。

可能很明显,但请注意,即使所有参数都是可选的,且所有服务/组件都不可用,函数仍然会被调用。

组件工厂用法

您可以注册工厂函数以按需创建组件

$container->registerComponent(
    ArticleFinder::class,
    function (Database $db) {
        return new ArticleFinder($db);
    }
);

消费者可以像请求服务一样请求新的组件实例

$container->invoke(function (ArticleFinder $finder) {
    // a new ArticleFinder component is created every time you call invoke
});

换句话说,语法是相同的;填充容器的任何人都可以决定给定类型是注册为服务(每次都相同)还是组件(每次都是新实例)。

命名服务和组件

您可以可选地命名服务/组件定义 - 在您有两个相同类的不同实例或希望提供抽象类或接口的不同实现的情况下,这很有用。每个公共API方法都有一个命名的对应方法 - 例如,这里我们注册了两个不同的缓存组件,都注册到了一个公共接口

$container->registerNamed('file_cache', CacheInterface::class, function () {
    return new FileCache(...);
});

$container->registerNamed('memory_cache', CacheInterface::class, function () {
    return new MemoryCache(...);
});

然后,消费者可以通过使用与它们注册时匹配的参数名称来请求这些服务

$container->invoke(function (CacheInterface $file_cache) {
    echo get_class($file_cache); // => FileCache
});

$container->invoke(function (CacheInterface $disk_cache) {
    echo get_class($disk_cache); // => DiskCache
});

在这里

$container->registerService(CacheInterface::class, function () {
    return new MockCache();
});

invoke()方法始终尝试首先提供具有匹配名称的服务/组件,但如果找不到,则会回退到仅匹配类型的定义 - 因此,在单元测试场景(您没有上面的registerNamed调用)中,您可以通过注册单个模拟缓存来模拟上面的示例中的两个依赖项。

现在,invoke()方法将为任何CacheInterface参数提供MockCache实例,无论名称是否匹配。

配置服务和组件

可以为服务或组件注册额外的配置函数

$container->configure(function (Database $db) {
    $db->exec("set names utf8");
});

配置函数将尽可能晚地执行,例如,您第一次调用 invoke() 并请求服务或组件时。(如果服务已经初始化,配置函数将立即执行。)

覆盖服务

可以覆盖之前注册的服务创建函数

$container->overrideService(
    Database::class,
    function () {
        return new Database();
    }
);

您也可以随时覆盖组件工厂函数,但请注意,仅在服务初始化之前可以覆盖服务创建函数 - 初始化后的尝试覆盖将引发异常。

打包服务提供者

通过实现 Provider 接口,可以将服务/组件定义打包起来以便于重用

use mindplay\boxy\Provider;

class ServiceProvider implements Provider
{
    public function register(Container $container)
    {
        $container->registerService(
            Database::class,
            function () {
                return new Database();
            }
        );
    }
}

然后注册您的自定义提供者到容器实例

$container->register(new ServiceProvider);

请注意,提供者将立即注册 - 这意味着如果您想要惰性初始化,您仍然应该使用 registerService() 而不是 insertService()

消费者接口

通过实现 Consumer 接口,您可以使组件与服务容器显式互操作,该接口仅是一个返回 invoke() 方法的函数 - 这提供了一种方法,在添加设置器或不使内容公开的情况下,将类开放给依赖注入(包括在测试期间,独立于 Container 实例)。

例如

class PetStore implements Consumer
{
    protected $cat;
    protected $dog;
    
    public function getInjector()
    {
        return function (Cat $cat, Dog $dog) {
            $this->cat = $cat;
            $this->dog = $dog;
        };
    }
}

现在,假设您已经有一个配置好的 Container,该容器提供 CatDog 的实例,您可以通过将 PetStore 传递给 provide() 方法来提供这些依赖项

$container->provide($store = new PetStore);

在此调用之后,由 getInjector() 方法返回的函数已被调用,并且已经提供了 CatDog 依赖项。