照猫画虎 实现 min-laravel 框架系列之服务容器

php   laravel  

服务容器

laravel 的核心部分,就是服务容器,本系列重点放在代码实现部分,不会过多的讲解服务容器概念等,不了解的同学可以自行 Google 。

可以粗略的理解:在面向对象的思想下,服务容器相当于一个黑盒子,里边保存了各种接口类和实现类的对应关系,在系统初始化之前,往黑盒子中绑定各种对应关系,当对外提供服务时,需要什么类则直接通过黑盒子进行解析提取即可。

重点类介绍

ArrayAccess

该类的主要功能是提供像访问数据一样的功能,去访问 Container 成员

Container

在 laravel 框架中,Application 类继承了 Container,所有关于服务容器的核心功能代码,都在这个类中。为了清楚看到该类的核心方法,可以先浏览一下接口类 Illuminate\Contracts\Container\Container。其中最重要的属于 bind(绑定) 、resolve(解析) 两个方法

bind 方法

  1. public function bind($abstract, $concrete = null, $shared = false)
  2. {
  3. // 旧的删除
  4. $this->dropStaleInstances($abstract);
  5. /**
  6. * 如果没有给实现类,那么就定义自己为自己的实现类
  7. */
  8. if (is_null($concrete)) {
  9. $concrete = $abstract;
  10. }
  11. // 进行一个闭包包装
  12. if (! $concrete instanceof Closure) {
  13. $concrete = $this->getClosure($abstract, $concrete);
  14. }
  15. // 用类变量记录绑定记录
  16. $this->bindings[$abstract] = compact('concrete', 'shared');
  17. // 如果曾经解析过,需要处理一写东西
  18. if ($this->resolved($abstract)) {
  19. $this->rebound($abstract);
  20. }
  21. }

instance 方法

该方法作用和 bind 类似,区别是 bind 的 abstract 是闭包或者实现类的类名,而 instance 的 abstract 参数是一个已经实例化后的对象,

  1. public function instance($abstract, $instance)
  2. {
  3. // 删除之前有过的别名
  4. $this->removeAbstractAlias($abstract);
  5. // 是否绑定过
  6. $isBound = $this->bound($abstract);
  7. // 删除别名
  8. unset($this->aliases[$abstract]);
  9. // 保存绑定关系
  10. $this->instances[$abstract] = $instance;
  11. if ($isBound) {
  12. $this->rebound($abstract);
  13. }
  14. return $instance;
  15. }

resolve 方法

从服务容器中解析对象实例

  1. protected function resolve($abstract, $parameters = [], $raiseEvents = true)
  2. {
  3. // 拿到大名
  4. $abstract = $this->getAlias($abstract);
  5. // 获取上下文保存的数据
  6. $concrete = $this->getContextualConcrete($abstract);
  7. $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
  8. // 单列问题
  9. if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
  10. return $this->instances[$abstract];
  11. }
  12. // 把当前用到的参数进栈
  13. $this->with[] = $parameters;
  14. if (is_null($concrete)) {
  15. $concrete = $this->getConcrete($abstract);
  16. }
  17. // 如果 concrete 是一个闭包或者实例化的类,直接调用 build 直接解析,否则接着调用 make,进栈
  18. if ($this->isBuildable($concrete, $abstract)) {
  19. $object = $this->build($concrete);
  20. } else {
  21. $object = $this->make($concrete);
  22. }
  23. // 扩展部分
  24. foreach ($this->getExtenders($abstract) as $extender) {
  25. $object = $extender($object, $this);
  26. }
  27. // 单例模式
  28. if ($this->isShared($abstract) && ! $needsContextualBuild) {
  29. $this->instances[$abstract] = $object;
  30. }
  31. if ($raiseEvents) {
  32. $this->fireResolvingCallbacks($abstract, $object);
  33. }
  34. // 设置解析标志,表明此抽象类系统已经解析过了
  35. $this->resolved[$abstract] = true;
  36. // 参数,出栈
  37. array_pop($this->with);
  38. return $object;
  39. }

build 方法

这个方法就是实际利用 php 反射机制进行实例化对象的方法。

  1. public function build($concrete)
  2. {
  3. // 如果是闭包,直接返回闭包
  4. if ($concrete instanceof Closure) {
  5. return $concrete($this, $this->getLastParameterOverride());
  6. }
  7. try {
  8. // 反射机制了
  9. $reflector = new ReflectionClass($concrete);
  10. } catch (ReflectionException $e) {
  11. throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
  12. }
  13. // 如果该类无法实例化,抛出异常
  14. if (! $reflector->isInstantiable()) {
  15. return $this->notInstantiable($concrete);
  16. }
  17. // 正在实例化的类,进栈
  18. $this->buildStack[] = $concrete;
  19. // 构造函数
  20. $constructor = $reflector->getConstructor();
  21. // 如果没有初始化函数,则表明此类没有依赖,直接进行 new Class ,返回对象就行
  22. if (is_null($constructor)) {
  23. // 出栈
  24. array_pop($this->buildStack);
  25. return new $concrete;
  26. }
  27. // 获取构造函数的所有参数
  28. $dependencies = $constructor->getParameters();
  29. // 获取所有的构造函数参数数组,然后一个个进行解析
  30. try {
  31. $instances = $this->resolveDependencies($dependencies);
  32. } catch (BindingResolutionException $e) {
  33. array_pop($this->buildStack);
  34. throw $e;
  35. }
  36. array_pop($this->buildStack);
  37. return $reflector->newInstanceArgs($instances);
  38. }

alias 方法

为什么会有别名,这个就像现实生活中,我们有身份证上的大名,也有自己的乳名、外号等,虽然一个大名也完全够用,但是在家人之间一直叫大名,也不是很完美,

获取别名,该方法是一个递归类函数,一定会刨根到底,拿倒最后的身份证上的大名,ex A—> B —-> C ,如果要获取 C,该方法会通过递归调用,最后返回 A

  • 设置别名
  1. public function alias($abstract, $alias)
  2. {
  3. if ($alias === $abstract) {
  4. throw new LogicException("[{$abstract}] is aliased to itself.");
  5. }
  6. $this->aliases[$alias] = $abstract;
  7. $this->abstractAliases[$abstract][] = $alias;
  8. }
  • 解析别名
  1. public function getAlias($abstract)
  2. {
  3. if (! isset($this->aliases[$abstract])) {
  4. return $abstract;
  5. }
  6. return $this->getAlias($this->aliases[$abstract]);
  7. }

flush 方法

这个方法代码也很简单,就是清楚所有在绑定过程中设置过的成员变量。通过这个方法,可以看到,在进行服务绑定的过程中,laravel 系统都设置了哪些变量,最好时刻清楚每个变量里边存储的类型

  1. public function flush()
  2. {
  3. $this->aliases = [];
  4. $this->resolved = [];
  5. $this->bindings = [];
  6. $this->instances = [];
  7. $this->abstractAliases = [];
  8. }

laravel 步骤

application 初始化

通过 bootstrap 目录下的 app 文件,初始化 application 类。

1、设置基础路程

所有系统可能用到的路径,都在这里进行设置,保存系统根目录、app 目录、bootstrap 目录、public 目录等等。

2、注册一些基础绑定关系

这块主要是绑定 application 类到服务容器

  1. protected function registerBaseBindings()
  2. {
  3. // 绑定 application 自己
  4. static::setInstance($this);
  5. // 设置 app ---> application 的对应关系
  6. $this->instance('app', $this);
  7. $this->instance(Container::class, $this);
  8. // 这个暂时不管
  9. // $this->singleton(Mix::class);
  10. // 这个是包自动发现的实现代码,暂时不管
  11. // $this->singleton(PackageManifest::class, function () {
  12. // return new PackageManifest(
  13. // new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
  14. // );
  15. // });
  16. }

3、注册核心的服务提供者

服务提供者是 laravel 另一个重要概念,在这就不展开说明了。这块主要注册了三个系统核心的服务提供者

  • 事件服务提供者
  • 日志服务提供者
  • 路由服务提供者

4、设置别名

在这里先设置 app 的别名,

总结

到此,基本上完成了框架 application 类的初始化动作,通过设计这么一个复杂的服务提供者,通过 php 的反射机制,使系统更好的管理各个类的初始化和依赖关系



评论 0

发表评论

Top