说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑定主要有三种方式:bind(),singleton(),instance(),且singleton()只是一种'shared' = true的bind(),这些已经在Laravel学习笔记之IoC Container实例化源码解析聊过,其实现方法并不复杂。当Service通过Service Provider绑定到Container中后,当需要该Service时,是需要Container帮助自动解析make()。OK,下面聊聊自动解析过程,研究下Container是如何在自动解析Service时解决该Service的依赖问题的。
开发环境: Laravel5.3 PHP7 OS X 10.11
PHPUnit测试下绑定
在聊解析过程前,先测试下IlluminateContainerContainer中绑定的源码,这里测试下bind(),singleton(),instance()三个绑定方式:
代码语言:javascript复制<?php
namespace MyRightCapitalContainerTests;
use MyRightCapitalContainerContainer;
class ContainerBindTest extends PHPUnit_Framework_TestCase
{
/**
* @var Container $container
*/
protected $container;
public function setUp()
{
$this->container = new Container();
}
public function testBindClosure()
{
// Arrange
$expected = 'Laravel is a PHP Framework.';
$this->container->bind('PHP', function () use ($expected) {
return $expected;
});
// Actual
$actual = $this->container->make('PHP');
// Assert
$this->assertEquals($expected, $actual);
}
public function testBindInterfaceToImplement()
{
// Arrange
$this->container->bind(IContainerStub::class, ContainerImplementationStub::class);
// Actual
$actual = $this->container->make(IContainerStub::class);
// Assert
$this->assertInstanceOf(IContainerStub::class, $actual);
}
public function testBindDependencyResolution()
{
// Arrange
$this->container->bind(IContainerStub::class, ContainerImplementationStub::class);
// Actual
$actual = $this->container->make(ContainerNestedDependentStub::class);
// Assert
$this->assertInstanceOf(ContainerDependentStub::class, $actual->containerDependentStub);
$this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub);
}
public function testSingleton()
{
// Arrange
$this->container->singleton(ContainerConcreteStub::class);
$expected = $this->container->make(ContainerConcreteStub::class);
// Actual
$actual = $this->container->make(ContainerConcreteStub::class);
// Assert
$this->assertSame($expected, $actual);
}
public function testInstanceExistingObject()
{
// Arrange
$expected = new ContainerImplementationStub();
$this->container->instance(IContainerStub::class, $expected);
// Actual
$actual = $this->container->make(IContainerStub::class);
// Assert
$this->assertSame($expected, $actual);
}
}
class ContainerConcreteStub
{
}
interface IContainerStub
{
}
class ContainerImplementationStub implements IContainerStub
{
}
class ContainerDependentStub
{
/**
* @var MyRightCapitalContainerTestsIContainerStub
*/
public $containerStub;
public function __construct(IContainerStub $containerStub)
{
$this->containerStub = $containerStub;
}
}
class ContainerNestedDependentStub
{
/**
* @var MyRightCapitalContainerTestsContainerDependentStub
*/
public $containerDependentStub;
public function __construct(ContainerDependentStub $containerDependentStub)
{
$this->containerDependentStub = $containerDependentStub;
}
}
这里测试了bind()绑定闭包,绑定接口和对应实现,依赖解析这三个feature,singleton()测试了是否为单例绑定一个feature,instance()测试了已存在对象绑定这个feature,测试结果5个tests都通过:
关于在PHPStorm中配置PHPUnit可参考这篇:Laravel学习笔记之基于PHPStorm编辑器的Laravel开发
make()源码解析
从以上testcase知道,make()
是负责从Container中解析出service的,而且在testBindDependencyResolution()
这个test中,还能发现当ContainerNestedDependentStub::class
有构造依赖时,Container也会自动去解析这个依赖并注入ContainerNestedDependentStub::class
的构造函数中,这个依赖是ContainerDependentStub::class
,而这个依赖又有自己的依赖IContainerStub::class
,从断言语句$this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub);
知道,Container又自动解析了这个依赖,所有这一切都不需要我们手动去解析,全都是Container自动化解析的。
这一切Container是怎么做到的?实际上并不复杂,解决依赖只是用了PHP的Reflector反射机制来实现的。先看下make()源码:
代码语言:javascript复制 /**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($this->normalize($abstract));
// 如果是instance()绑定的方式,就直接解析返回绑定的service
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 获取$abstract对应绑定的$concrete
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete, $parameters);
} else {
$object = $this->make($concrete, $parameters);
}
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
$this->resolved[$abstract] = true;
return $object;
}
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// 如果是$this->container->singleton(ContainerConcreteStub::class);这种方式绑定,即$concrete = null
// 则 $abstract = $concrete,可看以上PHPUnit的testSingleton()这个test
// 这种方式称为'自动补全'绑定
if (! isset($this->bindings[$abstract])) {
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
从以上源码可知道如果绑定的是闭包或者'自动补全'绑定($concrete = null),则需要build()这个闭包或类名,转换成对应的实例。如果是'接口实现'这种方式绑定,则需要再一次调用make()并经过getConcrete后$abstract = $concrete,然后符合isBuildable()的条件,进入build()函数内。所以以上的PHPUnit的测试用例中不管什么方式的绑定,都要进入build()函数内编译出相应对象实例。当编译出对象后,检查是否是共享的,以及是否要触发回调,以及标记该对象已经被解析。OK,看下build()的源码:
代码语言:javascript复制 /**
* Instantiate a concrete instance of the given type.
*
* @param string $concrete
* @param array $parameters
* @return mixed
*
* @throws IlluminateContractsContainerBindingResolutionException
*/
public function build($concrete, array $parameters = [])
{
// 如果是闭包直接执行闭包并返回,e.g. PHPUnit的这个test:testBindClosure()
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
// 如这个test:testBindInterfaceToImplement(),这里的$concrete = ContainerImplementationStub::class类名称,
// 则使用反射ReflectionClass来探测ContainerImplementationStub这个类的构造函数和构造函数的依赖
$reflector = new ReflectionClass($concrete);
// 如果ContainerImplementationStub不能实例化,这应该是接口或抽象类,再或者就是ContainerImplementationStub的构造函数是private的
if (! $reflector->isInstantiable()) {
if (! empty($this->buildStack)) {
$previous = implode(', ', $this->buildStack);
$message = "Target [$concrete] is not instantiable while building [$previous].";
} else {
$message = "Target [$concrete] is not instantiable.";
}
throw new BindingResolutionException($message);
}
$this->buildStack[] = $concrete;
// 获取构造函数的反射
$constructor = $reflector->getConstructor();
// 如果构造函数是空,说明没有任何依赖,直接new返回
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取构造函数的依赖,返回ReflectionParameter[]
$dependencies = $constructor->getParameters();
$parameters = $this->keyParametersByArgument(
$dependencies, $parameters
);
// 然后就是获取相关依赖,如testBindDependencyResolution()这个test中,
// ContainerNestedDependentStub::class是依赖于ContainerDependentStub::class的
$instances = $this->getDependencies(
$dependencies, $parameters
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
从源码可知道,比较麻烦的是当ContainerNestedDependentStub::class的构造函数有依赖ContainerDependentStub::class时,通过getDependencies()来解决的,看下getDependencies()的源码:
代码语言:javascript复制 // 这里$parameters = ReflectionParameter[]
protected function getDependencies(array $parameters, array $primitives = [])
{
$dependencies = [];
foreach ($parameters as $parameter) {
$dependency = $parameter->getClass();
// 如果某一依赖值已给,就赋值
if (array_key_exists($parameter->name, $primitives)) {
$dependencies[] = $primitives[$parameter->name];
}
// 如果类名为null,说明是基本类型,如'int','string' and so on.
elseif (is_null($dependency)) {
$dependencies[] = $this->resolveNonClass($parameter);
}
// 如果是类名,如ContainerDependentStub::class,则resolveClass去解析成对象
else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return $dependencies;
}
通过上面注释,看下resolveClass()的源码:
代码语言:javascript复制 protected function resolveClass(ReflectionParameter $parameter)
{
try {
// $parameter->getClass()->name返回的是类名,如ContainerNestedDependentStub依赖于$containerDependentStub
// $containerDependentStub的typehint是ContainerDependentStub,所以类名是'ContainerDependentStub'
// 然后递归继续make(ContainerDependentStub::class)
// 又和PHPUnit中这个测试$this->container->make(ContainerNestedDependentStub::class)相类似了
// ContainerNestedDependentStub又依赖于IContainerStub::class,
// IContainerStub::class是绑定于ContainerImplementationStub::class
// 直到ContainerImplementationStub没有依赖或者是构造函数是基本属性,
// 最后build()结束
return $this->make($parameter->getClass()->name);
} catch (BindingResolutionException $e) {
if ($parameter->isOptional()) {
return $parameter->getDefaultValue();
}
throw $e;
}
}
从以上代码注释直到build()是个递归过程,A类依赖于B类,B类依赖于C类和D类,那就从A类开始build,发现依赖于B类,再从Container中解析make()即再build()出B类,发现依赖于C类,再make() and build(),发现B类又同时依赖于D类,再make() and build(),以此类推直到没有依赖或依赖基本属性,解析结束。这样一步步解析完后,发现Container的解析make()并不是很神秘很复杂中的过程。
从以上源码发现PHP的反射Reflector是个很好用的技术,这里给出个test,看下Reflector能干些啥:
代码语言:javascript复制<?php
class ConstructorParameter
{
}
class ReflectorTest
{
private $refletorProperty1;
protected $refletorProperty2;
public $refletorProperty3;
/**
* @var int
*/
private $request;
public function __construct(int $request = 10, string $response, ConstructorParameter $constructorParameter, Closure $closure)
{
$this->request = $request;
}
private function reflectorMethod1()
{
}
protected function reflectorMethod2()
{
}
public function reflectorMethod3()
{
}
}
$reflector_class = new ReflectionClass(ReflectorTest::class);
$methods = $reflector_class->getMethods();
$properties = $reflector_class->getProperties();
$constructor = $reflector_class->getConstructor();
$constructor_parameters = $constructor->getParameters();
foreach ($constructor_parameters as $constructor_parameter) {
$dependency = $constructor_parameter->getClass();
var_dump($dependency);
if ($constructor_parameter->isDefaultValueAvailable()) {
var_dump($constructor_parameter->getDefaultValue());
}
}
var_dump($methods);
var_dump($properties);
var_dump($constructor);
var_dump($constructor_parameters);
打印结果太长了,就不粘贴了。可以看下PHP官方文档:Reflector
总结:本文学习了下Container的核心功能:service resolve的过程,并学习了service的依赖是如何被自动解析的。遇到好的心得再分享,到时见。