hartmann/planck

极简主义,PSR-11(+) 兼容,基于提供者的容器

2.2.0 2019-04-23 17:09 UTC

This package is auto-updated.

Last update: 2024-09-25 21:37:50 UTC


README

Latest Stable Version License

Planck

Planck 是一个极简主义的依赖注入容器,具有 PSR-11+ 支持,灵感来源于 Pimple/Simplex。目前,我甚至使用了它们的大部分文档,但以后我会做出改变。

  • Hartmann\Planck\Container 实现 ContainerInterface 并完全支持 container-interop 的 ServiceProviderInterface

    • $container->extend()
      • 可以用来扩展标量值、工厂和服务
    • $container->factory()
      • 可以用来标记一个可调用对象为工厂服务。如果是这样,每次请求条目时都会返回一个新的实例
    • $container->preserve()
      • 可以用来保护/保留一个函数不被容器用作服务工厂
    • $container->autowire()
      • 可以用来自动连接函数和类

安装

composer require hartmann/planck

使用

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

$container = new \Hartmann\Planck\Container();

定义服务提供者

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

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

use Interop\Container\ServiceProviderInterface

class Provider implements ServiceProviderInterface
{
    public function getFactories()
    {
        return [
            stdClass::class => function(ContainerInterface $container) {
                return new stdClass;
            },
            ...
        ];
    }
    
    public function getExtensions()
    {
        return [
            stdClass::class => function(ContainerInterface $container, ?stdClass $class) {
                $class->foo = 'bar';
                
                return $class;
            },
            ...
        ];
    }
}

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

由于对象仅在获取时创建,因此定义的顺序无关紧要。

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

$class = $container->get(stdClass::class);

定义工厂服务

默认情况下,每次获取服务时,Planck 都返回其 相同实例。如果您希望所有调用都返回不同的实例,请使用 factory() 方法包装匿名函数

$container->set('factory', $container->factory(function (ContainerInterface $container) {
    return new stdClass;
}));

现在每次调用 $container->get(stdClass::class) 都会返回一个新的 stdClass 实例。

定义参数

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

$container->set('cookie_name', 'SESSION_ID');
$container->set('session_storage_class', 'SessionStorage');

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

保留/保护参数

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

$container->set('random_bytes', $container->preserve(function () {
    return random_bytes(4);
}));

定义后修改服务

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

$container->set('session_storage', function (ContainerInterface $container) {
    return new $container->get('session_storage_class')($container->get('cookie_name'));
});

$container->extend('session_storage', function (ContainerInterface $container, ?SessionStorage $storage) {
    $storage->...();

    return $storage;
});

自动连接

有时在容器本身上解决依赖项是有用的。为此,可以使用 autowire() 方法。
类和匿名函数都可以连接。

$container->set(Foo::class, new Foo());
$container->set(Bar::class, new Bar());

$container->set('autowired', $container->autowire(function (Foo $foo, Bar $bar) {
    return ...
}));

autowire() 方法有两个参数 array $parameters = []
如果您知道容器无法解析参数或您希望传递自己的值,您可以轻松地做到这一点

class Foo {
    ...
}

$container->set(Foo::class, new Foo());
$container->set('autowired', $container->autowire(function (Foo $foo, $bar) {
    var_dump($foo) // object Foo
    var_dump($bar) // string 'foo'
}, ['bar' => 'foo']]));

自版本 1.0.3 开始,可以将可调用对象以数组形式传递。
这允许自动注入静态和非静态对象方法,这在许多方面都非常有用,例如控制器。

class HomeController {
    
    protected $logger
    
    public function __contruct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    
    public function index(Request $request, Response $response): Response 
    {
        $this->logger->info('someone visited my site!');
        
        return $response->write('Hello');
    }    
}

// adding the required classes to the container ...
$container->autowire([HomeController::class, 'index']);

这也适用于已经实例化的对象。

$container->autowire([$homeControllerInstance, 'index']);

只要依赖项已在容器中注册,扩展类就会按正常方式表现。

class Request
{
    public function __construct(string $method, UriInterface $uri, HeadersInterface $headers, ...);
}

class CreateUserRequest extends Request {
    ... 
}

// adding the required classes to the container ...
$container->autowire(CreateUserRequest::class, ['method' => $requestMethod]);

提示参数 & 自动注入

PHP 5 和 7 中添加了命名参数。Planck 可以处理内置和常规提示。
以下是一些可能的配置。

// unresolvable, must be passed directly to the parameters
function ($foo) {  
}                             

// unresolvable, must be passed directly to the parameters
function (string|int|float|array|bool $foo) {  
}

// hinted, required
function (Foo $foo) { 
}                         

// hinted, optional
function (Foo $foo = null) {  
}                  

// hinted, nullable
function (?Foo $foo) {  
}                        

如果找不到可空参数的值,则传递 null。
如果找不到可选参数的值,则传递默认值。

不支持引用参数,您必须使用 set 方法注册此类条目。

隐式自动注入

Planck 还提供了隐式自动注入的选项,即尚未存储在容器中的类请求可以自动创建。

要激活此功能,必须调用以下方法

$container->enableImplicitAutowiring(true); // enable
$container->enableImplicitAutowiring(false); // disable

现在可以无错误地调用以下内容

$container = new \Hartmann\Planck\Container
$container->enableImplicitAutowiring(true);

$container->set('autowired', $container->autowire(function(Foo $foo, Bar $bar) {
    return ...
}));

$value = $container->get('autowired');

在类被隐式加载后,它将直接存储在容器中。
只能隐式加载类。

解析策略

可以使用解析策略自动解析可以类似创建的类。
例如,如果您使用 FormRequests 来验证输入字段,它们可以使用相应的策略进行解析,而无需为每个创建服务工厂。

这可能看起来像这样

use \Hartmann\ResolveStrategy\ResolveStrategyInterface

class RequestResolveStrategy implements ResolveStrategyInterface
{
    public function suitable(string $class): bool
    {
        return method_exists($class, 'createFromEnvironment') && in_array(FormRequest::class, class_parents($class));
    }

    public function resolve(\Psr\Container\ContainerInterface $container, string $class)
    {
        return call_user_func([$class, 'createFromEnvironment'], $container->get('environment'));
    }
}

$container = new \Hartmann\Planck\Container();

$container->enableImplicitAutowiring(true);
$container->addResolveStrategy(new RequestResolveStrategy());

$container->get(CreateUserFormRequest::class);
$container->get(DeletePostFormRequest::class);
$container->get(LoginFormRequest::class);

要使此功能正常工作,必须启用隐式自动注入。