思考并回答以下问题:
- make解析的时候会调用build函数实例化对象。
- make($abstract, array $parameters = []);
简介
make和resolve都是从容器中解析实例(这个实例是指concrete)出来。简单说就是从容器中把前面bind进去的东西拿出来用。
这里需要明确的是,make解析的时候会调用build函数实例化对象,就是说理论上如果绑定的是一个字符串,Laravel默认这是一个可以实例化对象的类路径。
而我们如果想要绑定一个纯粹的字符串或者数字,我们可以使用闭包函数。让闭包返回我们需要的类型。具体看下面的源码。
把resolve和make放在一起是因为其实在Container类中,make就是resolve的一个包装。
我们看看make方法:很简单直接调用了resolve方法,类似的还有makeWith方法。
1 | /** |
1 | /** |
源码
先整体看下resolve函数源码:
1 | /** |
还是从参数说起:
- 参数$abstract,获取在容器中的服务的名字,或者叫id。
- 参数$parameters,有些实例对象实例化的时候会需要参数,这个$parameters就是我们传入的参数。
举例:看代码,上一章我们知道,bind只是绑定一个闭包,啥也不干,所以不用传入参数,因为压根没有实例化对象。但是当我们这里要make解析的时候,即实例化Boss::class的时候,我们要把这个Object类型的对象传进去,Boss::class才能实例化。
1 |
|
1.获取$abstract的别名。请参看。
1 | $abstract = $this->getAlias($abstract); |
2.设置一个变量$needsContextualBuild来做标记,标记当前这个解析的实例需不需要上下文绑定。
1 | $needsContextualBuild = ! empty($parameters) || ! is_null( |
在上下文绑定那章我们也说了,上下文绑定其实就是依赖绑定,就是判断当前的make的实例需不需要依赖。满足下面两个条件中的任意一个就需要:
- a.传入的参数不为空。很好理解,你都传入参数了,这个参数上面刚刚讲了就是为了当前实例化的时候传入作为依赖的。
- b.通过函数getContextualConcrete,获取到了当前解析的这个类,是否已经有了上下文绑定的依赖。(就是事先已经使用上下文绑定过了),这个其实虚的没有任何作用,往下细看。
2.1.那让我们看看getContextualConcrete($abstract)方法如何获取事先绑定的上下文依赖的。
getContextualConcrete代码如下:
1 | /** |
2.1.1.首先判断是否在上下文绑定的数组中存在abstract的实例concrete,如果有就返回。直接从数组中找。
2.1.2.如果没有,看看这个$abstractAliases数组里面有没有$abstract别名,这个数组前面“别名”章节我们提过,和$aliases数据保存相反格式,保存abstract和alias关系的数组。注意,后面的数组value值才是别名,键值‘app’是abstract,格式如下:
1 | $abstractAliases = [ |
继续看源代码。如果这个数组是空的,直接返回了。
2.1.3.如果这个数组不是空的,遍历所有abstract的别名,这个别名在binding数组中是否存在。
简单说就是abstract如果不在上下文绑定的数组中,那么看看abstract的别名是否在上下文绑定数组中。最后判断一下返回。
2.1.3.1重点来了,我们去看看findInContextualBindings源码:
1 | /** |
还记得上下文绑定那章的存储结构就是这样:contextual[when][give]=implement。这里就是取对应的值。
但是我们发现他在取[give]值的时候它使用了end($this->buildStack)。buildStack是build的实例的堆栈,我们上下文绑定的流程中完全没有这个绑定。也就是说我们从resolve进来你是找不到这个值的,这完全是虚的没有任何作用,getContextualConcrete不会取得任何值。他的存在其实是给build函数创建依赖对象的时候,会递归再次回来make解析依赖类用的。看下一章build方法解析。
总结第二点,其实我们这里主要判断是就是有没有parameters,getContextualConcrete似乎完全不会取得任何值。
3.回到主线resolve函数,如果在数组instances中已经存在这个abstract的对象了并且不需要上下文绑定,直接调用这个instances中的值返回。我们前面章节知道instances数组是保存可以shared的实体对象。既然有了,并且没有依赖,就直接返回。
这里有个问题,如果有依赖,instances中的值为什么不能直接返回,因为依赖可能会变化,仔细想想是不是。你前面使用instance传入的有依赖的对象的参数,和这次我们要求的对象传入的依赖参数,可能是不同的。比如以前存储的new A(‘1’),这次需要的newA(‘2’),一个对象参数不同。
1 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) |
这里有一个问题,通过instance()方法是可以保存任何类型数据的。但是如果instances数组中没有事先存在的值,那么make解析的字符串默认被当做一个类路径的。(后面章节有instace绑定源码分析)举例如下:
1 | // 使用instance存入字符串绑定。成功 |
4.1前面的条件没成立的话,接下来,把参数parameters存入with数组,前面讲过了,parameters是实例化的时候需要的依赖,所以暂存于with数组。
1 | $this->with[] = $parameters; |
4.2.接下来通过函数getConcrete($abstract)获取concrete。
1 | $concrete = $this->getConcrete($abstract); |
我们看getConcrete源代码:
1 | /** |
主要的思路是:
a. 看看上下文绑定数组中有没有$abstract对应的concrete值,如果有,太好了,最复杂的情况就是上下文绑定。直接返回就好了。连依赖都已经添加了。(参看上下文绑定存储结构和使用方法)
这里要特别注意,上下文绑定获取的concrete值可以是一个类路径,也可以是一个闭包(看看文档如何使用上下文绑定就知道了,可以传入类路径也可以是闭包。)。但是在后面的处理对两个情况是不一样的。
和上面情况雷同,其实这里getConcrete还是调用了getContextualConcrete,但buildstack中没有值,所以这个是虚的。暂时是没有值的。build解析依赖类的时候递归回来才有这个buildstack值。
b.如果没找到上下文绑定,就是一个普通绑定,就去bindings的数组中看看有没有$abstract对应的concrete值,从而确认是不是以前有绑定过。同样的$concrete可以是一个闭包,也可以是一个类路径。
c.都没有,说明没有绑定!!直接返回$abstract。
这里说明什么呢,我猜想我们是可以不用绑定bind函数,而直接make的,这样的话可以直接把$abstract当做$concrete来解析。
1 | // 实测有效,直接返回Money::class 对象。 |
这个方法处理的结果也有三种可能:
- a.上下文绑定的concrete(这个其实没有)
- b.binding 数组中的concrete
- c.把$concrete===$abstract相等。
这里的c步骤到底做了什么,怎么处理的?我们往下看代码。第五步。
5.获取解析的对象了。
1 | // 我们已经准备好实例化为绑定注册的具体类型的实例。 |
5.1 首先,我们要看下函数isBuildable函数是什么要求。如果$concrete===$abstract或者 concrete是一个闭包,好办返回true。
1 | /** |
5.2 如果是true,那么使用build函数处理这个object。
我们在这里简单说下build具体的会在下一章build源码中分析。
build的作用是这样的:
- a.如果concrete是闭包,build执行闭包函数。
- b.不是闭包,build函数会使用反射产生当前$concrete类的对象。和前面我们的猜想一样。既然$abstract===$concrete,那么直接解析,都不用绑定。
5.3 如果isBuildable返回的是false呢?就是$concrete的值是・类路径・的情况,调用make进入递归。如下give给的不是一个闭包是一个类路径。则进入make。
1 | $container |
make再去getConcrete函数,去上下文绑定数组和binding数组,查询这个时候这个・类路径下・(就是 abstract)有没有对应的闭包或类路径。但不管怎么样。最后下来要么闭包,要么相等,他都会进入build函数创建对象。
6.到此,我们得到了解析出来的object对象。
然后第六步我们要看看是否有扩展绑定entend的处理,参看0.2章节,执行:
1 | // 如果我们为此类型定义了任何扩展程序,则需要遍历它们并将其应用于正在构建的对象。 |
7.是否是单例分享的,如果是的话就存入instance,参看0.4章节
1 | // 如果将请求的类型注册为单例,我们将要缓存“内存”中的实例, |
8.接着触发各个回调函数,参看0.3章节,执行回调,这个函数就是触发3个地方的回调函数。
1 | $this->fireResolvingCallbacks($abstract, $object); |
9.标记已经解析了。并且把参数从with中pop掉,没用了。这个with在build方法中使用了,在make方法中没有用到。
1 | // 返回之前,我们还将解析标记设置为“true”,并弹出此构建的参数替代。 |
最后返回对象。
总结
make(解析)相对复杂。但是主要关注几个大步骤就能明白流程。
- 1.首先获取最终的别名。
- 2.设置是否是・上下文绑定・的标记。
- 3.如果在shared的instances数组中找到了,同时又不是有上下文绑定需求的。直接返回对象。结束程序。
- 4.否则,把实例化对象所依赖的参数parameters暂存with数组。
- 5.通过getConcrete方法获取$concrete.注意这里的concrete还不是对象,是类路径或者是一个闭包函数。
- 6.有了$concrete,如果是闭包,我们利用build函数生成对象。
- 7.如果是类路径,我们要再递归,看看这个路径下是否还有$concrete的绑定。如果有再递归,像别名一样,找到真正那个。如果没有,使用build函数反射原理生成对象返回,with数组将在build反射中使用。
- 8.完成对象生成,看看有没有extend扩展。
- 9.看看是否需要shard,把对象存入instance中。
- 10.触发各个回调函数。
- 11.记录这个abstract已经解析过了。
- 12.把with数组中parameters清空掉。
- 13.返回对象。