热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Laravel核心——Ioc服务容器源码解析(服务器解析)

make解析首先欢迎关注我的博客:www.leoyang90.cn服务容器对对象的自动解析是服务容器的核心功能,make函数、build函数是实例化对象
make解析

首先欢迎关注我的博客: www.leoyang90.cn

服务容器对对象的自动解析是服务容器的核心功能,make 函数、build 函数是实例化对象重要的核心,先大致看一下代码:

public function make($abstract)
{$abstract = $this->getAlias($abstract);if (isset($this->deferredServices[$abstract])) {$this->loadDeferredProvider($abstract);}return parent::make($abstract);
}

public function make($abstract){return $this->resolve($abstract);}public function resolve($abstract, $parameters = []){$abstract = $this->getAlias($abstract);$needsContextualBuild = ! empty($parameters) || ! is_null($this->getContextualConcrete($abstract));// If an instance of the type is currently being managed as a singleton we'll// just return an existing instance instead of instantiating new instances// so the developer can keep using the same objects instance every time.if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {return $this->instances[$abstract];}$concrete = $this->getConcrete($abstract);// We're ready to instantiate an instance of the concrete type registered for// the binding. This will instantiate the types, as well as resolve any of// its "nested" dependencies recursively until all have gotten resolved.if ($this->isBuildable($concrete, $abstract)) {$object = $this->build($concrete);} else {$object = $this->make($concrete);}// If we defined any extenders for this type, we'll need to spin through them// and apply them to the object being built. This allows for the extension// of services, such as changing configuration or decorating the object.foreach ($this->getExtenders($abstract) as $extender) {$object = $extender($object, $this);}// If the requested type is registered as a singleton we'll want to cache off// the instances in "memory" so we can return it later without creating an// entirely new instance of an object on each subsequent request for it.if ($this->isShared($abstract) && ! $needsContextualBuild) {$this->instances[$abstract] = $object;}$this->fireResolvingCallbacks($abstract, $object);$this->resolved[$abstract] = true;return $object;}

在讲解解析流程之前,我们先说说使用make函数进行解析的分类:

Markdown

我们详细的讲一下上图。这里我把使用make函数进行解析的情况分为大致两种:

  • 解析对象没有绑定过任何类,例如:

$app->make('App\Http\Controllers\HomeController');

  • 解析对象绑定过实现类

对于绑定过实现类的对象可以分为两种:

  • 绑定的是类名,例如:

$app->when('App\Http\Controllers\HomeController')
->needs('App\Http\Requests\InRequest')
->give('App\Http\Requests\TestsRequest');

  • 绑定的是闭包

对于绑定的是闭包的又可以分为:

  • 用户绑定闭包,例如:

$app->singleton('auth',function($app){return new AuthManager($app)
});//对象类直接实现方法$app->singleton(EloquentFactory::class, function ($app) {return EloquentFactory::construct($app->make(FakerGenerator::class), database_path('factories')
);//对象类依赖注入
});

  • 服务容器外包一层闭包函数(make/build),例如:

$app->singleton(Illuminate\Contracts\Http\Kernel::class,App\Http\Kernel::class
);//包装make$app->singleton(App\ConSole\Kernel::class,
);//包装build

我们在这里先大致讲一下服务容器解析的流程,值得注意的是其中 build 函数有可能会递归调用 make:

Markdown

  1. 获取服务名称。

  2. 加载延迟服务。判断当前的接口是否是延迟服务提供者,若是延迟服务提供者,那么还要对服务提供者进行注册与启动操作。

  3. 解析单例。如果接口服务是已经被解析过的单例对象,而且并非上下文绑定,那么直接取出对象。

  4. 获取注册的实现。实现方式可能是上下文绑定的,也可能是 binding 数组中的闭包,也有可能就是接口本身。

  5. build 解析。首先判断是否需要递归。是,则递归 make;否,则调用 build 函数;直到调用 build 为止

  6. 执行扩展。若当前解析对象存在扩展,运行扩展函数。

  7. 创造单例对象。若 shared 为真,且不存在上下文绑定,则放入单例数组中

  8. 回调

  9. 标志解析

下面我们开始详细分解代码逻辑。由于 getAlias 函数已经在 上一篇 讲过,这里不会再说。而loadDeferredProvider 函数作用是加载延迟服务,与容器解析关系不大,我们放在以后再说。

获取注册的实现

获取解析类的真正实现,函数优先去获取上下文绑定的实现,否则获取 binding 数组中的实现,获取不到就是直接返回自己作为实现:

protected function getConcrete($abstract)
{if (! is_null($concrete = $this->getContextualConcrete($abstract))) {return $concrete;}if (isset($this->bindings[$abstract])) {return $this->bindings[$abstract]['concrete'];}return $abstract;
}

一般来说,上下文绑定的服务是通过依赖注入来实现的:

$this->app->when(PhotoController::class)->needs(Filesystem::class)->give(function () {return Storage::disk('local');});class PhotoController{protected $file;public function __construct(Filesystem $file){$this->file = $file;}
}

服务容器会在解析 PhotoController 的时候,通过放射获取参数类型 Filesystem,并且会把 Filesystem 自动解析为 Storage::disk('local')。如何实现的呢?首先,从 上一篇 文章我们知道,当进行上下文绑定的时候,实际上是维护 contextual 数组,通过上下文绑定,这个数组中存在:

contextual[PhotoController][Filesystem] = function () { return Storage::disk('local'); }

若是服务容器试图构造 PhotoController 类,那么由于其构造函数依赖于 Filesystem,所以容器必须先生成 Filesystem 类,然后再注入到 PhotoController 中。

在构造 Filesystem 之前,服务容器会先把 PhotoController 放入 buildStack 中,继而再去解析 Filesystem。

解析 Filesystem 时,运行 getContextualConcrete 函数:

protected function getContextualConcrete($abstract)
{if (! is_null($binding = $this->findInContextualBindings($abstract))) {return $binding;}if (empty($this->abstractAliases[$abstract])) {return;}foreach ($this->abstractAliases[$abstract] as $alias) {if (! is_null($binding = $this->findInContextualBindings($alias))) {return $binding;}}
}protected function findInContextualBindings($abstract)
{if (isset($this->contextual[end($this->buildStack)][$abstract])) {return $this->contextual[end($this->buildStack)][$abstract];}
}

从上面可以看出,getContextualConcrete 函数把当前解析的类(Filesystem)作为 abstract,buildStack 最后一个类(PhotoController)作为 concrete,寻找 this->contextual[concrete] [abstract] (contextual[PhotoController] [Filesystem])中的值,在这个例子里面这个数组值就是那个匿名函数。

build 解析

对于服务容器来说,绑定是可以递归的,例如:

$app->bind('a','b');
$app->bind('b','c');
$app->bind('c',function(){return new C;})

遇到这样的情况,bind 绑定中 getClosure 函数开始发挥作用,该函数会给类包一层闭包,闭包内调用 make 函数,服务容器会不断递归调用 make 函数,直到最后一层,也就是绑定 c 的匿名函数。但是另一方面,有一些绑定方式并没有调用 bind 函数,例如上下文绑定 context:

$this->app->when(E::class)->needs(F::class)->give(A::class);

当make(E::class)的时候,getConcrete 返回 A 类,而不是调用 make 函数的闭包,所以并不会启动递归流程得到 C 的匿名函数,所以造成 A 类完全无法解析,isBuildable 函数就是解决这种问题的,当发现需要解析构造的对象很有可能是递归的,那么就递归调用 make 函数,否则才会调用build。

...
if ($this->isBuildable($concrete, $abstract)) {$object = $this->build($concrete);} else {$object = $this->make($concrete);}
...protected function isBuildable($concrete, $abstract)
{return $concrete === $abstract || $concrete instanceof Closure;
}

执行扩展

获取扩展闭包,并运行扩展函数:

protected function getExtenders($abstract)
{$abstract = $this->getAlias($abstract);if (isset($this->extenders[$abstract])) {return $this->extenders[$abstract];}return [];
}

回调

先后启动全局的解析事件回调函数,再启动针对类型的事件回调函数:

protected function fireResolvingCallbacks($abstract, $object)
{$this->fireCallbackArray($object, $this->globalResolvingCallbacks);$this->fireCallbackArray($object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks));$this->fireAfterResolvingCallbacks($abstract, $object);
}protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
{$results = [];foreach ($callbacksPerType as $type => $callbacks) {if ($type === $abstract || $object instanceof $type) {$results = array_merge($results, $callbacks);}}return $results;
}protected function fireAfterResolvingCallbacks($abstract, $object)
{$this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);$this->fireCallbackArray($object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks));
build 解析


make 函数承担了解析的大致框架,build 主要的职责就是利用反射将类构造出来,先看看主要代码:

public function build($concrete)
{// If the concrete type is actually a Closure, we will just execute it and// hand back the results of the functions, which allows functions to be// used as resolvers for more fine-tuned resolution of these objects.if ($concrete instanceof Closure) {return $concrete($this, $this->getLastParameterOverride());}$reflector = new ReflectionClass($concrete);// If the type is not instantiable, the developer is attempting to resolve// an abstract type such as an Interface of Abstract Class and there is// no binding registered for the abstractions so we need to bail out.if (! $reflector->isInstantiable()) {return $this->notInstantiable($concrete);}$this->buildStack[] = $concrete;$constructor = $reflector->getConstructor();// If there are no constructors, that means there are no dependencies then// we can just resolve the instances of the objects right away, without// resolving any other types or dependencies out of these containers.if (is_null($constructor)) {array_pop($this->buildStack);return new $concrete;}$dependencies = $constructor->getParameters();// Once we have all the constructor's parameters we can create each of the// dependency instances and then use the reflection instances to make a// new instance of this class, injecting the created dependencies in.$instances = $this->resolveDependencies($dependencies);array_pop($this->buildStack);return $reflector->newInstanceArgs($instances);
}

我们下面详细的说一下各个部分:

闭包函数执行

if ($concrete instanceof Closure) {return $concrete($this, $this->getLastParameterOverride());
}

这段代码很简单,但是作用很大。前面说过闭包函数有很多种类:

  • 用户绑定时提供的直接提供实现类的方式:

$app->singleton('auth',function($app){return new AuthManager($app)
});//对象类直接实现方法

这种情况 concrete(this) 直接就可以解析构造出具体实现类,服务容器解析完毕。

  • 用户绑定时提供的带有依赖注入的实现:

$app->singleton(EloquentFactory::class, function ($app) {return EloquentFactory::construct($app->make(FakerGenerator::class), database_path('factories')
);//对象类依赖注入

这种情况下,concrete(this) 会转而去解析 FakerGenerator::class,递归调用 make 函数。

  • bind函数使用 getClosure 包装而来:

function($container, $parameters = []){method = make/build;return $container->$method($concrete, $parameters);
}

这种情况,concrete(this) 将会继续递归调用 make 或者 build。

反射

当 build 的参数是类名而不是闭包的时候,就要利用反射构建类对象,如果构建的类对象不需要依赖任何其他参数,那么:

$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {return new $concrete;
}

如果需要依赖注入,那么就要用反射机制来获取 __construct 函数所需要注入的依赖,如果在make的时候带入参数值,那么直接利用传入的参数值;如果依赖是类对像,那么递归调用 make 函数;如果依赖是变量值,那么就从上下文中或者参数默认值中去获取:

...
$dependencies = $constructor->getParameters();
$instances = $this->resolveDependencies($dependencies);
...protected function resolveDependencies(array $dependencies)
{$results = [];foreach ($dependencies as $dependency) {if ($this->hasParameterOverride($dependency)) {$results[] = $this->getParameterOverride($dependency);continue;}$results[] = is_null($class = $dependency->getClass())? $this->resolvePrimitive($dependency): $this->resolveClass($dependency);}return $results;
}

解析变量值参数,如果变量值在上下文绑定中设置过,则去取上下文绑定的值,否则通过反射去取参数默认值,如果没有默认值,那么就要终止报错:

protected function resolvePrimitive(ReflectionParameter $parameter)
{if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {return $concrete instanceof Closure ? $concrete($this) : $concrete;}if ($parameter->isDefaultValueAvailable()) {return $parameter->getDefaultValue();}$this->unresolvablePrimitive($parameter);
}protected function hasParameterOverride($dependency)
{return array_key_exists($dependency->name, $this->getLastParameterOverride());
}protected function getParameterOverride($dependency)
{return $this->getLastParameterOverride()[$dependency->name];
}protected function getLastParameterOverride()
{return count($this->with) ? end($this->with) : [];
}

解析类参数,利用服务容器进行依赖注入:

protected function resolveClass(ReflectionParameter $parameter)
{try {return $this->make($parameter->getClass()->name);}catch (BindingResolutionException $e) {if ($parameter->isOptional()) {return $parameter->getDefaultValue();}throw $e;}
}

buildstack 解析栈

值的注意的是服务容器里面有个 buildStack,每次利用反射对参数进行依赖注入的时候,都要向这个数组中压入当前的解析对象,前面说过这部分是为了上下文绑定而设计的:

...
$this->buildStack[] = $concrete;//压入数组栈中
...
$instances = $this->resolveDependencies($dependencies);//解析依赖注入的参数
array_pop($this->buildStack);//弹出数组栈
...
解析标签


使用标签绑定的类,将会使用 tagged 来解析:

public function tagged($tag)
{$results = [];if (isset($this->tags[$tag])) {foreach ($this->tags[$tag] as $abstract) {$results[] = $this->make($abstract);}}return $results;
}
call方法注入


服务容器中,我们直接使用或者间接的使用 make 来构造服务对象,但是在实际的应用场景中,会有这样的需求:我们拥有一个对象或者闭包函数,想要调用它的一个函数,但是它函数里面却有其他类的参数,这个就需要进行 call 方法注入

public function call($callback, array $parameters = [], $defaultMethod = null)
{return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}

在 上一篇 文章中,我们说过,call 函数中的 callback 参数有以下几种形式:

  • 闭包 Closure

  • class@method

  • 类静态函数,class::method

  • 类静态函数: [ className/classObj, method ];类非静态函数: [ classObj, method ]

  • 若 defaultMethod 不为空,className
    首先,我们先看看 call 方法注入的流程图:

Markdown

从流程图中我们可以看出来,虽然调用 call 的形式有 5 种,但是实际最终的形式是三种,第二种和第五种被转化为了第四种。
接下来,我们详细的解析源码:

call

先看一下 call 方法的主体:

public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{if (static::isCallableWithAtSign($callback) || $defaultMethod) {return static::callClass($container, $callback, $parameters, $defaultMethod);}return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {return call_user_func_array($callback, static::getMethodDependencies($container, $callback, $parameters));});
}

可以看出来,call 方法注入主要有 4 个大的步骤:

  1. 对于 className@method 和 className-defaultMethod,实例化 className 为类对象,转化为 [ classObj, method ]。

  2. 判断 [ classObj / classname, method ] 是否存在被绑定的方法,如果有则调用。

  3. 利用服务容器解析依赖的参数。

  4. 调用 call_user_func_array。

实例化类对象

在这里 className@method 和 className-defaultMethod 两种情况被转化为 [ classObj, method ], className会被实例化为类对象,并重新调用 call:

protected static function isCallableWithAtSign($callback)
{return is_string($callback) && strpos($callback, '@') !== false;
}protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{$segments = explode('@', $target);$method = count($segments) == 2? $segments[1] : $defaultMethod;if (is_null($method)) {throw new InvalidArgumentException('Method not provided.');}return static::call($container, [$container->make($segments[0]), $method], $parameters);
}

执行绑定方法

针对 [ className/classObj, method ], 调用被绑定的方法:

protected static function callBoundMethod($container, $callback, $default)
{if (! is_array($callback)) {return value($default);}$method = static::normalizeMethod($callback);if ($container->hasMethodBinding($method)) {return $container->callMethodBinding($method, $callback[0]);}return value($default);
}protected static function normalizeMethod($callback)
{$class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);return "{$class}@{$callback[1]}";
}public function hasMethodBinding($method)
{return isset($this->methodBindings[$method]);
} public function callMethodBinding($method, $instance)
{return call_user_func($this->methodBindings[$method], $instance, $this);
}

那么这个被绑定的方法 methodBindings 从哪里来呢,就是 上一篇 文章提的 bindMethod:

public function bindMethod($method, $callback)
{$this->methodBindings[$method] = $callback;
}

从上面可以看出来,methodBindings 中 callback 参数一定是 classname@method 形式的。

实例化依赖

这一步就要通过反射来获取函数方法需要注入的参数类型,然后利用服务容器对参数类型进行解析构建:

protected static function getMethodDependencies($container, $callback, array $parameters = [])
{$dependencies = [];foreach (static::getCallReflector($callback)->getParameters() as $parameter) {static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);}return array_merge($dependencies, $parameters);
}

getCallReflector 函数利用反射来获取参数类型,值得注意的是class::method是需要拆分处理的:

protected static function getCallReflector($callback)
{if (is_string($callback) && strpos($callback, '::') !== false) {$callback = explode('::', $callback);}return is_array($callback)? new ReflectionMethod($callback[0], $callback[1]): new ReflectionFunction($callback);
}

利用传入的参数,利用服务容器构建解析参数类型,或者获取参数默认值:

protected static function addDependencyForCallParameter($container, $parameter,array &$parameters, &$dependencies)
{if (array_key_exists($parameter->name, $parameters)) {$dependencies[] = $parameters[$parameter->name];unset($parameters[$parameter->name]);} elseif ($parameter->getClass()) {$dependencies[] = $container->make($parameter->getClass()->name);} elseif ($parameter->isDefaultValueAvailable()) {$dependencies[] = $parameter->getDefaultValue();}
}

call_user_func_array

关于这个函数可以参考 Laravel学习笔记之Callback Type

call_user_func_array($callback, static::getMethodDependencies($container, $callback, $parameters));

Written with StackEdit.




推荐阅读
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 标题: ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
author-avatar
ian
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有