ホームページ >PHPフレームワーク >Laravel >Laravelの詳しい解説—IOCコンテナ

Laravelの詳しい解説—IOCコンテナ

藏色散人
藏色散人転載
2020-12-21 09:13:352346ブラウズ

#Laravel Framework の次のチュートリアルコラムでは、Laravel-IOC コンテナについて詳しく説明しています。

Laravelの詳しい解説—IOCコンテナ

1. 依存関係

IOC (コントローラーの反転) は、制御モードの反転と呼ばれ、(依存性注入) 依存性注入モードとも呼ばれます。 。依存関係注入の概念を理解するには、まずどの依存関係を理解し​​ます。

//支付宝支付
class Alipay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by alipay';
      }
}
//微信支付
class Wechatpay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by wechatpay';
      }
}
//银联支付
class Unionpay{
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by unionpay';
      }
}

//支付账单
class PayBill {

      private $payMethod;

      public function __construct( )
      {
          $this->payMethod= new Alipay ();
      }

      public function  payMyBill()
      {
           $this->payMethod->pay();
      }
}


$pb = new PayBill ();
$pb->payMyBill();

上記のコードを通じて、PayBill クラスのインスタンスを作成するとき、PayBill のコンストラクターには { $this->payMethod = が含まれることがわかります。 new Alipay(); }、つまりAlipayというクラスがインスタンス化されますが、このとき依存関係が生成されますので、Alipayで支払いたい場合は、まずAlipayのインスタンスを取得する必要があることが分かります。 Alipay の機能サポートを取得するためです。新しいキーワードの使用を終了すると、Alipay のインスタンスを取得したため、依存関係は実際には解決されています。


実は、ioc の概念を知る前に、私のコードのほとんどはこのパターンです~ _ ~. これの何が問題ですか? 簡単に言うと、たとえば、Alipay の代わりに WeChat を使用したい場合はどうすればよいですか? できることは、 Payment のコンストラクター。コードは WeChat 支払い Wechatpay をインスタンス化します。

プログラムがそれほど大きくない場合は何も感じないかもしれませんが、コードが非常に複雑で巨大な場合、ニーズが頻繁に変わる場合は、変更します。コードが非常に面倒になります。したがって、ioc のアイデアは、Payment クラスの依存関係をインスタンス化して解決するために new を使用するのではなく、それを外部の責任に変えることです。簡単に言うと、内部的には新しいステップはなく、支払いは次のように行うことができます。 Example.

2. 依存関係の注入

依存関係の意味はわかったので、依存関係の注入とは何を意味するのでしょうか?上記のコード

//支付类接口
interface Pay
{
    public function pay();
}


//支付宝支付
class Alipay implements Pay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by alipay';
      }
}
//微信支付
class Wechatpay implements Pay  {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by wechatpay';
      }
}
//银联支付
class Unionpay implements Pay  {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by unionpay';
      }
}

//付款
class PayBill {

      private $payMethod;

      public function __construct( Pay $payMethod)
      {
          $this->payMethod= $payMethod;
      }

      public function  payMyBill()
      {
           $this->payMethod->pay();
      }
}

//生成依赖
$payMethod =  new Alipay();
//注入依赖
$pb = new PayBill( $payMethod );
$pb->payMyBill();

aboveを展開してみましょう。前のコードと比較して、Pay インターフェイスを追加し、すべての支払いメソッドがこのインターフェイスを継承し、Pay 関数を実装します。なぜインターフェイスが使用されるのか疑問に思われるかもしれませんが、これについては後ほど説明します。

#PayBill をインスタンス化するときは、最初に Alipay をインスタンス化します。このステップは依存関係を生成することです。次に、この依存関係を PayBill インスタンスに注入する必要があります。コードを通して、次のことがわかります。 { $pb = new PayBill( payMethod ); }, コンストラクターを通じてこの依存関係を PayBill に注入しました。このようにして、PayBill インスタンス $pb は Alipay で支払う機能を備えています。

コンストラクターを通じてクラス Alipay のインスタンスを注入して、 class PayBill. ここでのインジェクションは自動ではなく手動インジェクションです。Laravel フレームワークの実装は自動インジェクションです。

3. リフレクション

IOC コンテナを紹介する前に、まずリフレクションの概念を理解しましょう(リフレクション), IOC コンテナもリフレクションを通じて実装されるため、リフレクションの意味を説明するためにインターネットから段落をコピーしました

「リフレクション」とは、PHP の実行状態で PHP プログラムの分析を拡張することを指します。クラス、メソッド、プロパティ、パラメータなどのコメントを含む詳細情報をエクスポートまたは抽出する この情報を動的に取得し、オブジェクトのメソッドを動的に呼び出す機能をリフレクションAPIと呼びます リフレクションはオブジェクト内のメタモデルを操作するためのAPIです指向パラダイム。これは非常に強力で、複雑でスケーラブルなアプリケーションの構築に役立ちます。その用途には、プラグインの自動ロード、ドキュメントの自動生成、さらには PHP 言語の拡張にも使用できます。"

Give a簡単な例

class B{

}


class A {

    public function __construct(B $args)
    {
    }

    public function dosomething()
    {
        echo 'Hello world';
    }
}

//建立class A 的反射
$reflection = new ReflectionClass('A');

$b = new B();

//获取class A 的实例
$instance = $reflection ->newInstanceArgs( [ $b ]);

$instance->dosomething(); //输出 ‘Hellow World’

$constructor = $reflection->getConstructor();//获取class A 的构造函数

$dependencies = $constructor->getParameters();//获取class A 的依赖类

dump($constructor);

dump($dependencies);

ダンピングによって取得された $constructor と $dependency は次のとおりです。

//constructor
ReflectionMethod {#351 
        +name: "__construct" 
        +class: "A" 
        parameters: array:1 [] 
        extra: array:3 [] 
        modifiers: "public"
}

//$dependencies
array:1 [
        0 => ReflectionParameter {#352 
         +name: "args"
          position: 0
          typeHint: "B"
      }
]

上記のコードを通じて、クラス A のコンストラクターとそのコンストラクターが依存するクラスを取得できます。ここでは、「args」という名前の量に依存しており、TypeHint を通じて、それがクラス B 型であることがわかります。リフレクション メカニズムにより、クラスを解析でき、必要な属性、メソッド、コンストラクター、パラメーターを取得できます。クラス内のコンストラクターによって。これだけで Laravel の IOC コンテナが実現できます。

4.IOC コンテナ

次に、Laravel の IOC サービス コンテナの概念を紹介します。 Laravel のコア全体であり、システム全体の機能とサービスの設定と呼び出しを提供します。コンテナとは、文字通り、冷蔵庫などの物を保持するものです。冷蔵庫の中身が必要なときは、単に取り出すことができますサービスコンテナ プログラムの実行開始時に、必要なサービスをコンテナに入れたり登録(バインド)したりして、必要なときに取り出し(メイク)するだけ、というふうにも理解できます。上記のバインドとメイクは、登録と削除の 2 つのアクションです。

5. IOC 容器代码

好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 当然小伙伴完全可以试着运行一下这段代码,然后调试一下,这样会更有助于理解.

<?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(&#39;concrete&#39;, &#39;shared&#39;);
    }

    //默认生成实例的回调函数
    protected function getClosure($abstract, $concrete) {
        
        return function($c) use ($abstract, $concrete) {
            $method = ($abstract == $concrete) ? &#39;build&#39; : &#39;make&#39;;
            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][&#39;concrete&#39;];
    }

    //实例化对象
    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);
    }

}

上面的代码就生成了一个容器,下面是如何使用容器

$app = new Container();
$app->bind("Pay", "Alipay");//Pay 为接口, Alipay 是 class Alipay
$app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill可以当做是Class PayBill 的服务别名

//通过字符解析,或得到了Class PayBill 的实例
$paybill = $app->make("tryToPayMyBill"); 

//因为之前已经把Pay 接口绑定为了 Alipay,所以调用pay 方法的话会显示 &#39;pay bill by alipay &#39;
$paybill->payMyBill();

当我们实例化一个Container得到 $app 后, 我们就可以向其中填充东西了

$app->bind("Pay", "Alipay");
$app->bind("tryToPayMyBill", "PayBill");

当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,我们来看下

array:2 [
 "App\Http\Controllers\Pay" => array:2 [
     "concrete" => Closure {#355 
       class: "App\Http\Controllers\Container" 
       this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
       parameters: array:1 [
         "$c" => []
       ] 
       use: array:2 [
         "$abstract" => "App\Http\Controllers\Pay"
        "$concrete" => "App\Http\Controllers\Alipay"
       ] 
       file: "C:\project\test\app\Http\Controllers\IOCController.php" line:       "119 to 122"
   } 
   "shared" => false 
 ]

"tryToPayMyBill" => array:2 [
     "concrete" => Closure {#359 
         class: "App\Http\Controllers\Container" 
         this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
         parameters: array:1 [
               "$c" => []
         ] 
         use: array:2 [
               "$abstract" => "tryToPayMyBill" 
               "$concrete" => "\App\Http\Controllers\PayBill"
         ] 
         file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"
   } 
     "shared" => false 
 ]
]

当执行 $paybill = $app->make(“tryToPayMyBill”); 的时候, 程序就会用make方法通过闭包函数的回调开始解析了.

解析’tryToPayBill’ 这个字符串, 程序通过闭包函数 和build方法会得到 ‘PayBill’ 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 然后程序还会以类似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = ‘PayBill’. 这个时候反射就派上了用场, 大家有没有发现,PayBill 不就是 class PayBill 吗? 然后在通过反射的方法ReflectionClass(‘PayBill’) 获取PayBill 的实例. 之后通过getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依赖

//$constructor = $reflector->getConstructor();
ReflectionMethod {#374 
    +name: "__construct" 
    +class: "App\Http\Controllers\PayBill" 
    parameters: array:1 [
          "$payMethod" => ReflectionParameter {#371 
              +name: "payMethod" 
              position: 0 typeHint: "App\Http\Controllers\Pay"
          }
    ]
     extra: array:3 [
          "file" => "C:\project\test\app\Http\Controllers\IOCController.php"
          "line" => "83 to 86" 
          "isUserDefined" => true 
      ] 
    modifiers: "public"
}


//$dependencies = $constructor->getParameters();
array:1 [
    0 => ReflectionParameter {#370 
        +name: "payMethod" 
        position: 0 
        typeHint: "App\Http\Controllers\Pay"
        }
]

接着,我们知道了有’Pay’这个依赖之后呢,我们要做的就是解决这个依赖,通过 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,还有之前的绑定$app->bind(“Pay”, “Alipay”); 在build 一次的时候,通过 return new $concrete;到这里我们得到了这个Alipay 的实例

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

到这里我们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 Alipay. 到这里还没结束

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

上面的$instances 数组只有一个element 那就是 Alipay 实例

  array:1 [0 =>Alipay
      {#380}
 ]

最终通过 newInstanceArgs() 方法, 我们获取到了 PayBill 的实例。

 return $reflector->newInstanceArgs($instances);

到这里整个流程就结束了, 我们通过 bind 方式绑定了一些依赖关系, 然后通过make 方法 获取到到我们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.

好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.

$app->bind("Pay", "Wechatpay");
$app->bind("tryToPayMyBill", "PayBill");
$paybill = $app->make("tryToPayMyBill"); 
$paybill->payMyBill();

是不是很简单呢, 只要把绑定从’Alipay’ 改成 ‘Wechatpay’ 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.

好了,到这里不知道小伙伴有没有理解呢,我建议大家可以试着运行下这些代码, 这样理解起来会更快.同时推荐大家去看看 《laravel 框架关键技术解析》这本书,写的还是不错的.

以上がLaravelの詳しい解説—IOCコンテナの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。