【lara】IoC 模式 - 服务容器

【lara】IoC 模式 - 服务容器_第1张图片
服务容器工作示意图

$bindings用于存储提供服务的回调函数
$instances用于存储程序中共享的实例,也可以称为单例。

服务容器的生成

bootstrap/app.php 文件实现了服务容器的实例化,同时绑定了核心处理类。至此,已经获得了一个全局的服务容器实例,即$app。

Register an existing instance as shared in the container
绑定当前 Application 对象进容器,绑定的是同一对象,但给了两个名字
$this->registerBaseBindings();
  app
  Illuminate\Container\Container

$this->registerBaseServiceProviders();
   $this->register(new EventServiceProvider($this));
   $this->register(new LogServiceProvider($this));
   $this->register(new RoutingServiceProvider($this));


注册别名
$this->registerCoreContainerAliases();

服务绑定

绑定实际上可以简单地理解为一个关键字和一个服务进行绑定,可以简单看做是一种键值对形式,即一个“key”对应一个服务。对于绑定服务的不同,需要服务容器中不同的绑定函数来实现,主要包括回调函数服务绑定和实例对象服务绑定。

  • 普通绑定
    普通绑定每次生成该服务的实例对象时都会生成一个新的实例对象,也就是说在程序的生命周期中,可以同时生成很多个这种实例对象。
  • 单例绑定
    单例绑定在生成一个实例对象后,如果再次生成就会返回第一次生成的实例对象,也就是说在程序的生命周期中,只能生成一个这样的实例对象,如果想使用就会获取之前生成的,也就是设计模式中的单例模式。

回调函数服务绑定的就是一个回调函数

实例对象服务绑定的是一个实例对象

服务解析

当服务已经绑定到服务容器中后,就可以在之后的时间随时获取,也可以称为服务解析。
服务解析需要两个步骤

  • 一个是获取服务容器对象,在Laravel框架中因为所有的功能模块都是通过服务容器实例黏合在一起,所以大部分功能类中都记录服务容器实例的属性,通常为$app属性,也可以通过Facades中的App外观或app()全局函数来获取;
  • 另一个是通过服务容器实现对应服务的解析。

singleton()函数实现的是单例绑定,即程序中如果没有服务名称对应的实例对象,则通过服务容器实例化一个后并进行记录,如果在后续程序中还需要同名的服务时则返回先前创建的服务实例对象。该函数相当于bind()函数的一个特例,即参数$shared值为true的情况。对于bind()函数实现的服务绑定功能,在忽略$shared参数的情况下,即不讨论单例还是普通的服务,可以分为两种情况,
如果参数$concrete为一个回调函数,则直接将回调函数与服务名称$abstract进行绑定;
如果参数$concrete为一个名称,则首先需要通过getClosure()函数创建服务回调函数,然后将该回调函数与服务名称绑定,总之需要实现一个可以生成相应服务实例对象的回调函数与服务名称进行绑定。

首先介绍服务查找过程,即由make()函数实现的功能。该函数需要提供两个参数,分别是$abstract和$parameters,
$abstract可以看做是服务名称,
$parameters是创建实例化对象需要的参数,即一个类实例化时的依赖。

对于服务的查找是根据服务名称$abstract来进行的:

  • 首先通过getAlias()函数来查找服务名称是否有别名,对于服务别名的管理是通过服getAlias()函数来查找服务名称是否有别名,对于服务别名的管理是通过服务容器类中的$aliases数组属性实现的,而内容基本是通过Illuminate\Foundation\Application类中的registerCoreContainerAliases()函数注册的,如一个简单的实例,Illuminate\Contracts\Container\Container抽象类的别名为“app”,如果查找到了别名,将查找该别名对应的服务,如果该抽象类没有别名,则继续进行查找。

  • 然后在服务容器的共享实例数组($instances属性)中查找服务名称的实例,如果查找到则说明该服务名称对应为单例,直接返回先前实例化的对象,否则继续查询。

  • 接下来,会通过getConcrete()获取服务名称的实体,在服务绑定时,一个服务名称一般绑定一个回调函数用于生成实例对象,而这个回调函数就相当于服务名称的实体。这个实体的查找就是通过容器中的$bindings数组属性实现的,如果查找到则返回实体,否则修改服务名称的形式继续下一次的查找。

  • 然后,会通过isBuildable()函数判断服务实体能否创建实例化对象,如果可以则转到下一个步骤,否则继续通过make()函数来查找。

  • 在完成实例对象的创建后,通过isShared()判断该服务是否为单例,如果是需要在共享实例对象数组($instances)中记录。

在通过make()函数查找到服务实体后,会将其传递给build()函数用于对象的创建

  • 如果服务实体就是一个闭包函数,则直接调用该闭包函数完成服务实例化对象的创建
  • 如果服务实体只是一个具体类的类名,则需要通过反射机制来完成实例化对象的创建。
    通过反射机制完成对象实例化的过程,首先是根据将要实例化的类名称获取反射类(ReflectionClass)实例,然后获取该类在实例化过程中的依赖,即构造函数需要的参数,在build()函数中,通过getDependencies()函数来实现依赖的生成,如果在服务解析时提供了相应的参数,即通过$parameters参数提供,则直接使用提供的参数,如果没有提供,则通过服务容器中的resolveNonClass()函数来获取默认参数,或者通过resolveClass()函数来创建,而创建的方式也是通过服务容器,所以服务容器解决依赖注入的问题就是通过这部分代码实现的。在解决了依赖的问题后,可以直接通过反射机制完成服务实例对象的创建。

bind

绑定接口和生成相应的回调函数
如果参数 $concrete 为一个回调函数,则直接将回调函数与服务名称$abstract 进行绑定
如果参数 $concrete 为一个名称,则首先需要通过 getClosure() 函数创建服务回调函数,然后将回调函数与服务名称绑定,总之需要实现一个可以生成相应服务实例对象的回调函数与服务名称进行绑定。

make 服务解析

解析顺序

  • getAlias() 别名
  • $instances 共享实例
  • getConcrete( $abstract ) 获取服务名称实体
  • 找到实体后,如果是回调函数直接返回; 如果是类名则通过反射机制生成实例对象

abstract 服务名称
concrete 服务名称的实体 (对象 or 回调函数)

可以将其分为两个步骤来完成:

  • 一个是完成对应服务的查找
  • 另一个是完成服务的实现,一般是指完成实例化对象的创建。

两个步骤分别由make() 和 build() 函数完成。
首先介绍服务查找过程,即由 make() 函数实现的功能。该函数需要提供两个参数,分别是 $abstract 和 $parameters, $abstract 可以看做是服务名称,而 $parameters 是创建实例化对象需要的参数,即一个类实例化时的依赖。

对于服务的查找是根据服务名称 $abstract 来进行的,首先通过 getAlias() 函数来查找服务名称是否有别名,对于服务别名的管理是通过服务容器类的 $alias 数组属性实现的,而内容基本是通过 Illuminate\Foundation\Application 类中的 registerCoreContainerAliases() 函数注册的,如一个简单的实例, Illuminate\Contracts\Container\Container 抽象类的别名为 "app",如果查到了别名,将查找该别名对应的服务,如果该抽象类没有别名,则继续进行查找。然后在服务容器的共享实例数组( $instances 属性) 中查找服务名称的实例,如果查找到则说明该服务名称对应为单例,直接返回先前实例化的对象,否则继续查询。

接下来,通过 getConcrete() 获取服务名称的实体,在服务绑定时,一个服务名称一般绑定一个回调函数用于生成实例对象,而这个回调函数就相当于服务名称的实体。这个实体的查找就是通过容器中的 $bindings 数组属性实现的,如果查找到则返回实体,否则修改服务名称的形式继续下一次的查找。然后通过 isBuildable() 函数判断服务实体能否创建实例化对象,如果可以则转到下一个步骤,否则继续转到make() 函数来查找。在完成实例化对象的创建后,通过 isShared() 判断该服务是否为单例,如果是需要在共享实例函数组 ($instances) 中记录。

在通过 make() 函数查找到服务实体后,会将其传递给 build() 函数用于对象的创建,如果服务实体就是一个闭包函数,则直接调用该闭包函数完成服务实例化对象的创建,如果服务实体只是一个具体类的类名,则需要通过反射机制来完成实例化对象的创建。通过反射机制完成对象实例化的过程,首先是根据将要实例化的类名称获取反射类(ReflectionClass)实例,然后获取该类在实例化过程中的依赖,即构造函数需要的参数,在 build() 函数中,通过getDependencies() 函数来实现依赖的生成,如果在服务解析时提供了相应的参数,即通过 $parameters 参数提供,则直接使用提供的参数,如果没有提供,则通过服务容器中的 resoleveNonClass() 函数来获取默认参数,或者通过 resolveClass() 函数来创建,而创建的方式也是通过服务容器,所以服务容器解决依赖注入的问题就是通过这部分代码实现的。在解决了依赖的问题后,可以直接通过反射机制完成服务实例对象的创建。

build

反射机制实例化类

$reflector = new ReflectionClass( $concrete );
$constructor = $reflector->getConstructor();
$dependencies = $constructor->getDepencies();


# 解决通过反射机制实例化类时的依赖
$instances = $this->getDependencies( $dependencies );
return $reflector->newInstanceArgs( $instances );
  • 反射机制是指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。
  • 反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。 其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言。

实例化对象(对象的创建)
如果服务实体就是一个闭包函数,则直接调用该闭包函数完成服务实例化对象的创建
如果服务实体只是一个具体类的类名,则需要通过反射机制来完成实例化对象的创建。

在解决了依赖的问题后,可以直接通过反射机制完成服务实例对象的创建。

getClosure($abstract, $concrete);
    }
    $this->bindings[$abstract] = compact('concrete', 'shared');
  }
  
  //默认生成实例的回调函数
  protected function getClosure($abstract, $concrete)
  {
    //生成实例的回调函数, $c一般为IoC容器对象, 在调用回调生成实例时提供
    //即build函数中的 $concrete($this)
    return function($c) use ($abstract, $concrete)
    {
      $method = ($abstract == $concrete) ? 'build' : 'make';
  
      //调用的是容器的build或make方法生成实例
      return $c->$method($concrete);
    };
  }

  //生成实例对象, 首先解决接口和要实例化类之间的依赖关系
  public function make($abstract)
  {
    $concrete = $this->getConcrete($abstract);
    
    if ($this->isBuildable($concrete, $abstract)) {
      $object = $this->build($concrete);
    }
    else
    {
      $object = $this->make($concrete);
    }

    return $object;
  }
  
  protected function isBuildable($concrete, $abstract)
  {
    return $concrete === $abstract || $concrete instanceof Closure;
  }

  //获取绑定的回调函数
  protected function getConcrete($abstract)
  {
    if ( ! isset($this->bindings[$abstract]))
    {       
      return $abstract;
    }

    return $this->bindings[$abstract]['concrete'];
  }

  //实例化对象
  public function build($concrete)
  {
    if ($concrete instanceof Closure){
      return $concrete($this);
    }

    $reflector = new ReflectionClass($concrete);

    if ( ! $reflector->isInstantiable())
    {
      echo $message = "Target [$concrete] is not instantiable.";
    }

    $constructor = $reflector->getConstructor();

    if (is_null($constructor))
    {
      return new $concrete;
    }

    $dependencies = $constructor->getParameters();
    $instances = $this->getDependencies($dependencies);

    return $reflector->newInstanceArgs($instances);
  }

  //解决通过反射机制实例化对象时的依赖
  protected function getDependencies($parameters)
  {
    $dependencies = [];
    foreach ($parameters as $parameter)
    {
      $dependency = $parameter->getClass();
      if (is_null($dependency))
      {
        $dependencies[] = NULL;
      }
      else
      {
        $dependencies[] = $this->resolveClass($parameter);
      }
    }

    return (array) $dependencies;
  }

  protected function resolveClass(ReflectionParameter $parameter)
  {
    return $this->make($parameter->getClass()->name);
  }
}

class Traveller
{
  protected $tool;
  
  public function __construct(Visit $tool)
  {
    $this->tool = $tool;
  }
  
  public function visitTibet()
  {
    $this->tool->go();
  }
}

interface Visit
{
  public function go();
}

class Train implements Visit
{
  public function go()
  {
    echo "train. 
"; } } $app = new Container(); $app->bind("Visit", "Train"); $app->bind("traveller", "Traveller"); $tra = $app->make("traveller"); $tra->visitTibet();

Laravel学习笔记之IoC Container实例化源码解析

PHP 反射机制Reflection

Inversion of Control – The Hollywood Principle

你可能感兴趣的:(【lara】IoC 模式 - 服务容器)