【lara】IoC 模式 - 服務容器

服務容器工作示意圖

$bindings用于存儲提供服務的回調(diào)函數(shù)
$instances用于存儲程序中共享的實例,也可以稱為單例。

服務容器的生成

bootstrap/app.php 文件實現(xiàn)了服務容器的實例化,同時綁定了核心處理類。至此,已經(jīng)獲得了一個全局的服務容器實例,即$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();

服務綁定

綁定實際上可以簡單地理解為一個關(guān)鍵字和一個服務進行綁定,可以簡單看做是一種鍵值對形式,即一個“key”對應一個服務。對于綁定服務的不同,需要服務容器中不同的綁定函數(shù)來實現(xiàn),主要包括回調(diào)函數(shù)服務綁定和實例對象服務綁定。

  • 普通綁定
    普通綁定每次生成該服務的實例對象時都會生成一個新的實例對象,也就是說在程序的生命周期中,可以同時生成很多個這種實例對象。
  • 單例綁定
    單例綁定在生成一個實例對象后,如果再次生成就會返回第一次生成的實例對象,也就是說在程序的生命周期中,只能生成一個這樣的實例對象,如果想使用就會獲取之前生成的,也就是設計模式中的單例模式。

回調(diào)函數(shù)服務綁定的就是一個回調(diào)函數(shù)

實例對象服務綁定的是一個實例對象

服務解析

當服務已經(jīng)綁定到服務容器中后,就可以在之后的時間隨時獲取,也可以稱為服務解析。
服務解析需要兩個步驟

  • 一個是獲取服務容器對象,在Laravel框架中因為所有的功能模塊都是通過服務容器實例黏合在一起,所以大部分功能類中都記錄服務容器實例的屬性,通常為$app屬性,也可以通過Facades中的App外觀或app()全局函數(shù)來獲取;
  • 另一個是通過服務容器實現(xiàn)對應服務的解析。

singleton()函數(shù)實現(xiàn)的是單例綁定,即程序中如果沒有服務名稱對應的實例對象,則通過服務容器實例化一個后并進行記錄,如果在后續(xù)程序中還需要同名的服務時則返回先前創(chuàng)建的服務實例對象。該函數(shù)相當于bind()函數(shù)的一個特例,即參數(shù)$shared值為true的情況。對于bind()函數(shù)實現(xiàn)的服務綁定功能,在忽略$shared參數(shù)的情況下,即不討論單例還是普通的服務,可以分為兩種情況,
如果參數(shù)$concrete為一個回調(diào)函數(shù),則直接將回調(diào)函數(shù)與服務名稱$abstract進行綁定;
如果參數(shù)$concrete為一個名稱,則首先需要通過getClosure()函數(shù)創(chuàng)建服務回調(diào)函數(shù),然后將該回調(diào)函數(shù)與服務名稱綁定,總之需要實現(xiàn)一個可以生成相應服務實例對象的回調(diào)函數(shù)與服務名稱進行綁定。

首先介紹服務查找過程,即由make()函數(shù)實現(xiàn)的功能。該函數(shù)需要提供兩個參數(shù),分別是$abstract和$parameters,
$abstract可以看做是服務名稱,
$parameters是創(chuàng)建實例化對象需要的參數(shù),即一個類實例化時的依賴。

對于服務的查找是根據(jù)服務名稱$abstract來進行的:

  • 首先通過getAlias()函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服getAlias()函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服務容器類中的$aliases數(shù)組屬性實現(xiàn)的,而內(nèi)容基本是通過Illuminate\Foundation\Application類中的registerCoreContainerAliases()函數(shù)注冊的,如一個簡單的實例,Illuminate\Contracts\Container\Container抽象類的別名為“app”,如果查找到了別名,將查找該別名對應的服務,如果該抽象類沒有別名,則繼續(xù)進行查找。

  • 然后在服務容器的共享實例數(shù)組($instances屬性)中查找服務名稱的實例,如果查找到則說明該服務名稱對應為單例,直接返回先前實例化的對象,否則繼續(xù)查詢。

  • 接下來,會通過getConcrete()獲取服務名稱的實體,在服務綁定時,一個服務名稱一般綁定一個回調(diào)函數(shù)用于生成實例對象,而這個回調(diào)函數(shù)就相當于服務名稱的實體。這個實體的查找就是通過容器中的$bindings數(shù)組屬性實現(xiàn)的,如果查找到則返回實體,否則修改服務名稱的形式繼續(xù)下一次的查找。

  • 然后,會通過isBuildable()函數(shù)判斷服務實體能否創(chuàng)建實例化對象,如果可以則轉(zhuǎn)到下一個步驟,否則繼續(xù)通過make()函數(shù)來查找。

  • 在完成實例對象的創(chuàng)建后,通過isShared()判斷該服務是否為單例,如果是需要在共享實例對象數(shù)組($instances)中記錄。

在通過make()函數(shù)查找到服務實體后,會將其傳遞給build()函數(shù)用于對象的創(chuàng)建

  • 如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建
  • 如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。
    通過反射機制完成對象實例化的過程,首先是根據(jù)將要實例化的類名稱獲取反射類(ReflectionClass)實例,然后獲取該類在實例化過程中的依賴,即構(gòu)造函數(shù)需要的參數(shù),在build()函數(shù)中,通過getDependencies()函數(shù)來實現(xiàn)依賴的生成,如果在服務解析時提供了相應的參數(shù),即通過$parameters參數(shù)提供,則直接使用提供的參數(shù),如果沒有提供,則通過服務容器中的resolveNonClass()函數(shù)來獲取默認參數(shù),或者通過resolveClass()函數(shù)來創(chuàng)建,而創(chuàng)建的方式也是通過服務容器,所以服務容器解決依賴注入的問題就是通過這部分代碼實現(xiàn)的。在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。

bind

綁定接口和生成相應的回調(diào)函數(shù)
如果參數(shù) $concrete 為一個回調(diào)函數(shù),則直接將回調(diào)函數(shù)與服務名稱$abstract 進行綁定
如果參數(shù) $concrete 為一個名稱,則首先需要通過 getClosure() 函數(shù)創(chuàng)建服務回調(diào)函數(shù),然后將回調(diào)函數(shù)與服務名稱綁定,總之需要實現(xiàn)一個可以生成相應服務實例對象的回調(diào)函數(shù)與服務名稱進行綁定。

make 服務解析

解析順序

  • getAlias() 別名
  • $instances 共享實例
  • getConcrete( $abstract ) 獲取服務名稱實體
  • 找到實體后,如果是回調(diào)函數(shù)直接返回; 如果是類名則通過反射機制生成實例對象

abstract 服務名稱
concrete 服務名稱的實體 (對象 or 回調(diào)函數(shù))

可以將其分為兩個步驟來完成:

  • 一個是完成對應服務的查找
  • 另一個是完成服務的實現(xiàn),一般是指完成實例化對象的創(chuàng)建。

兩個步驟分別由make() 和 build() 函數(shù)完成。
首先介紹服務查找過程,即由 make() 函數(shù)實現(xiàn)的功能。該函數(shù)需要提供兩個參數(shù),分別是 $abstract 和 $parameters, $abstract 可以看做是服務名稱,而 $parameters 是創(chuàng)建實例化對象需要的參數(shù),即一個類實例化時的依賴。

對于服務的查找是根據(jù)服務名稱 $abstract 來進行的,首先通過 getAlias() 函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服務容器類的 $alias 數(shù)組屬性實現(xiàn)的,而內(nèi)容基本是通過 Illuminate\Foundation\Application 類中的 registerCoreContainerAliases() 函數(shù)注冊的,如一個簡單的實例, Illuminate\Contracts\Container\Container 抽象類的別名為 "app",如果查到了別名,將查找該別名對應的服務,如果該抽象類沒有別名,則繼續(xù)進行查找。然后在服務容器的共享實例數(shù)組( $instances 屬性) 中查找服務名稱的實例,如果查找到則說明該服務名稱對應為單例,直接返回先前實例化的對象,否則繼續(xù)查詢。

接下來,通過 getConcrete() 獲取服務名稱的實體,在服務綁定時,一個服務名稱一般綁定一個回調(diào)函數(shù)用于生成實例對象,而這個回調(diào)函數(shù)就相當于服務名稱的實體。這個實體的查找就是通過容器中的 $bindings 數(shù)組屬性實現(xiàn)的,如果查找到則返回實體,否則修改服務名稱的形式繼續(xù)下一次的查找。然后通過 isBuildable() 函數(shù)判斷服務實體能否創(chuàng)建實例化對象,如果可以則轉(zhuǎn)到下一個步驟,否則繼續(xù)轉(zhuǎn)到make() 函數(shù)來查找。在完成實例化對象的創(chuàng)建后,通過 isShared() 判斷該服務是否為單例,如果是需要在共享實例函數(shù)組 ($instances) 中記錄。

在通過 make() 函數(shù)查找到服務實體后,會將其傳遞給 build() 函數(shù)用于對象的創(chuàng)建,如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建,如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。通過反射機制完成對象實例化的過程,首先是根據(jù)將要實例化的類名稱獲取反射類(ReflectionClass)實例,然后獲取該類在實例化過程中的依賴,即構(gòu)造函數(shù)需要的參數(shù),在 build() 函數(shù)中,通過getDependencies() 函數(shù)來實現(xiàn)依賴的生成,如果在服務解析時提供了相應的參數(shù),即通過 $parameters 參數(shù)提供,則直接使用提供的參數(shù),如果沒有提供,則通過服務容器中的 resoleveNonClass() 函數(shù)來獲取默認參數(shù),或者通過 resolveClass() 函數(shù)來創(chuàng)建,而創(chuàng)建的方式也是通過服務容器,所以服務容器解決依賴注入的問題就是通過這部分代碼實現(xiàn)的。在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。

build

反射機制實例化類

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


# 解決通過反射機制實例化類時的依賴
$instances = $this->getDependencies( $dependencies );
return $reflector->newInstanceArgs( $instances );
  • 反射機制是指在PHP運行狀態(tài)中,擴展分析PHP程序,導出或提取出關(guān)于類、方法、屬性、參數(shù)等的詳細信息,包括注釋。這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為反射API。
  • 反射是操縱面向?qū)ο蠓缎椭性P偷腁PI,其功能十分強大,可幫助我們構(gòu)建復雜,可擴展的應用。 其用途如:自動加載插件,自動生成文檔,甚至可用來擴充PHP語言。

實例化對象(對象的創(chuàng)建)
如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建
如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。

在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。

<?php

//設計容器類, 容器類裝實例或提供實例的回調(diào)函數(shù)
class Container
{
  //用于裝提供實例的回調(diào)函數(shù), 真正的容器還會裝實例等其他內(nèi)容
  //從而實現(xiàn)單例等高級功能
  protected $bindings = [];
  //綁定接口和生成相應實例的回調(diào)函數(shù)
  public function bind($abstract, $concrete = null, $shared = false)
  {
    if ( ! $concrete instanceof Closure){
    //如果提供的參數(shù)不是回調(diào)函數(shù), 則產(chǎn)生默認的回調(diào)函數(shù)
      $concrete = $this->getClosure($abstract, $concrete);
    }
    $this->bindings[$abstract] = compact('concrete', 'shared');
  }
  
  //默認生成實例的回調(diào)函數(shù)
  protected function getClosure($abstract, $concrete)
  {
    //生成實例的回調(diào)函數(shù), $c一般為IoC容器對象, 在調(diào)用回調(diào)生成實例時提供
    //即build函數(shù)中的 $concrete($this)
    return function($c) use ($abstract, $concrete)
    {
      $method = ($abstract == $concrete) ? 'build' : 'make';
  
      //調(diào)用的是容器的build或make方法生成實例
      return $c->$method($concrete);
    };
  }

  //生成實例對象, 首先解決接口和要實例化類之間的依賴關(guān)系
  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;
  }

  //獲取綁定的回調(diào)函數(shù)
  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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 2.1 我們的理念是:讓別人為你服務 IoC是隨著近年來輕量級容器(Lightweight Container)的...
    好好學習Sun閱讀 2,877評論 0 11
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 16,208評論 2 11
  • 本文面向php語言的laravel框架的用戶,介紹一些laravel框架里面容器管理方面的使用要點。文章很長,但是...
    spacexxxx閱讀 1,292評論 0 1
  • 1.JQuery 基礎 改變web開發(fā)人員創(chuàng)造搞交互性界面的方式。設計者無需花費時間糾纏JS復雜的高級特性。 1....
    LaBaby_閱讀 1,505評論 0 2
  • 服務容器 容器 通俗的理解就是裝東西的物體,常見的一個變量、一個對象等都可以看成一個容器,而服務容器就是提供服務的...
    邱杉的博客閱讀 704評論 0 51

友情鏈接更多精彩內(nèi)容