【lara】IoC 模式 - 服务容器

服务容器工作示意图

$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语言。

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

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

<?php

//设计容器类, 容器类装实例或提供实例的回调函数
class Container
{
  //用于装提供实例的回调函数, 真正的容器还会装实例等其他内容
  //从而实现单例等高级功能
  protected $bindings = [];
  //绑定接口和生成相应实例的回调函数
  public function bind($abstract, $concrete = null, $shared = false)
  {
    if ( ! $concrete instanceof Closure){
    //如果提供的参数不是回调函数, 则产生默认的回调函数
      $concrete = $this->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. <br>";
  }
}
$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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 2.1 我们的理念是:让别人为你服务 IoC是随着近年来轻量级容器(Lightweight Container)的...
    好好学习Sun阅读 2,705评论 0 11
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981阅读 15,895评论 2 11
  • 本文面向php语言的laravel框架的用户,介绍一些laravel框架里面容器管理方面的使用要点。文章很长,但是...
    spacexxxx阅读 1,152评论 0 1
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,330评论 0 2
  • 服务容器 容器 通俗的理解就是装东西的物体,常见的一个变量、一个对象等都可以看成一个容器,而服务容器就是提供服务的...
    邱杉的博客阅读 601评论 0 51