Maison  >  Article  >  cadre php  >  Explication détaillée de Laravel — Conteneur IOC

Explication détaillée de Laravel — Conteneur IOC

藏色散人
藏色散人avant
2020-12-21 09:13:352289parcourir

Ce qui suit est une explication détaillée du conteneur Laravel-IOC de la colonne du didacticiel Laravel framework J'espère que cela sera utile aux amis dans le besoin !

Explication détaillée de Laravel — Conteneur IOC

1. Dépendance

IOC (inversion du contrôleur) est appelé inversion du mode de contrôle, également connu sous le nom de mode d'injection de dépendance (injection de dépendances). . Pour comprendre le concept d'injection de dépendances, nous comprenons d'abord quelles dépendances

//支付宝支付
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();

À partir du code ci-dessus, nous savons que lorsque nous créons une instance de la classe PayBill, le constructeur de PayBill contient { $this->payMethod = new Alipay (); c'est-à-dire qu'une classe Alipay est instanciée. À ce stade, on peut comprendre que lorsque je veux payer avec Alipay, je dois d'abord obtenir une instance d'Alipay, ou comprendre In. afin d'obtenir le support fonctionnel d'Alipay. Lorsque nous avons fini d'utiliser le nouveau mot-clé, la dépendance a effectivement été résolue car nous avons obtenu une instance d'Alipay.

En fait, avant de connaître le concept d'ioc, mon La plupart du code est dans ce modèle ~ _ ~. Quel est le problème avec ça ? Pour le dire simplement, par exemple, que dois-je faire lorsque je veux utiliser WeChat au lieu d'Alipay ? de paiement. Le code instancie un paiement WeChat Wechatpay.

Si notre programme n'est pas très gros, nous pouvons ne rien ressentir, mais lorsque votre code est très complexe et énorme, si nos besoins changent fréquemment, alors modifiez le le code devient très gênant. Par conséquent, l'idée d'ioc n'est pas d'utiliser new pour instancier et résoudre les dépendances dans la classe Paiement, mais d'en faire une responsabilité externe. Pour faire simple, il n'y a pas de nouvelle étape en interne, et le paiement peut l'être. obtenu par injection de dépendances. Exemple.

2. Injection de dépendances

Nous savons ce que signifie la dépendance, mais que signifie l'injection de dépendances

//支付类接口
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();

ci-dessus ? dans le code, par rapport à la précédente, nous ajoutons une interface Pay, puis tous les modes de paiement héritent de cette interface et implémentent la fonction pay. Vous pouvez vous demander pourquoi une interface est utilisée, dont nous parlerons plus tard

Lorsque nous instancions PayBill, nous instancions d'abord un Alipay. Cette étape consiste à générer la dépendance. Ensuite, nous devons injecter cette dépendance dans l'instance PayBill. Grâce au code, nous pouvons voir { $pb = new PayBill(. payMethod ); }, nous avons injecté cette dépendance dans PayBill via le constructeur. De cette façon, l'instance PayBill $pb a la possibilité de payer avec Alipay.

Injectez l'instance de la classe Alipay via le constructeur pour instancier un classe PayBill. Notre injection ici est une injection manuelle, pas automatique. L'implémentation du framework Laravel est une injection automatique.

3. Réflexion

Avant d'introduire le conteneur IOC, comprenons d'abord le concept de réflexion, parce que le conteneur IOC est également implémenté par réflexion. J'ai copié un paragraphe d'Internet pour expliquer ce que signifie la réflexion

"Réflexion" fait référence à l'extension de l'analyse des programmes PHP dans l'état d'exécution de PHP, à l'exportation ou à l'extraction de détails. des informations sur les classes, les méthodes, les propriétés, les paramètres, etc., y compris les commentaires. Ces informations obtenues dynamiquement et la méthode d'appel dynamique des objets. La fonction est appelée API de réflexion. La réflexion est une API permettant de manipuler le méta-modèle dans l'orientation objet. paradigme. Il est très puissant et peut nous aider à construire des applications complexes et évolutives. Ses utilisations incluent : le chargement automatique de plug-ins, la génération automatique de documents, et même l'utilisation d'étendre le langage PHP"

Un exemple simple<.>
class B{

}


class A {

    public function __construct(B $args)
    {
    }

    public function dosomething()
    {
        echo &#39;Hello world&#39;;
    }
}

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

$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);

Le $constructeur et les $dépendances obtenues par dumping sont les suivants

//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"
      }
]

Grâce au code ci-dessus, nous pouvons obtenir le constructeur de la classe A, ainsi que les classes dont dépend le constructeur on. Ici, nous nous appuyons sur une quantité nommée 'args', et grâce à TypeHint nous pouvons savoir qu'elle est de type Classe B ; le mécanisme de réflexion me permet d'analyser une classe, vous pouvez obtenir les attributs, méthodes, constructeurs et paramètres requis. par le constructeur dans une classe. Ce n'est qu'avec cela que nous pouvons réaliser le conteneur IOC de Laravel


4. Conteneur IOC

Ensuite, nous présenterons le concept du conteneur de service IOC de Laravel. est l'ensemble Le noyau de laravel, il fournit la configuration et l'invocation de l'ensemble des fonctions et des services du système. Un conteneur est littéralement un objet qui contient des objets, comme un réfrigérateur. Lorsque nous avons besoin du contenu du réfrigérateur, nous pouvons simplement le prendre. directement à partir de celui-ci. Conteneur de services Cela peut également être compris de cette façon. Lorsque le programme démarre, nous mettons ou enregistrons (lions) certains services dont nous avons besoin dans le conteneur, et lorsque j'en ai besoin, nous les retirons simplement (make). Le bind mentionné ci-dessus et make sont les deux actions d'enregistrement et de suppression.

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 框架关键技术解析》这本书,写的还是不错的.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer