zoukankan      html  css  js  c++  java
  • laravel核心Ioc容器

    laravel容器和依赖注入


    • 啥是Ioc容器,方便我们实现依赖注入的一种实现,也就是说依赖注入不一定需要控制反转容器,只不过使用容器可能会方便些。

    • laravel通过向容器中绑定接口的具体实现,可实现不同实现的快速切换,接口在laravel中有个好听的名字叫契约。

    • 面向接口编程和容器结合使用,可以轻松实现代码解耦,实现了关注分离。

    • 面向接口开发的好处:除了可以快速切换实现了相同契约的实现,对开发测试同步进行,以及对单元测试都是非常有好的。

    • 下面是一个简单的使用示例,为了相对好理解没有加入service层。

      1 创建文章接口 
      <?php
      
      namespace AppContracts;
      
      interface ArticleRepository
      {   
          // 返回文章列表
          public function getList(): array;
      }
      
      2 创建一个文章具体实现类
      <?php
      
      namespace App;
      
      use AppContractsArticleRepository;
      use AppModelsArticle;
      
      class DbArticle implements ArticleRepository
      {
          public function getList(): array
          {
              return Article::all()->toArray();
          }
      }
      
      3 在容器中进行绑定
      <?php
      
      namespace AppProviders;
      
      use AppDbArticle;
      use IlluminateSupportServiceProvider;
      use AppContractsArticleRepository;
      use AppDbArticle;
      
      class AppServiceProvider extends ServiceProvider
      {
          /**
           * Register any application services.
           *
           * @return void
           */
          public function register()
          {
              $this->app->bind(ArticleRepository::class, function () {
                  return new DbArticle();
              });
          }
          
      4 在控制器中使用
      <?php
      
      namespace AppHttpControllersTest;
      
      use AppHttpControllersController;
      use IlluminateHttpRequest;
      use AppContractsArticleRepository;
      
      class ArticleController extends Controller
      {   
          protected $articles;
      
          public function __construct(ArticleRepository $articles)
          {
              $this->articles = $articles;
          }   
      
          public function index()
          {   
              $articles = $this->articles->getList();
              return view('article.index', compact('articles'));
          }
      }
      
      以上便是一个比较标准的依赖注入的使用方式,其实还有很多使用方式,比如通过app()函数或者App门面直接获取依赖等等。看到这里应该能感觉到依赖注入为开发者带来的方便了,上述例子中文章是从ORM中取出,当需求改变要求文章从mongodb或者redis中取出的时候,我们只需要编写单独的实现类,然后在服务提供者中绑定新的实现,业务代码完全不需要改变,就能快速实现切换。
      
    • 下面讲解laravel如何实现的依赖注入 laravel版本6.12

      从bootstrap/app.php开始
      
      // 实例化app容器类 并传递项目根目录给构造函数
      $app = new IlluminateFoundationApplication(
          $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
      );
      
      IlluminateFoundationApplication中
      public function __construct($basePath = null)
      {
          if ($basePath) {
              // 注册基本路径 调用container的instance方法 添加路径实例到容器
              // 结果就是app实例的instances属性下多了几条路径映射 可直接打印$this查看
              $this->setBasePath($basePath); 
          }
          // 注册基本绑定 跳转到下面的registerBaseBindings方法
          $this->registerBaseBindings();          
          $this->registerBaseServiceProviders();
          $this->registerCoreContainerAliases();
      }
      
      protected function registerBaseBindings()
      {   
          static::$instance = $this;
      	// 绑定自身到容器
          $this->instance('app', $this);
      	// 依然还是绑定
          $this->instance(Container::class, $this);
          // 重点来了!!! 跳转到下面的singleton方法
          $this->singleton(Mix::class);  
          $this->instance(PackageManifest::class, new PackageManifest(
              new Filesystem,
              $this->basePath(),
              $this->getCachedPackagesPath()
          ));
      }
      
      
      IlluminateContainerContainer中
      // 绑定共享实例 就是单例绑定  可以看到singleton方法就是调用了bind方法
      public function singleton($abstract, $concrete = null)
      {
          $this->bind($abstract, $concrete, true);
      }
      
      /**
       * Register a binding with the container.
       *
       * @param  string  $abstract
       * @param  Closure|string|null  $concrete 注意参数类型
       * @param  bool  $shared
       * @return void
       */
      public function bind($abstract, $concrete = null, $shared = false)
      {	
          // 此处的$abstract = IlluminateFoundationMix
          // 删除老的实例绑定
          $this->dropStaleInstances($abstract);
      	
          // laravel默认的如果没传递对应抽象的实例,那么就认为实例应该是传递的抽象的一个实例
          if (is_null($concrete)) {
              // 如果concrete为null
              $concrete = $abstract;
          }
      	
          // 如果传递的不是一个闭包
          if (!$concrete instanceof Closure) {
              // laravel会试图根据传递进来的抽象得到一个闭包
              // 跳转到getClosure方法
              // 
              $concrete = $this->getClosure($abstract, $concrete);
          }
      	
          // 将返回的闭包和是否shared标志保存到bindings数组下
          $this->bindings[$abstract] = compact('concrete', 'shared');
      
          if ($this->resolved($abstract)) {
              $this->rebound($abstract);
          }
      }
      
      /**
       * Get the Closure to be used when building a type.
       *
       * @param  string  $abstract
       * @param  string  $concrete
       * @return Closure
       */
      // 值得注意的是此时生成的闭包不会立即执行,触发条件官方在注释中写的很清楚,当要使用时才会执行此闭包
      protected function getClosure($abstract, $concrete)
      {	
          // 此时的$abstract $concrete都是IlluminateFoundationMix
          return function ($container, $parameters = []) use ($abstract, $concrete) {
              // 如果调用bind方法只传入了abstract
              if ($abstract == $concrete) {
                  // 返回容器的build方法返回的实例
                  return $container->build($concrete);
              }
      		// 否则直接调用contaner的resolve方法进行解析
              return $container->resolve(
                  $concrete,
                  $parameters,
                  $raiseEvents = false
              );
          };
      }
      
      // 什么时候使用到这些绑定呢(如何从容器中解析需要的实例呢) 来看make方法 调用方式app()->make($yourAbstract)
      /**
       * Resolve the given type from the container.
       *
       * @param  string  $abstract
       * @param  array  $parameters
       * @return mixed
       *
       * @throws IlluminateContractsContainerBindingResolutionException
       */
      public function make($abstract, array $parameters = [])
      {	
          // 跳转到resolve方法
          return $this->resolve($abstract, $parameters);
      }
      
      /**
       * Resolve the given type from the container.
       *
       * @param  string  $abstract
       * @param  array  $parameters
       * @param  bool  $raiseEvents
       * @return mixed
       *  
       * @throws IlluminateContractsContainerBindingResolutionException
       */
      protected function resolve($abstract, $parameters = [], $raiseEvents = true)
      {
          $abstract = $this->getAlias($abstract);
      	
          $needsContextualBuild = !empty($parameters) || !is_null(
              $this->getContextualConcrete($abstract)
          );
      
          // 如果要解析的类名instances中已经有了 并且不需要临时build  那么就返回已经存在的实例
          if (isset($this->instances[$abstract]) && !$needsContextualBuild) {
              // dd(123, $abstract);
              // dd($this->instances);
              return $this->instances[$abstract];
          }
      
          $this->with[] = $parameters;
      	
          // getConcrete方法 拿到之前可能绑定过的binding 绑定过就是一个闭包
          // 没绑定过返回自身,即通过容器make可一个自定义的类
          $concrete = $this->getConcrete($abstract);
      	
          if ($this->isBuildable($concrete, $abstract)) {
      		// 当concrete === abstract 或者 concrete是一个闭包的时候 直接调用build方法
              // 跳转到build方法
              $object = $this->build($concrete);
          } else {
              // 当concrete是一个类名的时候 调用make方法
              $object = $this->make($concrete);
          }
      
          foreach ($this->getExtenders($abstract) as $extender) {
              $object = $extender($object, $this);
          }
      	
          // 如果是共享的实例 或者 不需要临时build 那么就将实例存放到容器的instances数组中
          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;
      }
      
      public function build($concrete)
      {	
          // 如果传递进来的是一个闭包 (通过之前bind方法绑定得到的闭包)
          // 直接调用闭包返回实例
          if ($concrete instanceof Closure) {
              return $concrete($this, $this->getLastParameterOverride());
          }
      	
          // 如果传递进来的是一个可能的类名 那么调用反射api解析依赖
          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实例
          if (is_null($constructor)) {
              array_pop($this->buildStack);
              return new $concrete;
          }
      	
          // 拿到构造方法中的依赖
          $dependencies = $constructor->getParameters();
      
          try {
              // 解析依赖
              // 跳转到resolveDependencies方法
              $instances = $this->resolveDependencies($dependencies);
          } catch (BindingResolutionException $e) {
              array_pop($this->buildStack);
      
              throw $e;
          }
      
          array_pop($this->buildStack);
      	
          // 返回实例
          return $reflector->newInstanceArgs($instances);
      }
      
      protected function resolveDependencies(array $dependencies)
      {
          $results = [];
      
          foreach ($dependencies as $dependency) {
              if ($this->hasParameterOverride($dependency)) {
                  // 如果存在临时重写 则获取
                  $results[] = $this->getParameterOverride($dependency);
      
                  continue;
              }
      		
              // 如果构造方法中不存在类型提示 那么会返回null
              // 跳转到resolvePrimitive方法
              $results[] = is_null($dependency->getClass())
                  ? $this->resolvePrimitive($dependency)
                  // 如果存在类型提示 根据类型尝试解析出依赖实例 getClass能拿到一个确切的类名
                  // 跳转到resolveClass方法
                  : $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 resolveClass(ReflectionParameter $parameter)
      {
          try {
              // 又回到了make方法 进行了递归调用 
              // 至此一切形成了闭环 完美!!!
              return $this->make($parameter->getClass()->name);
          }
      
          catch (BindingResolutionException $e) {
              if ($parameter->isOptional()) {
                  return $parameter->getDefaultValue();
              }
      
              throw $e;
          }
      }
      
      可以看到:
      1 当从laravel中解析对象的时候,实际调用的是build方法,而build方法首先会尝试从之前的绑定中获取,如果未找到绑定那么就通过反射机制尝试解析,当然解析依赖的过程依然存在尝试获得之前绑定的过程。
      2 laravel的容器解析依赖需要使用其固定的规则,直接调用app()函数或者App门面从容器中解析。
      3 建议使用容器进行解析自定义类的时候,一定要加上依赖的类型
      4 绑定的时候尽量使用闭包形式
      

    其中的临时绑定等需要配合laravel容器的when with等api使用,还有解析时的事件等,本文都没进行讲解。

    下期预告:Application类实例化时剩下的两个方法

  • 相关阅读:
    ZOJ 1649: Rescue(BFS)
    UVA
    hdu2458:Kindergarten (最大独立集)
    hdu3829:Cat VS Dog (最大独立集)
    Java 泛型
    request.getParameter() 和request.getAttribute() 区别
    Solr版本安装部署指南
    java.sql.SQLException: Incorrect string value: 'xE6x88x91xE7x9Ax84...' for column 'groupName'
    Incorrect string value: 'xF0x9Fx98x84xF0x9F
    java里面byte数组和String字符串怎么转换
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13365915.html
Copyright © 2011-2022 走看看