xtompie / container
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^11.3
README
Container 类为 PHP 应用程序中管理依赖关系和类实例提供了基本功能。它允许您
- 检索一个唯一的共享容器实例(单例模式)。
- 自动使用反射解析类实例,包括处理构造函数依赖项。
- 将接口绑定到具体类,以便进行依赖注入。
- 将一个类别名为另一个类,允许不同类实现可互换使用。
- 管理共享(单例)实例,确保多次使用时返回相同的实例。
- 使用服务提供程序来控制如何解析特定服务。
- 配置瞬态服务,每次访问时返回不同的实例。
要求
- PHP >= 8
安装
使用 composer
composer require xtompie/container
使用方法
单例
Container 类实现了单例模式,确保整个应用程序使用相同的容器实例。这对于管理全局服务和依赖项很有用。
use Xtompie\Container\Container; $container1 = Container::container(); $container2 = Container::container(); var_dump($container1 === $container2); // true
获取
Container 会自动使用 PHP 的反射功能解析类实例。它检查类构造函数以确定所需的依赖项,并自动解析它们。
use Xtompie\Container\Container; class Foo { // Foo class logic } $container = new Container(); $foo = $container->get(Foo::class); var_dump($foo instanceof Foo); // true
解析
Container 类提供了 resolve
方法,允许您在解析依赖项时直接传递特定值到类的构造函数。当您想覆盖或注入容器无法自动解析的某些参数时,这很有用。
与可以返回共享实例的 get
方法不同,resolve
方法始终返回类的新的实例,即使多次调用。这确保了瞬态行为并避免了实例之间的共享状态。
use Xtompie\Container\Container; class Bar { public function __construct( public Foo $foo, public ?string $qux = null ) { } } $container = new Container(); $values = ['qux' => 'quxx']; $bar = $container->resolve(Bar::class, $values); var_dump($bar instanceof Bar); // true var_dump($bar->qux); // 'quxx'
在此示例中,resolve()
方法手动提供值 quxx
给 qux
参数,而容器自动解析 Foo
依赖项。每次调用 resolve
都创建一个新的 Bar
实例。
依赖项
如果类在其构造函数中需要依赖项,则 Container 也会解析它们。它会检查构造函数并创建所需的类的实例。
use Xtompie\Container\Container; class Bar { public function __construct( public Foo $foo, public ?string $qux = null ) { } } $container = new Container(); $bar = $container->get(Bar::class); var_dump($bar instanceof Bar); // true var_dump($bar->foo instanceof Foo); // true var_dump($bar->qux); // null
绑定
您可以将接口绑定到具体类,使容器在请求接口时解析正确的实现。
use Xtompie\Container\Container; interface FooInterface {} class Foo implements FooInterface {} $container = new Container(); $container->bind(FooInterface::class, Foo::class); $fooInterface = $container->get(FooInterface::class); var_dump($fooInterface instanceof Foo); // true
别称
容器允许您将一个类别名为另一个类,使不同类实现可互换使用。
use Xtompie\Container\Container; class Foo2 {} $container = new Container(); $container->bind(Foo::class, Foo2::class); $foo = $container->get(Foo::class); var_dump($foo instanceof Foo2); // true
共享
容器管理共享服务,确保每次检索服务时返回相同的实例。这对于管理单例很有用。
use Xtompie\Container\Container; $container = new Container(); $instance1 = $container->get(Foo::class); $instance2 = $container->get(Foo::class); var_dump($instance1 === $instance2); // true
瞬态
瞬态服务每次访问时都返回一个新的实例。通过将类标记为瞬态,Container 确保每次访问都返回不同的实例。
您可以通过使用 transient()
方法手动定义服务为瞬态,或者实现 Transient
接口以自动将类标记为瞬态。
use Xtompie\Container\Container; $container = new Container(); $container->transient(Foo::class); $instance1 = $container->get(Foo::class); $instance2 = $container->get(Foo::class); var_dump($instance1 === $instance2); // false
或者,如果服务类实现了 Transient
接口,则 Container 将自动将其视为瞬态,这意味着每次检索都会返回一个新的实例。
use Xtompie\Container\Container; use Xtompie\Container\Transient; class Foo implements Transient {} $container = new Container(); $instance1 = $container->get(Foo::class); $instance2 = $container->get(Foo::class); var_dump($instance1 === $instance2); // false
在两种情况下,这都确保了瞬态对象总是新的实例,而不是共享服务,每次都返回相同的对象。
提供者
提供者可用于更复杂的服务解析。提供者是一个实现了 Provider
接口的类,这允许您控制容器内特定服务的创建或管理方式。提供者将额外的逻辑注入到解析过程中。
这是 Provider
接口
namespace Xtompie\Container; interface Provider { public static function provide(string $abstract, Container $container): mixed; }
要使用提供者,首先创建一个实现了 Provider
接口的类。应返回服务实例的 provide()
方法,它将在需要时由容器调用。
提供者类示例
use Xtompie\Container\Container; use Xtompie\Container\Provider; class Baz {} class BazProvider implements Provider { public static function provide(string $abstract, Container $container): mixed { return new Baz(); } }
然后您可以向容器注册提供者并检索服务
use Xtompie\Container\Container; $container = new Container(); $container->provider('Quux', BazProvider::class); $quux = $container->get('Quux'); var_dump($quux instanceof Baz); // true
这种方法允许在容器内解析服务时进行更复杂或条件逻辑。
自提供服务
一个服务可以通过实现 Provider
接口来提供自己的提供者。这允许一个类自动为容器提供实例,而无需在容器中注册提供者。
如果一个类实现了 Provider
接口,容器会识别并自动将其用作提供者在创建实例时使用。
use Xtompie\Container\Container; use Xtompie\Container\Provider; class Qux implements Provider { public static function provide(string $abstract, Container $container): object { return new Qux(val: 42); } public function __construct( public int $val, ) { } } $container = new Container(); $qux = $container->get(Qux::class); var_dump($qux instanceof Qux); // true var_dump($qux->val); // 42
在此示例中,Qux
类实现了 Provider
接口,这意味着容器将自动在类中找到提供者并使用它来创建实例。这样就无需手动在容器中注册提供者。
多绑定
如果您绑定多个类,容器将解析最具体的类。这对于管理多个抽象级别或类继承很有用。
use Xtompie\Container\Container; $container = new Container(); $container->bind(FooInterface::class, Foo::class); $container->bind(Foo::class, Foo2::class); $fooInterface = $container->get(FooInterface::class); var_dump($fooInterface instanceof Foo2); // true
调用
容器 类包含一个 call
方法,允许您使用自动依赖注入调用任何回调。此方法适用于闭包(匿名函数)、静态类方法和实例方法。容器自动解析并注入回调所需的依赖项。
使用闭包(匿名函数)
use Xtompie\Container\Container; class Foo { public function method(): string { return 'Foo'; } } $container = new Container(); $result = $container->call(function(Foo $foo) { return $foo->method(); // 'Foo' }); echo $result; // Foo
在此示例中,容器自动解析 Foo
依赖项并将其注入到闭包中。
使用静态类方法
use Xtompie\Container\Container; class Foo { public function method(): string { return 'Foo'; } } class Call { public static function f1(Foo $foo): string { return $foo->method(); } } $container = new Container(); $result = $container->call([Call::class, 'f1']); echo $result; // Foo
对于静态类方法,容器在调用静态方法之前解析依赖项并将它们注入其中。
使用实例方法
use Xtompie\Container\Container; class Foo { public function method(): string { return 'Foo'; } } class Call { public function f2(Foo $foo): string { return $foo->method(); } } $container = new Container(); $call = new Call(); $result = $container->call([$call, 'f2']); echo $result; // Foo
在这里,容器将 Foo
依赖项注入到实例方法中,然后在该提供对象上调用该方法。
带值调用
Container
类包含一个 call
方法,允许您使用自动依赖注入调用可调用的。此外,您还可以传递自定义值以覆盖容器的自动解析。
use Xtompie\Container\Container; class Foo { public function method(): string { return 'Foo'; } } class Bar { public function execute(Foo $foo, string $name): string { return $foo->method() . ' ' . $name; } } $container = new Container(); $customValues = [ 'name' => 'CustomName', ]; $bar = new Bar(); $result = $container->call([$bar, 'execute'], $customValues); echo $result; // Foo CustomName
在此示例中
Container
解析一个Foo
实例。- 我们传递一个自定义字符串值
'CustomName'
作为$name
参数,这覆盖了容器解析它的需求。
callArgs
callArgs
方法允许您检索调用给定可调用(函数、方法等)所需的自定义值,并允许使用自动依赖解析。它会检查可调用参数并解析必要的依赖项,可选地允许使用自定义值或解析器。
use Xtompie\Container\Container; class Foo { public function method(): string { return 'Foo'; } } $container = new Container(); $args = $container->callArgs(function(Foo $foo) { return $foo->method(); }); var_dump($args); // Array with resolved arguments, e.g., [Foo instance]
自定义值
您可以将自定义值传递给 callArgs
以覆盖容器的默认解析行为。
$customValues = ['foo' => new Foo2()]; $args = $container->callArgs(function(Foo $foo) { return $foo->method(); }, $customValues); var_dump($args); // Custom values take precedence over container resolution
自定义参数解析器($arg
)
callArgs
方法还接受一个可选的 $arg
可调用,在回退到容器解析或自定义值之前解析参数。如果 $arg
可调用返回 null
,则解析回退到容器或自定义值。
$customResolver = function (\ReflectionParameter $parameter) { if ($parameter->getType()?->getName() === Foo::class) { return new Foo2(); } return null; }; $args = $container->callArgs(function(Foo $foo) { return $foo->method(); }, [], $customResolver); var_dump($args[0] instanceof Foo2); // true
在这种情况下,自定义解析器具有优先权。如果解析器未处理参数(返回 null
),则使用容器或提供的自定义值作为回退。
要点
$arg
解析器具有最高优先级。- 接下来检查自定义值。
- 如果没有找到匹配项,容器将自动解析依赖项。
扩展
您可以通过扩展 Container 类来添加自定义功能或特定行为以适应您的应用程序。
use Xtompie\Container\Container; class CustomContainer extends Container { // Custom methods or properties }
这允许您根据项目特定需求定制容器,在依赖项解析过程中添加自定义逻辑或行为。