照猫画虎 实现 min-laravel 框架系列之服务容器
- laravel
- 2020-06-04
- 258
- 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 的反射机制,使系统更好的管理各个类的初始化和依赖关系