照猫画虎 实现 min-laravel 框架系列之服务容器
- laravel
- 2020-06-04
- 4015
- 0
服务容器
laravel 的核心部分,就是服务容器,本系列重点放在代码实现部分,不会过多的讲解服务容器概念等,不了解的同学可以自行 Google 。
可以粗略的理解:在面向对象的思想下,服务容器相当于一个黑盒子,里边保存了各种接口类和实现类的对应关系,在系统初始化之前,往黑盒子中绑定各种对应关系,当对外提供服务时,需要什么类则直接通过黑盒子进行解析提取即可。
重点类介绍
ArrayAccess
该类的主要功能是提供像访问数据一样的功能,去访问 Container 成员
Container
在 laravel 框架中,Application 类继承了 Container,所有关于服务容器的核心功能代码,都在这个类中。为了清楚看到该类的核心方法,可以先浏览一下接口类 Illuminate\Contracts\Container\Container。其中最重要的属于 bind(绑定) 、resolve(解析) 两个方法
bind 方法
public function bind($abstract, $concrete = null, $shared = false)
{
// 旧的删除
$this->dropStaleInstances($abstract);
/**
* 如果没有给实现类,那么就定义自己为自己的实现类
*/
if (is_null($concrete)) {
$concrete = $abstract;
}
// 进行一个闭包包装
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
// 用类变量记录绑定记录
$this->bindings[$abstract] = compact('concrete', 'shared');
// 如果曾经解析过,需要处理一写东西
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
instance 方法
该方法作用和 bind 类似,区别是 bind 的 abstract 是闭包或者实现类的类名,而 instance 的 abstract 参数是一个已经实例化后的对象,
public function instance($abstract, $instance)
{
// 删除之前有过的别名
$this->removeAbstractAlias($abstract);
// 是否绑定过
$isBound = $this->bound($abstract);
// 删除别名
unset($this->aliases[$abstract]);
// 保存绑定关系
$this->instances[$abstract] = $instance;
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
resolve 方法
从服务容器中解析对象实例
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
// 拿到大名
$abstract = $this->getAlias($abstract);
// 获取上下文保存的数据
$concrete = $this->getContextualConcrete($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
// 单列问题
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
// 把当前用到的参数进栈
$this->with[] = $parameters;
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}
// 如果 concrete 是一个闭包或者实例化的类,直接调用 build 直接解析,否则接着调用 make,进栈
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// 扩展部分
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// 单例模式
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
// 设置解析标志,表明此抽象类系统已经解析过了
$this->resolved[$abstract] = true;
// 参数,出栈
array_pop($this->with);
return $object;
}
build 方法
这个方法就是实际利用 php 反射机制进行实例化对象的方法。
public function build($concrete)
{
// 如果是闭包,直接返回闭包
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
try {
// 反射机制了
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// 如果该类无法实例化,抛出异常
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
// 正在实例化的类,进栈
$this->buildStack[] = $concrete;
// 构造函数
$constructor = $reflector->getConstructor();
// 如果没有初始化函数,则表明此类没有依赖,直接进行 new Class ,返回对象就行
if (is_null($constructor)) {
// 出栈
array_pop($this->buildStack);
return new $concrete;
}
// 获取构造函数的所有参数
$dependencies = $constructor->getParameters();
// 获取所有的构造函数参数数组,然后一个个进行解析
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
alias 方法
为什么会有别名,这个就像现实生活中,我们有身份证上的大名,也有自己的乳名、外号等,虽然一个大名也完全够用,但是在家人之间一直叫大名,也不是很完美,
获取别名,该方法是一个递归类函数,一定会刨根到底,拿倒最后的身份证上的大名,ex A—> B —-> C ,如果要获取 C,该方法会通过递归调用,最后返回 A
- 设置别名
public function alias($abstract, $alias)
{
if ($alias === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
- 解析别名
public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
return $this->getAlias($this->aliases[$abstract]);
}
flush 方法
这个方法代码也很简单,就是清楚所有在绑定过程中设置过的成员变量。通过这个方法,可以看到,在进行服务绑定的过程中,laravel 系统都设置了哪些变量,最好时刻清楚每个变量里边存储的类型
public function flush()
{
$this->aliases = [];
$this->resolved = [];
$this->bindings = [];
$this->instances = [];
$this->abstractAliases = [];
}
laravel 步骤
application 初始化
通过 bootstrap 目录下的 app 文件,初始化 application 类。
1、设置基础路程
所有系统可能用到的路径,都在这里进行设置,保存系统根目录、app 目录、bootstrap 目录、public 目录等等。
2、注册一些基础绑定关系
这块主要是绑定 application 类到服务容器
protected function registerBaseBindings()
{
// 绑定 application 自己
static::setInstance($this);
// 设置 app ---> application 的对应关系
$this->instance('app', $this);
$this->instance(Container::class, $this);
// 这个暂时不管
// $this->singleton(Mix::class);
// 这个是包自动发现的实现代码,暂时不管
// $this->singleton(PackageManifest::class, function () {
// return new PackageManifest(
// new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
// );
// });
}
3、注册核心的服务提供者
服务提供者是 laravel 另一个重要概念,在这就不展开说明了。这块主要注册了三个系统核心的服务提供者
- 事件服务提供者
- 日志服务提供者
- 路由服务提供者
4、设置别名
在这里先设置 app 的别名,
总结
到此,基本上完成了框架 application 类的初始化动作,通过设计这么一个复杂的服务提供者,通过 php 的反射机制,使系统更好的管理各个类的初始化和依赖关系