intraworlds/service-container

轻量且强大的自动装配依赖注入容器实现

v0.8.4 2024-02-26 08:38 UTC

This package is auto-updated.

Last update: 2024-09-25 13:24:29 UTC


README

轻量且强大的自动装配依赖注入容器实现。

安装

使用 Composer 安装此包

composer require intraworlds/service-container

用法

该容器实现了标准PSR-11接口。您可以直接使用它进行自动装配。

假设我们正在构建一个缓存客户端。这个客户端不会直接实现缓存,而只是提供通用的API - 它将依赖于给定的适配器。

我们将从定义内存适配器开始

namespace Acme\Cache;

class MemoryAdapter {
  function get(string $key): string {}
  function set(string $key, string $string): void {}
}

缓存客户端

namespace Acme\Cache;

class Client {
  private $adapter;

  function __construct(MemoryAdapter $adapter) {
    $this->adapter = $adapter;
  }

  function get(string $key) {
    $string = $this->adapter->get($key);
    return unserialize($string);
  }

  function set(string $key, $value): void {
    $string = serialize($value);
    $this->adapter->set($key, $string);
  }
}

现在在主程序中,我们可以轻松地实例化缓存客户端 - 客户端的依赖关系将被自动解决。

namespace Acme;

use IW\ServiceContainer;

$container = new ServiceContainer;
$client = $container->get(Cache\Client::class);

我们尚未自己实现序列化。让我们将其移到依赖中。

namespace Acme\Cache;

class PhpSerializer {
  function serialize($value): string {}
  function unserialize(string $string) {}
}

客户端有少许变化。

namespace Acme\Cache;

class Client {
  private $adapter;

  function __construct(MemoryAdapter $adapter, PhpSerializer $serializer) {
    $this->adapter = $adapter;
    $this->serializer = $serializer;
  }

  function get(string $key) {
    $string = $this->adapter->get($key);
    return $this->serializer->unserialize($string);
  }

  function set(string $key, $value): void {
    $string = $this->serializer->serialize($value);
    $this->adapter->set($key, $string);
  }
}

我们的主要代码保持不变。

$client = $container->get(Cache\Client::class);

resolve 方法对于解决调用者的任何依赖非常有用。特别是对于init模板方法非常有用。请看以下示例。

abstract class Parent {
  function __construct(Dependency $dependency, ServiceContainer $container) {
    $this->dependency = $dependency;
    $container->resolve([$this, 'init']);
  }
}

class Child extends Parent {
  function init(AhotherDependency $another) {
    // ...
  }
}

手动装配

有时您可能想手动配置容器。让我们考虑以下关于命令模式的示例。

interface OrderCommand
{
  function execute();
}

class OrderInvoker
{
  function __construct(private OrderCommand ...$commands) {}

  function execute() : void {
    array_walk($this->commands, fn($command) => $command->execute());
  }
}

使用 IW\ServiceContainer,您有几种方法可以解决 OrderInvoker 的依赖关系。

// an alias but that's no good for multiple commands
$container->alias('OrderInvoker', 'ReserveItems');

// external factory
$container->bind('OrderInvoker', function (IW\ServiceContainer $container) {
  return new OrderInvoker($container->get('ReserveItems'), $container->get('SendInvoice'));
});

// internal factory
$container->bind('OrderInvoker', 'OrderInvoker::create');

class OrderInvoker
{
  static function create(ReserveItems $reserveItems, SendInvoice $sendInvoice) : OrderInvoker {
    return new OrderInvoker($reserveItems, $sendInvoice);
  }
}

// wiring factory
$container->wire('OrderInvoker', 'ReserveItems', 'SendInvoice');

// using annotations (TBD PHP 8.0), used as a fallback (can be overridden by defining factory directly (eg. in tests)
class OrderInvoker
{
  #[IW\ServiceContainer\Bind('create')]
  function __construct(private OrderCommand ...$commands) {}

  static function create(ReserveItems $reserveItems, SendInvoice $sendInvoice) : OrderInvoker {
    return new OrderInvoker($reserveItems, $sendInvoice);
  } 
}

所有方法都有其优点。内部工厂方法对于静态分析很好。装配工厂对于通用应用程序模式和当依赖关系可能变化时非常有用。

待办事项 继续举例说明

待办事项

$exception->getOrigin(); // returns first exception outside the framework (useful for avoiding of tracing)

许可证

本包的所有内容均受MIT许可证许可。