pimple/pimple

Pimple,一个简单的依赖注入容器

安装量: 108,273,836

依赖: 1,643

建议者: 52

安全: 0

星标: 2,642

关注者: 103

分支: 307

v3.5.0 2021-10-28 11:13 UTC

README

注意!

Pimple 已不再接受更改。不会添加新功能,也不会接受任何外观上的更改。唯一接受的变化是与更新版本的 PHP 兼容性和安全问题的修复。

注意!

这是 Pimple 3.x 的文档。如果您使用的是 Pimple 1.x,请阅读 Pimple 1.x 文档。阅读 Pimple 1.x 代码也是了解如何创建简单的依赖注入容器(Pimple 的最新版本更注重性能)的好方法。

Pimple 是一个用于 PHP 的小型依赖注入容器。

安装

在您的项目中使用 Pimple 之前,将其添加到您的 composer.json 文件中

$ ./composer.phar require pimple/pimple "^3.0"

用法

创建容器只需要创建一个 Container 实例

use Pimple\Container;

$container = new Container();

与其他依赖注入容器一样,Pimple 管理两种不同的数据:服务参数

定义服务

服务是一个对象,作为更大系统的一部分执行某些操作。服务的示例:数据库连接、模板引擎或邮件发送者。几乎任何 全局 对象都可以是服务。

服务通过返回对象实例的 匿名函数 来定义

// define some services
$container['session_storage'] = fn($c) => new SessionStorage('SESSION_ID');

$container['session'] = fn($c) => new Session($c['session_storage']);

注意,匿名函数可以访问当前容器实例,允许引用其他服务或参数。

由于对象只在获取时创建,因此定义的顺序并不重要。

使用定义的服务也非常简单

// get the session object
$session = $container['session'];

// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $session = new Session($storage);

定义工厂服务

默认情况下,每次您获取一个服务时,Pimple 都会返回相同的实例。如果您想为所有调用返回不同的实例,请使用 factory() 方法将匿名函数包装起来

$container['session'] = $container->factory(fn($c) => new Session($c['session_storage']));

现在,每次调用 $container['session'] 都会返回会话的新实例。

定义参数

定义参数可以简化从外部配置容器并存储全局值的过程

// define some parameters
$container['cookie_name'] = 'SESSION_ID';
$container['session_storage_class'] = 'SessionStorage';

如果您像下面这样更改 session_storage 服务的定义

$container['session_storage'] = fn($c) => new $c['session_storage_class']($c['cookie_name']);

现在您可以通过覆盖 cookie_name 参数而不是重新定义服务定义来轻松更改 cookie 名称。

保护参数

由于 Pimple 将匿名函数视为服务定义,您需要使用 protect() 方法将匿名函数包装起来以将其存储为参数

$container['random_func'] = $container->protect(fn() => rand());

在定义之后修改服务

在某些情况下,您可能想在定义之后修改服务定义。您可以使用 extend() 方法定义在服务创建后运行的附加代码

$container['session_storage'] = fn($c) => new $c['session_storage_class']($c['cookie_name']);

$container->extend('session_storage', function ($storage, $c) {
    $storage->...();

    return $storage;
});

第一个参数是要扩展的服务的名称,第二个是一个函数,它可以访问对象实例和容器。

扩展容器

如果您反复使用相同的库,您可能希望将一些服务从一项项目重用到另一项项目中;通过实现 Pimple\ServiceProviderInterface 将服务打包到一个 提供者

use Pimple\Container;

class FooProvider implements Pimple\ServiceProviderInterface
{
    public function register(Container $pimple)
    {
        // register some services and parameters
        // on $pimple
    }
}

然后,在容器上注册提供者

$pimple->register(new FooProvider());

获取服务创建函数

当你访问一个对象时,Pimple会自动调用你定义的匿名函数,为你创建服务对象。如果你想要直接访问这个函数,可以使用raw()方法

$container['session'] = fn($c) => new Session($c['session_storage']);

$sessionFunction = $container->raw('session');

PSR-11 兼容性

由于历史原因,Container类没有实现PSR-11的ContainerInterface接口。然而,Pimple提供了一个辅助类,可以让你将代码与Pimple容器类解耦。

PSR-11 容器类

Pimple\Psr11\Container类允许你使用Psr\Container\ContainerInterface方法访问底层Pimple容器的内容

use Pimple\Container;
use Pimple\Psr11\Container as PsrContainer;

$container = new Container();
$container['service'] = fn($c) => new Service();
$psr11 = new PsrContainer($container);

$controller = function (PsrContainer $container) {
    $service = $container->get('service');
};
$controller($psr11);

使用PSR-11 ServiceLocator

有时,一个服务可能需要访问几个其他服务,但又不确定所有这些服务都会实际被使用。在这些情况下,你可能希望服务的实例化是延迟的。

传统的解决方案是将整个服务容器注入,以获取真正需要的那些服务。然而,这并不可取,因为它给服务提供了对应用程序其余部分的过于广泛的访问,并隐藏了它们的实际依赖关系。

ServiceLocator旨在通过在真正需要时才实例化它们,来解决这个问题,同时提供对一组预定义服务的访问。

它还允许你将服务在不同于注册时的名称下提供。例如,你可能希望使用一个期望EventDispatcherInterface实例的event_dispatcher名称,而你的事件调度器已经注册为dispatcher

use Monolog\Logger;
use Pimple\Psr11\ServiceLocator;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

class MyService
{
    /**
     * "logger" must be an instance of Psr\Log\LoggerInterface
     * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface
     */
    private $services;

    public function __construct(ContainerInterface $services)
    {
        $this->services = $services;
    }
}

$container['logger'] = fn($c) => new Monolog\Logger();
$container['dispatcher'] = fn($c) => new EventDispatcher();

$container['service'] = function ($c) {
    $locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher'));

    return new MyService($locator);
};

延迟引用服务集合

如果消耗集合的类只需在稍后某个方法被调用时迭代它,那么在数组中传递服务实例的集合可能会低效。如果集合中存储的服务与消耗它的类之间存在循环依赖关系,这也可能导致问题。

ServiceIterator类可以帮助你解决这些问题。它在实例化时接收一个服务名称列表,并在迭代时检索这些服务。

use Pimple\Container;
use Pimple\ServiceIterator;

class AuthorizationService
{
    private $voters;

    public function __construct($voters)
    {
        $this->voters = $voters;
    }

    public function canAccess($resource)
    {
        foreach ($this->voters as $voter) {
            if (true === $voter->canAccess($resource)) {
                return true;
            }
        }

        return false;
    }
}

$container = new Container();

$container['voter1'] = fn($c) => new SomeVoter();
$container['voter2'] = fn($c) => new SomeOtherVoter($c['auth']);
$container['auth'] = fn ($c) => new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2'));