rock-symphony/container

基于Laravel Container的独立服务容器实现

3.1.0 2017-12-03 14:54 UTC

This package is auto-updated.

Last update: 2024-08-29 04:25:33 UTC


README

Build Status StyleCI

基于Laravel Container的独立服务容器实现。

哲学

功能

  • PHP 5.4+,PHP 7.0+
  • 自动依赖项解析
  • 依赖注入的构造函数调用
  • 依赖注入的方法调用

使用方法

安装

使用 composer

composer require rock-symphony/container:^2.0

基础知识

当然,您可以向容器中添加服务(->set())并从中获取它们(->get()),以及检查容器是否具有特定的服务(->has())。

<?php

use RockSymphony\ServiceContainer\ServiceContainer;

$container = new ServiceContainer();

// Definition:
// Set a service instance to container
$container->set('acme', new AcmeService());

// Consumer:
// Check if there is a service binding for given service ID  
echo $container->has('acme') ? 'It has acme service' : 'wtf?';

// Get a service from container
$acme = $container->get('acme');
$acme->doSomeStuff();

使用抽象接口

通过绑定服务到它们的抽象接口,可以方便地在定义和消费者两端显式地声明接口。

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
// Note we bind instance by it's **abstract** interface.
// This way you force consumers to not care about implementation details, but rely on interface. 
$container->set(\Psr\Log\LoggerInterface::class, $my_fancy_psr_logger_implementation);

// Consumer:
// Then you have a consumer that needs a logger implementation,
// but doesn't care on details. It can use any PSR-compatible logger.
$logger = $container->get(\Psr\Log\LoggerInterface::class);
$logger->info('Nice!');

别名

有时您可能还希望使用不同的ID绑定相同的服务。您可以使用别名来实现这一点(->alias()

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->alias('logger', \Psr\Log\LoggerInterface::class);

// Consumer:
$logger = $container->get(\Psr\Log\LoggerInterface::class);
// ... or 
$logger = $container->get('logger'); // 100% equivalent
$logger->info('Nice!');

绑定到解析函数

您可以通过提供解析闭包函数(->bindResolver())来声明服务。服务容器将在每次解决服务时调用该函数。

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->bindResolver('now', function () {
    return new DateTime();
});

// Consumer:
$now = $container->get('now'); // DateTime object
$another_now = $container->get('now'); // another DateTime object

echo $now === $another_now ? 'true' : 'false'; // == false

延迟解析服务绑定

您可以将服务初始化延迟到第一次请求时。解析函数将只被调用一次,其结果将被存储到服务容器中。

它的工作方式类似于->bindResolver(),但将结果存储在第一次调用之后。

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->bindSingletonResolver('cache', function () {
    return new MemcacheCache('127.0.0.1');
});

// Consumer:
$cache = $container->get('cache'); // DateTime object
// do something with $cache

扩展已绑定的服务

您可以使用->extend()方法扩展/装饰现有的服务绑定。

<?php
use RockSymphony\ServiceContainer\ServiceContainer;

/** @var $container ServiceContainer */
// Definition:
$container->deferred('cache', function () {
    return new MemcacheCache('127.0.0.1');
}); 

// Wrap cache service with logging decorator
$container->extend('cache', function($cache, ServiceContainer $container) { 
    // Note: it's passing a service container instance as second parameter
    //       so you can get dependencies from it.
    return new LoggingCacheDecorator($cache, $container->get('logger'));
});

// Consumer:
$cache = $container->get('cache'); // DateTime object
// Uses cache seamlessly as before
// (implying that MemcacheCache and LoggingCacheDecorator have the same interface)

服务容器的隔离扩展

一个用例:您想创建一个新的容器,该容器从现有的容器继承服务。但您不想再次定义服务,使用最初定义的服务。您还希望提供更多服务,而无需修改原始容器。

把它想象成JavaScript变量的作用域:嵌套作用域从父作用域继承所有变量。但定义新作用域变量不会修改父作用域。就是这样。

$parent = new ServiceContainer();
$parent->set('configuration', $global_configuration);

$layer = new ServiceContainerLayer($existing_container);
$layer->set('configuration', $layer_configuration); 
$layer->bindResolver('layer_scope_service', ...);
// and so on

var_dump($parent->get('configuration') === $layer->get('configuration')); // "false"

自动依赖注入

依赖注入构造

您可以通过自动注入来自服务容器的类提示依赖项来构造任何类实例。它将尝试从容器中解析依赖项或递归地构造它们以解析它们的依赖项。

<?php

// Class we need to inject dependencies into
class LoggingCacheDecorator {
    public function __construct(CacheInterface $cache, LoggerInterface $logger, array $options = []) {
        // initialize
    }
}

/** @var $container RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->set(LoggerInterface::class, $logger);
$container->set(CacheInterface::class, $cache);


// Consumer:
$logging_cache = $container->construct(LoggingCacheDecorator::class);
// you can also provide constructor arguments with second parameter:
$logging_cache = $container->construct(LoggingCacheDecorator::class, ['options' => ['level' => 'debug']]);

依赖注入方法调用

您可以通过自动注入来自服务容器的依赖项来调用任何可调用对象。这主要用于调用应用程序HTTP控制器,但不仅限于此。

<?php
/** @var $container RockSymphony\ServiceContainer\ServiceContainer */

class MyController {
    public function showPost($url, PostsRepository $posts, TemplateEngine $templates)
    {
        // Warning! Pseudo-code :)
        $post = $posts->findPostByUrl($url);
        return $templates->render('post.html', ['post' => $post]); 
    }
    
    public static function error404(TemplateEngine $templates)
    {
        return $templates->render('404.html');
    }
}
// 1) It can auto-inject dependencies into instance method callables.
//    In this case it will check container for PostsRepository and TemplateEngine bindings.
//    Or try to create those instances automatically.
//    Or throw an exception if both options are not possible.
$container->call([$container, 'showPost'], ['url' => '/hello-world']);

// 2) It can construct class and auto-inject dependencies into method call:
//    Here it will first construct a new instance of MyController (see `->construct()`)
//    And then follows the same logic as the call 1) above.
$container->call('MyController@showPost', ['url' => '/hello-world']); 
// ... or the same: 
$container->call('MyController', ['url' => '/hello-world'], 'showPost');

// 3) It can auto-inject dependencies into static method call: 
$container->call(['MyController', 'error404']);
// ... or the same:
$container->call('MyController::error404');

// 4) It can auto-inject dependencies into closure function calls  
$container->call(function (PostsRepository $repository) {
    $repository->erase();
});

注意:服务容器仅解析类提示参数(即显式类型提示到类的参数)。您应该使用第二个参数提供所需的标量参数。它将使用默认值作为选项参数(如果您没有指定它们)。

常见问题解答

  1. 为什么不使用 Laravel Container

我们曾在内部项目中使用Laravel Container。但它不是一个好的库链接候选者,因为

  • 它不遵循SemVer - 在每次小版本升级时都会出现BC breaks
  • 它有不需要的依赖项 illuminate/contracts
  • 它被设计为Laravel框架的一部分,因此几乎不能作为库单独使用
  • 您只能在使用特定版本时使用所有Laravel组件(例如,全部在5.3版本;或全部在5.4版本;但不能混用)
  • 如果您想继续前进,您被迫升级到最新的PHP版本(例如,容器5.4需要PHP 7.0)
  • 膨胀的公共API:31个公共API方法(与这个库中的10个公共方法相比)
  • 可疑的方法命名:`->make()` 和 `->build()` 之间有什么区别?

许可协议

本项目根据MIT许可协议授权。