Laravel中Container如何使用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。PHPUnit测试下绑定在聊解析过程前
Laravel中Container如何使用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
PHPUnit测试下绑定
在聊解析过程前,先测试下\Illuminate\Container\Container中绑定的源码,这里测试下bind(),singleton(),instance()三个绑定方式:
<?PHP namespace MyRightCapital\Container\Tests; use MyRightCapital\Container\Container; class ContainerBindTest extends \phpUnit_Framework_TestCase { 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 { public $containerStub; public function __construct(IContainerStub $containerStub) { $this->containerStub = $containerStub; } } class ContainerNestedDependentStub { 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()源码:
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()的源码:
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()的源码:
// 这里$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()的源码:
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能干些啥:
<?php class ConstructorParameter { } class ReflectorTest { private $refletorProperty1; protected $refletorProperty2; public $refletorProperty3; 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);
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注编程网JavaScript频道,感谢您对编程网的支持。
--结束END--
本文标题: Laravel中Container如何使用
本文链接: https://lsjlt.com/news/73697.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-12
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0