pwm/sfw-container

一个简单的容器,确保无环依赖图

3.0.0 2018-10-12 21:12 UTC

This package is auto-updated.

Last update: 2024-09-14 00:18:51 UTC


README

Build Status codecov Maintainability Test Coverage License: MIT

一个具有循环检测、可缓存实例解析和动态加载的最小化DI容器。

目录

为什么

市面上有各种各样的DI容器。我的设计目标是

  • 简约
  • 在依赖图中进行循环检测
  • 默认实例缓存
  • 动态加载

要求

PHP 7.1+

安装

composer require pwm/sfw-container

使用

基本用法

// Have some classes, some of them depend on others
class A { public function __construct(int $x) {} }
class B { public function __construct(string $s) {} }
class C { public function __construct(A $a, B $b) {} }

// Create a container
$c = new Container();

// Add resolver functions to the container that will instantiate your classes
$c->add(A::class, function (): A {
    return new A(1);
});
$c->add(B::class, function (): B {
    return new B('x');
});

// Resolving from within resolvers is easy as the Container is passed to the functions as the 1st parameter
$c->add(C::class, function (Container $c): C {
    return new C(
        $c->resolve(A::class),
        $c->resolve(B::class)
    );
});

// Resolve your classes
assert($c->resolve(A::class) instanceof A);
assert($c->resolve(B::class) instanceof B);
assert($c->resolve(C::class) instanceof C);

循环检测

// X depends on Y and Y depends on X ...
class X { public function __construct(Y $y) {} }
class Y { public function __construct(X $x) {} }

$c = new Container();

$c->add(X::class, function (Container $c): X {
    return new X($c->resolve(Y::class));
});
$c->add(Y::class, function (Container $c): Y {
    return new Y($c->resolve(X::class));
});

try {
    $c->resolve(X::class);
} catch (CycleDetected $e) {
    assert('X -> Y -> X' === $e->getMessage());
}

工厂与缓存实例

// Simple class that saves a timestamp
class TS {
    private $timestamp;
    public function __construct(int $timestamp) {
        $this->timestamp = $timestamp;
    }
    public function getTimestamp(): int {
        return $this->timestamp;
    }
}

$c = new Container();

// Add our TS class both cached and as a factory (using different keys)
$c->add('cachedTS', function (): TS {
    return new TS(time());
});
$c->factory('factoryTS', function (): TS {
    return new TS(time());
});

// Get an instance for both
$cTS = $c->resolve('cachedTS'); // instantiate and cache
$fTS = $c->resolve('factoryTS'); // just instantiate

// Wait a sec ...
sleep(1);

// For the cached ones timestamps will match
assert($cTS->getTimestamp() === $c->resolve('cachedTS')->getTimestamp());

// Factory instantiates again, hence timestamps will differ
assert($fTS->getTimestamp() !== $c->resolve('factoryTS')->getTimestamp());

动态加载

interface Reader {
    public function read(): string;
}
class XmlReader implements Reader {
    public function read(): string {
        return 'Reading Xml...';
    }
}
class JsonReader implements Reader {
    public function read(): string {
        return 'Reading Json...';
    }
}

$c = new Container();

// Reader's resolver has to be added via factory otherwise
// it will be bound to whatever it first resolves to
$c->factory(Reader::class, function (Container $c, string $strategy): Reader {
    switch ($strategy) {
        case 'xml':
            return new XmlReader();
        case 'json':
            return new JsonReader();
        default:
            throw new RuntimeException(sprintf('No reader found for %s', $strategy));
    }
});

assert('Reading Xml...' === $c->resolve(Reader::class, 'xml')-> read());
assert('Reading Json...' === $c->resolve(Reader::class, 'json')-> read());

try {
    $c->resolve(Reader::class, 'csv');
} catch (RuntimeException $e) {
    assert('No reader found for csv' === $e->getMessage());
}

工作原理

容器只是一个映射,键是我们定义的类的ID,值是称为解析器的函数。解析一个类简单地意味着执行它的解析器,这将返回该类的实例。使用包含命名空间的全类名作为ID是良好的实践,但我们可以使用任何我们喜欢的字符串。

所有解析器都将容器本身作为它们的第一个参数,这使得在解析器内部进行解析变得容易,从而使解析过程递归。这对于类可能具有其他类作为依赖项非常有用。

虽然我们可以构建我们喜欢的任何依赖图,但解析过程将在遍历过程中遇到循环时停止。这确保我们只处理无环图(即DAGs),并使我们免于调用栈溢出。

容器默认缓存解析实例,这意味着后续的解析将返回相同的实例。这是好的,通常也是我们想要的。然而,如果解析器是通过factory()方法添加的,则每次解析都将返回该类的新实例。

我们还可以向解析器传递额外的参数。结合factory(),这使得实现动态加载成为可能,即以不同的方式实例化接口。一个例子是所谓的策略模式,即使用运行时信息,例如用户提供的CLI参数,来实例化某些接口的适当实现。

测试

$ vendor/bin/phpunit
$ composer phpcs
$ composer phpstan
$ composer infection

变更日志

点击这里

许可证

MIT