Laravel学习笔记之Container源码解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:


说明:本文主要学习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测试下绑定

在聊解析过程前,先测试下\Illuminate\Container\Container中绑定的源码,这里测试下bind(),singleton(),instance()三个绑定方式:


 
 
  1. <?php 
  2.  
  3. namespace MyRightCapital\Container\Tests; 
  4.  
  5. use MyRightCapital\Container\Container; 
  6.  
  7. class ContainerBindTest extends \PHPUnit_Framework_TestCase 
  8.     /** 
  9.      * @var Container $container 
  10.      */ 
  11.     protected $container; 
  12.  
  13.     public function setUp() 
  14.     { 
  15.         $this->container = new Container(); 
  16.     } 
  17.  
  18.     public function testBindClosure() 
  19.     { 
  20.         // Arrange 
  21.         $expected = 'Laravel is a PHP Framework.'
  22.         $this->container->bind('PHP'function () use ($expected) { 
  23.             return $expected; 
  24.         }); 
  25.  
  26.         // Actual 
  27.         $actual = $this->container->make('PHP'); 
  28.  
  29.         // Assert 
  30.         $this->assertEquals($expected, $actual); 
  31.     } 
  32.  
  33.     public function testBindInterfaceToImplement() 
  34.     { 
  35.         // Arrange 
  36.         $this->container->bind(IContainerStub::class, ContainerImplementationStub::class); 
  37.  
  38.         // Actual 
  39.         $actual = $this->container->make(IContainerStub::class); 
  40.  
  41.         // Assert 
  42.         $this->assertInstanceOf(IContainerStub::class, $actual); 
  43.     } 
  44.  
  45.     public function testBindDependencyResolution() 
  46.     { 
  47.         // Arrange 
  48.         $this->container->bind(IContainerStub::class, ContainerImplementationStub::class); 
  49.  
  50.         // Actual 
  51.         $actual = $this->container->make(ContainerNestedDependentStub::class); 
  52.  
  53.         // Assert 
  54.         $this->assertInstanceOf(ContainerDependentStub::class, $actual->containerDependentStub); 
  55.         $this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub); 
  56.     } 
  57.  
  58.     public function testSingleton() 
  59.     { 
  60.         // Arrange 
  61.         $this->container->singleton(ContainerConcreteStub::class); 
  62.         $expected = $this->container->make(ContainerConcreteStub::class); 
  63.  
  64.         // Actual 
  65.         $actual = $this->container->make(ContainerConcreteStub::class); 
  66.  
  67.         // Assert 
  68.         $this->assertSame($expected, $actual); 
  69.     } 
  70.  
  71.     public function testInstanceExistingObject() 
  72.     { 
  73.         // Arrange 
  74.         $expected = new ContainerImplementationStub(); 
  75.         $this->container->instance(IContainerStub::class, $expected); 
  76.  
  77.         // Actual 
  78.         $actual = $this->container->make(IContainerStub::class); 
  79.  
  80.         // Assert 
  81.         $this->assertSame($expected, $actual); 
  82.     } 
  83.  
  84. class ContainerConcreteStub 
  85.  
  86.  
  87. interface IContainerStub 
  88.  
  89.  
  90. class ContainerImplementationStub implements IContainerStub 
  91.  
  92.  
  93. class ContainerDependentStub 
  94.     /** 
  95.      * @var \MyRightCapital\Container\Tests\IContainerStub 
  96.      */ 
  97.     public $containerStub; 
  98.  
  99.     public function __construct(IContainerStub $containerStub) 
  100.     { 
  101.         $this->containerStub = $containerStub; 
  102.     } 
  103.  
  104. class ContainerNestedDependentStub 
  105.     /** 
  106.      * @var \MyRightCapital\Container\Tests\ContainerDependentStub 
  107.      */ 
  108.     public $containerDependentStub; 
  109.  
  110.     public function __construct(ContainerDependentStub $containerDependentStub) 
  111.     { 
  112.         $this->containerDependentStub = $containerDependentStub; 
  113.     } 

这里测试了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()源码:


 
 
  1. /** 
  2.      * Resolve the given type from the container. 
  3.      * 
  4.      * @param  string  $abstract 
  5.      * @param  array   $parameters 
  6.      * @return mixed 
  7.      */ 
  8.     public function make($abstract, array $parameters = []) 
  9.     { 
  10.         $abstract = $this->getAlias($this->normalize($abstract)); 
  11.  
  12.         // 如果是instance()绑定的方式,就直接解析返回绑定的service 
  13.         if (isset($this->instances[$abstract])) { 
  14.             return $this->instances[$abstract]; 
  15.         } 
  16.  
  17.         // 获取$abstract对应绑定的$concrete 
  18.         $concrete = $this->getConcrete($abstract); 
  19.  
  20.         if ($this->isBuildable($concrete, $abstract)) { 
  21.             $object = $this->build($concrete, $parameters); 
  22.         } else { 
  23.             $object = $this->make($concrete, $parameters); 
  24.         } 
  25.  
  26.         foreach ($this->getExtenders($abstract) as $extender) { 
  27.             $object = $extender($object, $this); 
  28.         } 
  29.  
  30.         if ($this->isShared($abstract)) { 
  31.             $this->instances[$abstract] = $object; 
  32.         } 
  33.  
  34.         $this->fireResolvingCallbacks($abstract, $object); 
  35.  
  36.         $this->resolved[$abstract] = true
  37.  
  38.         return $object; 
  39.     } 
  40.      
  41.     protected function getConcrete($abstract) 
  42.     { 
  43.         if (! is_null($concrete = $this->getContextualConcrete($abstract))) { 
  44.             return $concrete; 
  45.         } 
  46.  
  47.         // 如果是$this->container->singleton(ContainerConcreteStub::class);这种方式绑定,即$concrete = null 
  48.         // 则 $abstract = $concrete,可看以上PHPUnit的testSingleton()这个test 
  49.         // 这种方式称为'自动补全'绑定 
  50.         if (! isset($this->bindings[$abstract])) { 
  51.             return $abstract; 
  52.         } 
  53.  
  54.         return $this->bindings[$abstract]['concrete']; 
  55.     } 
  56.      
  57.     protected function isBuildable($concrete, $abstract) 
  58.     { 
  59.         return $concrete === $abstract || $concrete instanceof Closure; 
  60.     }  

从以上源码可知道如果绑定的是闭包或者'自动补全'绑定($concrete = null),则需要build()这个闭包或类名,转换成对应的实例。如果是'接口实现'这种方式绑定,则需要再一次调用make()并经过getConcrete后$abstract = $concrete,然后符合isBuildable()的条件,进入build()函数内。所以以上的PHPUnit的测试用例中不管什么方式的绑定,都要进入build()函数内编译出相应对象实例。当编译出对象后,检查是否是共享的,以及是否要触发回调,以及标记该对象已经被解析。OK,看下build()的源码:


 
 
  1. /** 
  2.      * Instantiate a concrete instance of the given type. 
  3.      * 
  4.      * @param  string  $concrete 
  5.      * @param  array   $parameters 
  6.      * @return mixed 
  7.      * 
  8.      * @throws \Illuminate\Contracts\Container\BindingResolutionException 
  9.      */ 
  10.     public function build($concrete, array $parameters = []) 
  11.     { 
  12.         // 如果是闭包直接执行闭包并返回,e.g. PHPUnit的这个test:testBindClosure() 
  13.         if ($concrete instanceof Closure) { 
  14.             return $concrete($this, $parameters); 
  15.         } 
  16.          
  17.         // 如这个test:testBindInterfaceToImplement(),这里的$concrete = ContainerImplementationStub::class类名称, 
  18.         // 则使用反射ReflectionClass来探测ContainerImplementationStub这个类的构造函数和构造函数的依赖 
  19.         $reflector = new ReflectionClass($concrete); 
  20.  
  21.         // 如果ContainerImplementationStub不能实例化,这应该是接口或抽象类,再或者就是ContainerImplementationStub的构造函数是private的 
  22.         if (! $reflector->isInstantiable()) { 
  23.             if (! empty($this->buildStack)) { 
  24.                 $previous = implode(', ', $this->buildStack); 
  25.  
  26.                 $message = "Target [$concrete] is not instantiable while building [$previous]."
  27.             } else { 
  28.                 $message = "Target [$concrete] is not instantiable."
  29.             } 
  30.  
  31.             throw new BindingResolutionException($message); 
  32.         } 
  33.  
  34.         $this->buildStack[] = $concrete; 
  35.  
  36.         // 获取构造函数的反射 
  37.         $constructor = $reflector->getConstructor(); 
  38.  
  39.         // 如果构造函数是空,说明没有任何依赖,直接new返回 
  40.         if (is_null($constructor)) { 
  41.             array_pop($this->buildStack); 
  42.  
  43.             return new $concrete; 
  44.         } 
  45.          
  46.         // 获取构造函数的依赖,返回ReflectionParameter[] 
  47.         $dependencies = $constructor->getParameters(); 
  48.  
  49.         $parameters = $this->keyParametersByArgument( 
  50.             $dependencies, $parameters 
  51.         ); 
  52.  
  53.         // 然后就是获取相关依赖,如testBindDependencyResolution()这个test中, 
  54.         // ContainerNestedDependentStub::class是依赖于ContainerDependentStub::class的 
  55.         $instances = $this->getDependencies( 
  56.             $dependencies, $parameters 
  57.         ); 
  58.  
  59.         array_pop($this->buildStack); 
  60.  
  61.         return $reflector->newInstanceArgs($instances); 
  62.     }  

从源码可知道,比较麻烦的是当ContainerNestedDependentStub::class的构造函数有依赖ContainerDependentStub::class时,通过getDependencies()来解决的,看下getDependencies()的源码:


 
 
  1. // 这里$parameters = ReflectionParameter[] 
  2.     protected function getDependencies(array $parameters, array $primitives = []) 
  3.     { 
  4.         $dependencies = []; 
  5.  
  6.         foreach ($parameters as $parameter) { 
  7.             $dependency = $parameter->getClass(); 
  8.  
  9.             // 如果某一依赖值已给,就赋值 
  10.             if (array_key_exists($parameter->name, $primitives)) { 
  11.                 $dependencies[] = $primitives[$parameter->name]; 
  12.             }  
  13.             // 如果类名为null,说明是基本类型,如'int','string' and so on
  14.             elseif (is_null($dependency)) { 
  15.                 $dependencies[] = $this->resolveNonClass($parameter); 
  16.             }  
  17.             // 如果是类名,如ContainerDependentStub::class,则resolveClass去解析成对象 
  18.             else { 
  19.                 $dependencies[] = $this->resolveClass($parameter); 
  20.             } 
  21.         } 
  22.  
  23.         return $dependencies; 
  24.     }  

通过上面注释,看下resolveClass()的源码:


 
 
  1. protected function resolveClass(ReflectionParameter $parameter) 
  2.    { 
  3.        try { 
  4.            // $parameter->getClass()->name返回的是类名,如ContainerNestedDependentStub依赖于$containerDependentStub 
  5.            // $containerDependentStub的typehint是ContainerDependentStub,所以类名是'ContainerDependentStub' 
  6.            // 然后递归继续make(ContainerDependentStub::class) 
  7.            // 又和PHPUnit中这个测试$this->container->make(ContainerNestedDependentStub::class)相类似了 
  8.            // ContainerNestedDependentStub又依赖于IContainerStub::class, 
  9.            // IContainerStub::class是绑定于ContainerImplementationStub::class 
  10.            // 直到ContainerImplementationStub没有依赖或者是构造函数是基本属性, 
  11.            // 最后build()结束 
  12.            return $this->make($parameter->getClass()->name); 
  13.        } catch (BindingResolutionException $e) { 
  14.            if ($parameter->isOptional()) { 
  15.                return $parameter->getDefaultValue(); 
  16.            } 
  17.  
  18.            throw $e; 
  19.        } 
  20.    }  

从以上代码注释直到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能干些啥:


 
 
  1. <?php 
  2.  
  3. class ConstructorParameter 
  4.  
  5.  
  6. class ReflectorTest 
  7.     private $refletorProperty1; 
  8.  
  9.     protected $refletorProperty2; 
  10.  
  11.     public $refletorProperty3; 
  12.  
  13.     /** 
  14.      * @var int 
  15.      */ 
  16.     private $request; 
  17.  
  18.     public function __construct(int $request = 10, string $response, ConstructorParameter $constructorParameter, Closure $closure) 
  19.     { 
  20.  
  21.         $this->request = $request; 
  22.     } 
  23.  
  24.     private function reflectorMethod1() 
  25.     { 
  26.     } 
  27.  
  28.     protected function reflectorMethod2() 
  29.     { 
  30.     } 
  31.  
  32.     public function reflectorMethod3() 
  33.     { 
  34.     } 
  35.  
  36. $reflector_class        = new ReflectionClass(ReflectorTest::class); 
  37. $methods                = $reflector_class->getMethods(); 
  38. $properties             = $reflector_class->getProperties(); 
  39. $constructor            = $reflector_class->getConstructor(); 
  40. $constructor_parameters = $constructor->getParameters(); 
  41.  
  42. foreach ($constructor_parameters as $constructor_parameter) { 
  43.     $dependency = $constructor_parameter->getClass(); 
  44.     var_dump($dependency); 
  45.  
  46.     if ($constructor_parameter->isDefaultValueAvailable()) { 
  47.         var_dump($constructor_parameter->getDefaultValue()); 
  48.     } 
  49.  
  50. var_dump($methods); 
  51. var_dump($properties); 
  52. var_dump($constructor); 
  53. var_dump($constructor_parameters);  

打印结果太长了,就不粘贴了。可以看下PHP官方文档:Reflector

总结:本文学习了下Container的核心功能:service resolve的过程,并学习了service的依赖是如何被自动解析的。遇到好的心得再分享,到时见。


作者:lx1036

来源:51CTO

相关文章
|
1天前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1天前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
2天前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
41 29
|
9天前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
11天前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
17天前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
118 0
|
2月前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
75 0
|
3月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
3月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
3月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

热门文章

最新文章

推荐镜像

更多