IOC 容器
Testing Is Documentation
IOC 容器是整个框架最核心的部分,负责服务的管理和解耦。
目前系统所有的关键服务都接入了 IOC 容器,包括控制器、Console 命令行。
Uses
<?php
use Leevel\Di\Container;
use Leevel\Di\ContainerInvalidArgumentException;
use Leevel\Di\ServiceNotFoundException;
use Leevel\Kernel\Utils\Api;
闭包绑定
闭包属于惰性,真正使用的时候才会执行。
我们可以通过 bind
来绑定一个闭包,通过 make
来运行服务,第二次运行如果是单例则直接使用生成后的结果,否则会每次执行闭包的代码。
通常来说,系统大部分服务都是单例来提升性能和共享。
public function testBindClosure(): void
{
$container = new Container();
$container->bind('foo', static function () {
return 'bar';
});
self::assertSame('bar', $container->make('foo'));
}
闭包绑定单例
public function testSingletonClosure(): void
{
$container = new Container();
$singleton = new \stdClass();
$container->singleton('singleton', static function () use ($singleton) {
return $singleton;
});
self::assertSame($singleton, $container->make('singleton'));
self::assertSame($singleton, $container->make('singleton'));
}
类直接生成本身
一个独立的类可以直接生成,而不需要提前注册到容器中。
fixture 定义
Tests\Di\Fixtures\Test1
namespace Tests\Di\Fixtures;
class Test1 {}
public function testClass(): void
{
$container = new Container();
self::assertInstanceOf(Test1::class, $container->make(Test1::class));
}
类单例
类也可以注册为单例。
public function testSingletonClass(): void
{
$container = new Container();
$container->singleton(Test1::class);
self::assertSame($container->make(Test1::class), $container->make(Test1::class));
}
接口绑定
可以为接口绑定实现。
fixture 定义
Tests\Di\Fixtures\ITest2
namespace Tests\Di\Fixtures;
interface ITest2 {}
Tests\Di\Fixtures\Test2
namespace Tests\Di\Fixtures;
class Test2 implements ITest2 {}
public function testInterface(): void
{
$container = new Container();
$container->bind(ITest2::class, Test2::class);
self::assertInstanceOf(ITest2::class, $container->make(ITest2::class));
self::assertInstanceOf(ITest2::class, $container->make(Test2::class));
}
接口绑定接口作为构造器参数
接口可以作为控制器参数来做依赖注入。
fixture 定义
Tests\Di\Fixtures\Test3
namespace Tests\Di\Fixtures;
class Test3 implements ITest3
{
public $arg1;
public function __construct(ITest2 $arg1)
{
$this->arg1 = $arg1;
}
}
通过 Test3
的构造函数注入 ITest2
的实现 Test2
,通过 IOC 容器可以实现代码解耦。
public function testInterface2(): void
{
$container = new Container();
$container->bind(ITest2::class, Test2::class);
self::assertInstanceOf(ITest2::class, $test2 = $container->make(Test3::class)->arg1);
self::assertInstanceOf(Test2::class, $test2);
}
绑定闭包第一个参数为 IOC 容器本身
public function testContainerAsFirstArgs(): void
{
$container = new Container();
$container->bind('test', static function ($container) {
return $container;
});
self::assertSame($container, $container->make('test'));
}
数组访问 ArrayAccess 支持
public function testArrayAccess(): void
{
$container = new Container();
$container['foo'] = static function () {
return 'bar';
};
self::assertTrue(isset($container['foo']));
self::assertSame('bar', $container['foo']);
unset($container['foo']);
self::assertFalse(isset($container['foo']));
}
alias 设置别名
public function testAliases(): void
{
$container = new Container();
$container['foo'] = 'bar';
$container->alias('foo', 'foo2');
$container->alias('foo', ['foo3', 'foo4']);
$container->alias(['foo' => ['foo5', 'foo6']]);
$container->alias(['foo' => 'foo7']);
self::assertNull($container->make('foo', throw: false));
self::assertNull($container->make('foo2', throw: false));
self::assertNull($container->make('foo3', throw: false));
self::assertNull($container->make('foo4', throw: false));
self::assertNull($container->make('foo5', throw: false));
self::assertNull($container->make('foo6', throw: false));
self::assertNull($container->make('foo7', throw: false));
}
make 创建容器服务并返回支持参数
public function testMakeWithArgs(): void
{
$container = new Container();
$container['foo'] = static function ($container, $arg1, $arg2) {
return [
$arg1,
$arg2,
];
};
self::assertSame([1, 2], $container->make('foo', [1, 2, 3]));
}
get 创建容器服务并返回
public function testGet(): void
{
$container = new Container();
$container['foo'] = static function ($container) {
return [1, 2];
};
self::assertSame([1, 2], $container->make('foo'));
}
bind 注册到容器支持覆盖
public function testOverridden(): void
{
$container = new Container();
$container['foo'] = static fn () => 'bar';
self::assertSame('bar', $container['foo']);
$container['foo'] = static fn () => 'bar2';
self::assertSame('bar2', $container['foo']);
}
instance 注册为实例
public function testInstance(): void
{
$container = new Container();
$instance = new \stdClass();
$container->instance('foo', $instance);
self::assertSame($instance, $container->make('foo'));
}
默认参数支持
fixture 定义
Tests\Di\Fixtures\Test5
namespace Tests\Di\Fixtures;
class Test5 implements ITest3
{
public $arg1;
public $arg2;
public function __construct(Test3 $arg1, $arg2 = 'hello default')
{
$this->arg1 = $arg1;
$this->arg2 = $arg2;
}
}
Tests\Di\Fixtures\ITest3
namespace Tests\Di\Fixtures;
interface ITest3 {}
public function testDefaultArgs(): void
{
$container = new Container();
$container->bind(ITest2::class, Test2::class);
$container->bind('foo', Test5::class);
$test5 = $container->make('foo');
self::assertInstanceOf(ITest3::class, $test5);
self::assertSame('hello default', $test5->arg2);
}
必填参数校验
fixture 定义
Tests\Di\Fixtures\Test6
namespace Tests\Di\Fixtures;
class Test6
{
public $arg1;
public $arg2;
public $arg3;
public function __construct($arg1, $arg2, $arg3)
{
$this->arg1 = $arg1;
$this->arg2 = $arg2;
$this->arg3 = $arg3;
}
}
public function testArgsRequiredContainerInvalidArgumentException(): void
{
$this->expectException(ContainerInvalidArgumentException::class);
$this->expectExceptionMessage(
'There are 3 required args,but 0 given.'
);
$container = new Container();
$container->make(Test6::class, []);
}
接口必须绑定服务
public function testInterfaceContainerInvalidArgumentException(): void
{
$this->expectException(ContainerInvalidArgumentException::class);
$this->expectExceptionMessage(
'Interface Tests\\Di\\Fixtures\\ITest2 cannot be normalize because not bound.'
);
$container = new Container();
$container->make(ITest2::class, []);
}
call 回调自动依赖注入
fixture 定义
Tests\Di\Fixtures\Test7
namespace Tests\Di\Fixtures;
class Test7 {}
Tests\Di\Fixtures\Test8
namespace Tests\Di\Fixtures;
class Test8 implements ITest8
{
public function func1()
{
return \func_get_args();
}
public function func2($arg1 = 'hello')
{
return \func_get_args();
}
public static function staticFunc3()
{
return \func_get_args();
}
public function handle()
{
return ['call handle'];
}
}
public function testCall(): void
{
$container = new Container();
$result = $container->call(static function (Test7 $arg1, array $arg2 = []) {
return \func_get_args();
});
self::assertInstanceOf(Test7::class, $result[0]);
self::assertSame([], $result[1]);
$result = $container->call(static function (Test7 $arg1, array $arg2 = [], $arg3 = null) {
return \func_get_args();
}, ['arg3' => 'hello']);
self::assertInstanceOf(Test7::class, $result[0]);
self::assertSame([], $result[1]);
self::assertSame('hello', $result[2]);
$test7 = new Test7();
$result = $container->call(static function (Test7 $arg1, $arg2 = 'hello') {
return \func_get_args();
}, [Test7::class => $test7, 'arg2' => 'hello world']);
self::assertSame($test7, $result[0]);
self::assertSame('hello world', $result[1]);
$test8 = new Test8();
$result = $container->call(static function ($arg1, $arg2, $arg3, ?ITest8 $arg4 = null, ?Test8 $arg5 = null) {
return \func_get_args();
}, ['arg1' => 'foo', 'arg3' => 'world2', Test8::class => $test8]);
self::assertSame('foo', $result[0]);
self::assertNull($result[1]);
self::assertSame('world2', $result[2]);
self::assertNull($result[3]);
self::assertSame($result[4], $test8);
}
call 回调自动依赖注入支持字符串或者数组类回调
public function testCallWithArrayOrString(): void
{
$container = new Container();
$result = $container->call([Test8::class, 'func1'], ['foo', 'bar']);
self::assertSame(['foo', 'bar'], $result);
$result = $container->call(Test8::class.'@func1', ['foo', 'bar']);
self::assertSame(['foo', 'bar'], $result);
$result = $container->call([Test8::class], ['foo', 'bar']);
self::assertSame(['call handle'], $result);
$result = $container->call(Test8::class.'@', ['foo', 'bar']);
self::assertSame(['call handle'], $result);
$result = $container->call(Test8::class.'@func2');
self::assertSame('hello', $result[0]);
$result = $container->call(Test8::class.'@func2', ['world', 'foo', 'bar']);
self::assertSame('world', $result[0]);
self::assertSame('foo', $result[1]);
self::assertSame('bar', $result[2]);
$result = $container->call(Test8::class.'@func2', ['world', 'arg1' => 'foo', 'bar']);
self::assertSame('foo', $result[0]);
self::assertSame('world', $result[1]);
self::assertSame('bar', $result[2]);
}
call 回调自动依赖注入支持实例和方法数组回调
public function testCallWithCallableArray(): void
{
$container = new Container();
$test8 = new Test8();
$result = $container->call([$test8, 'func1'], ['foo', 'bar']);
self::assertSame(['foo', 'bar'], $result);
}
call 回调自动依赖注入支持静态回调
public function testCallStatic(): void
{
$container = new Container();
$result = $container->call(Test8::class.'::staticFunc3', ['hello', 'world']);
self::assertSame(['hello', 'world'], $result);
}
remove 删除服务和实例
public function testRemove(): void
{
$container = new Container();
$test8 = new Test8();
$container->instance(Test8::class, $test8);
self::assertTrue($container->exists(Test8::class));
$container->remove(Test8::class);
self::assertFalse($container->exists(Test8::class));
}
exists 或者 has 服务或者实例是否存在
public function testExistsOrHas(): void
{
$container = new Container();
$test8 = new Test8();
$container->instance(Test8::class, $test8);
self::assertTrue($container->exists(Test8::class));
self::assertTrue($container->has(Test8::class));
$container->remove(Test8::class);
self::assertFalse($container->exists(Test8::class));
self::assertFalse($container->has(Test8::class));
}
实例数组访问 ArrayAccess.offsetUnset 支持
public function testUnsetInstances(): void
{
$container = new Container();
$container->instance('foo', 'bar');
$container->alias('foo', 'foo2');
$container->alias('foo', 'foo3');
self::assertTrue(isset($container['foo']));
self::assertTrue(isset($container['foo2']));
self::assertTrue(isset($container['foo3']));
unset($container['foo']);
self::assertFalse(isset($container['foo']));
self::assertFalse(isset($container['foo2']));
self::assertFalse(isset($container['foo3']));
}
类依赖注入构造器必须为 public
fixture 定义
Tests\Di\Fixtures\Test9
namespace Tests\Di\Fixtures;
class Test9
{
protected function __construct() {}
public function hello()
{
return 'world9';
}
}
public function testNotInstantiable(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage(
'Class Tests\\Di\\Fixtures\\Test9 is not instantiable.'
);
$container = new Container();
self::assertSame('world9', $container->make(Test9::class)->hello());
}
bind 注册到容器可以支持各种数据
public function testMakeServiceBool(): void
{
$container = new Container();
$container->bind('foo', false);
self::assertFalse($container->make('foo'));
}
bind 注册到容器支持传递数组来设置别名
public function testBindArrayAsAlias(): void
{
$container = new Container();
$container->bind(['foo' => 'bar'], false);
self::assertFalse($container->make('foo'));
self::assertFalse($container->make('bar'));
}
依赖注入的方法中类参数不存在例子
fixture 定义
Tests\Di\Fixtures\Test10
namespace Tests\Di\Fixtures;
class Test10
{
public function hello(TestNotFound $test)
{
return 'test10';
}
}
public function testParseReflectionException(): void
{
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage(
'Service Tests\\Di\\Fixtures\\TestNotFound was not found.'
);
$container = new Container();
$container->call([new Test10(), 'hello']);
}
instance 注册为实例支持传递数组来设置别名
public function testInstanceWithArray(): void
{
$container = new Container();
$instance = new \stdClass();
$container->instance(['foo' => 'bar'], $instance);
self::assertSame($instance, $container->make('foo'));
self::assertSame($instance, $container->make('bar'));
}
instance 注册为实例未传递第二个参数会注册自身
比如说系统中中间件注册。
# Leevel\Session\Provider\Register::middleware
/**
* 注册 middleware 服务.
*/
protected function middleware(): void;
public function testInstanceItSelf(): void
{
$container = new Container();
$container->instance('foo');
self::assertSame('foo', $container->make('foo'));
$container->instance('Leevel\\Foo\\Middleware\\Bar');
self::assertSame('Leevel\\Foo\\Middleware\\Bar', $container->make('Leevel\\Foo\\Middleware\\Bar'));
}
参数为类实例例子
fixture 定义
Tests\Di\Fixtures\Test20
namespace Tests\Di\Fixtures;
class Test20
{
public function handle(Test21 $arg1, Test22 $arg2)
{
return ['test21' => $arg1->prop, 'test22' => $arg2->prop];
}
}
Tests\Di\Fixtures\Test21
namespace Tests\Di\Fixtures;
class Test21
{
public $prop;
public function __construct(?string $prop = null)
{
$this->prop = $prop;
}
}
Tests\Di\Fixtures\Test22
namespace Tests\Di\Fixtures;
class Test22
{
public $prop;
public function __construct(?string $prop = null)
{
$this->prop = $prop;
}
}
public function testCallWithClassArgsAndItInstance(): void
{
$container = new Container();
$obj = new Test20();
$args = [new Test21('hello'), new Test22('world')];
$result = $container->call([$obj, 'handle'], $args);
self::assertSame(['test21' => 'hello', 'test22' => 'world'], $result);
}
参数为类实例例子和其它参数混合
fixture 定义
Tests\Di\Fixtures\Test23
namespace Tests\Di\Fixtures;
class Test23
{
public function handle(Test24 $arg1, Test25 $arg2, string $arg3)
{
return ['test24' => $arg1->prop, 'test25' => $arg2->prop, 'three' => $arg3];
}
}
Tests\Di\Fixtures\Test24
namespace Tests\Di\Fixtures;
class Test24
{
public $prop;
public function __construct(?string $prop = null)
{
$this->prop = $prop;
}
}
Tests\Di\Fixtures\Test25
namespace Tests\Di\Fixtures;
class Test25
{
public $prop;
public function __construct(?string $prop = null)
{
$this->prop = $prop;
}
}
public function testCallWithClassArgsAndItInstanceAndMore(): void
{
$container = new Container();
$obj = new Test23();
$args = [new Test24('hello'), new Test25('world'), 'more'];
$result = $container->call([$obj, 'handle'], $args);
self::assertSame(['test24' => 'hello', 'test25' => 'world', 'three' => 'more'], $result);
}
make 创建容器服务并返回支持类名生成服务
fixture 定义
Tests\Di\Fixtures\Test28
namespace Tests\Di\Fixtures;
class Test28
{
private $test;
public function __construct(Test27 $test)
{
$this->test = $test;
}
public function hello()
{
return $this->test->hello();
}
}
public function testClassArgsASingleClass(): void
{
$container = new Container();
$test = $container->make(Test28::class);
self::assertSame('world', $test->hello());
}
魔术方法 __get 支持
public function testMagicGet(): void
{
$container = new Container();
$container->instance('foo', 'bar');
self::assertSame('bar', $container->foo);
}
魔术方法 __set 支持
public function testMagicSet(): void
{
$container = new Container();
$container->foo = static function (): string {
return 'bar';
};
self::assertSame('bar', $container->foo);
}
clear 清理容器
public function testClear(): void
{
$container = new Container();
$container->instance('foo', 'bar');
self::assertSame('bar', $container->make('foo'));
self::assertNull($container->make('notfound', throw: false));
$container->clear();
self::assertNull($container->make('foo', throw: false));
}
IOC 容器禁止克隆
public function testClone(): void
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage(
'IOC container disallowed clone.'
);
$container = new Container();
$container2 = clone $container;
}
makeProvider 创建服务提供者
fixture 定义
Tests\Di\Fixtures\ProviderTest1
namespace Tests\Di\Fixtures;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;
class ProviderTest1 extends Provider
{
public function __construct(IContainer $container)
{
$_SERVER['testMakeProvider'] = 1;
}
public function register(): void {}
}
public function testMakeProvider(): void
{
$container = new Container();
$container->makeProvider(ProviderTest1::class);
self::assertSame(1, $_SERVER['testMakeProvider']);
unset($_SERVER['testMakeProvider']);
}
callProviderBootstrap 执行服务提供者 bootstrap
fixture 定义
Tests\Di\Fixtures\ProviderTest2
namespace Tests\Di\Fixtures;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;
class ProviderTest2 extends Provider
{
public function __construct(IContainer $container) {}
public function bootstrap(): void
{
$_SERVER['testCallProviderBootstrap'] = 1;
}
public function register(): void {}
}
public function testCallProviderBootstrap(): void
{
$container = new Container();
$container->callProviderBootstrap(new ProviderTest1($container));
self::assertSame(1, $_SERVER['testMakeProvider']);
unset($_SERVER['testMakeProvider']);
$container->callProviderBootstrap(new ProviderTest2($container));
self::assertSame(1, $_SERVER['testCallProviderBootstrap']);
unset($_SERVER['testCallProviderBootstrap']);
}
registerProviders 注册服务提供者
public function testRegisterProviders(): void
{
$container = new Container();
self::assertFalse($container->isBootstrap());
$container->registerProviders([], [], []);
self::assertTrue($container->isBootstrap());
// do nothing
$container->registerProviders([], [], []);
}
registerProviders 注册服务提供者支持延迟写入
fixture 定义
Tests\Di\Fixtures\DeferredProvider
namespace Tests\Di\Fixtures;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;
class DeferredProvider extends Provider
{
public function __construct(IContainer $container)
{
$_SERVER['testDeferredProvider'] = 1;
}
public function register(): void {}
}
public function testDeferredProvider(): void
{
$deferredProviders = [
'test_deferred' => 'Tests\\Di\\Fixtures\\DeferredProvider',
];
$deferredAlias = [
'Tests\\Di\\Fixtures\\DeferredProvider' => [
'test_deferred' => 'bar',
],
];
$container = new Container();
self::assertFalse($container->isBootstrap());
$container->registerProviders([], $deferredProviders, $deferredAlias);
self::assertTrue($container->isBootstrap());
self::assertNull($container->make('bar', throw: false));
self::assertNull($container->make('test_deferred', throw: false));
self::assertSame(1, $_SERVER['testDeferredProvider']);
unset($_SERVER['testDeferredProvider']);
}