Maison  >  Article  >  développement back-end  >  Une plongée approfondie dans le conteneur IoC de Laravel

Une plongée approfondie dans le conteneur IoC de Laravel

WBOY
WBOYoriginal
2023-09-01 18:49:101317parcourir

深入探索 Laravel 的 IoC 容器

L'Inversion de Contrôle (IoC) est une technique qui permet l'inversion de contrôle par rapport au code procédural classique. Bien entendu, la forme la plus importante d’IoC est l’injection de dépendances (DI). Le conteneur IoC de Laravel est l'une des fonctionnalités de Laravel les plus couramment utilisées, mais probablement la moins comprise.

Il s'agit d'un exemple très simple d'utilisation de l'injection de dépendances pour implémenter l'inversion de contrôle.

<?php

class JeepWrangler
{
    public function __construct(Petrol $fuel)
    {
        $this->fuel = $fuel;
    }
	
    public function refuel($litres)
    {
	    return $litres * $this->fuel->getPrice();
    }
}

class Petrol
{
    public function getPrice()
    {
    	return 130.7;
    }
}

$petrol = new Petrol;
$car = new JeepWrangler($petrol);

$cost = $car->refuel(60);

En utilisant l'injection de constructeur, nous savons maintenant d'où vient Petrol 实例的创建委托给调用者本身,从而实现控制反转。我们的 JeepWrangler 不需要知道 Petrol et nous l'obtenons simplement.

Alors, qu'est-ce que tout cela a à voir avec Laravel ? Beaucoup en fait. Si vous ne le saviez pas, Laravel est en fait un conteneur IoC. Comme on peut s'y attendre, un conteneur est un objet qui contient des choses. Le conteneur IoC de Laravel est utilisé pour contenir de nombreuses liaisons différentes. Tout ce que vous faites dans Laravel interagira avec le conteneur IoC à un moment donné. Cette interaction prend généralement la forme d’une résolution de liaison.

Si vous ouvrez un fournisseur de services Laravel existant, vous verrez probablement quelque chose de similaire dans la méthode register (l'exemple est très simplifié).

$this->app['router'] = $this->app->share(function($app) {
    return new Router;
});

Il s’agit d’une reliure très, très basique. Il se compose du nom lié (une instance de router)和解析器(闭包)组成。当从容器中解析该绑定时,我们将返回一个 Router.

Laravel regroupera souvent des noms de liaison similaires, tels que session 和 session.store.

Pour résoudre une liaison, nous pouvons appeler la méthode directement, ou utiliser la méthode make sur le conteneur.

$router = $this->app->make('router');

C'est ce que font les conteneurs dans leur forme la plus élémentaire. Mais, comme la plupart des choses dans Laravel, il ne s'agit pas seulement de lier et de résoudre des classes.

Liaisons partagées et non partagées

Si vous parcourez plusieurs fournisseurs de services Laravel, vous remarquerez que la plupart des liaisons sont définies de la même manière que les exemples précédents. C'est reparti :

$this->app['router'] = $this->app->share(function($app) {
    return new Router;
});

Cette liaison utilise la méthode share 方法。 Laravel 使用静态变量来存储先前解析的值,并在再次解析绑定时简单地重用该值。这基本上就是 share sur le conteneur.

$this->app['router'] = function($app) {
    static $router;
     
    if (is_null($router)) {
        $router = new Router;
    }
     
    return $router;
     
};

Une autre façon d'écrire est d'utiliser la méthode bindShared.

$this->app->bindShared('router', function($app) {
    return new Router;
});

Vous pouvez également utiliser les méthodes singleton et instance pour implémenter la liaison partagée. Alors, s’ils atteignent tous les deux le même objectif, quelle est la différence ? Pas beaucoup en fait. Personnellement, je préfère utiliser la méthode singleton 和 instance 方法来实现共享绑定。那么,如果它们都实现了相同的目标,那么有什么区别呢?实际上并不是很多。我个人更喜欢使用 bindShared.

Reliure conditionnelle

Parfois, vous souhaiterez peut-être lier quelque chose à un conteneur, mais seulement s'il n'a jamais été lié auparavant. Il existe plusieurs façons de résoudre ce problème, mais la plus simple est d'utiliser la méthode bindIf.

$this->app->bindIf('router', function($app) {
    return new ImprovedRouter;
});

La liaison router ne sera liée au conteneur que s'il n'existe pas déjà. La seule chose à noter ici est la manière dont les liaisons conditionnelles sont partagées. Pour ce faire, vous devez fournir un troisième paramètre à la méthode router 绑定尚不存在时,才会绑定到容器。这里唯一需要注意的是如何共享条件绑定。为此,您需要向 bindIf 方法提供第三个参数,其值为 true avec la valeur true.

Résolution automatique des dépendances

L'une des fonctionnalités les plus couramment utilisées du conteneur IoC est sa capacité à résoudre automatiquement les dépendances des classes non liées. Qu'est-ce que cela signifie? Premièrement, nous n'avons pas réellement besoin de lier quelque chose au conteneur pour résoudre l'instance. Nous pouvons simplement make créer une instance de presque n'importe quelle classe.

class Petrol
{
    public function getPrice()
    {
        return 130.7;
    }
}

// In our service provider...
$petrol = $this->app->make('Petrol');

Le conteneur instanciera la classe Petrol pour nous. La meilleure partie est que cela résoudra également les dépendances du constructeur pour nous.

class JeepWrangler
{
    public function __construct(Petrol $fuel)
    {
        $this->fuel = $fuel;
    }
	
    public function refuel($litres)
    {
        return $litres * $this->fuel->getPrice();
    }
    
}

// In our service provider...
$car = $this->app->make('JeepWrangler');

La première chose que fait le conteneur est de vérifier les dépendances de la classe JeepWrangler. Il tentera ensuite de résoudre ces dépendances. Ainsi, parce que notre type JeepWrangler fait allusion à la classe JeepWrangler 类的依赖项。然后它将尝试解决这些依赖关系。因此,因为我们的 JeepWrangler 类型提示了 Petrol, le conteneur le résoudra automatiquement et l'injectera en tant que dépendance.

Les conteneurs ne peuvent pas injecter automatiquement des dépendances sans indication de type. Ainsi, si l'une de vos dépendances est un tableau, vous devez alors l'instancier manuellement ou spécifier des valeurs par défaut pour les paramètres.

Mise en œuvre de la liaison

Le fait que Laravel résolve automatiquement les dépendances est formidable et simplifie le processus d'instanciation manuelle des classes. Cependant, vous souhaitez parfois injecter une implémentation spécifique, notamment lors de l'utilisation d'interfaces. Ceci est facilement réalisé en utilisant le nom complet de la classe comme liaison. Pour démontrer cela, nous utiliserons une nouvelle interface appelée Fuel.

interface Fuel
{
    public function getPrice();
}

现在我们的 JeepWrangler 类可以对接口进行类型提示,并且我们将确保我们的 Petrol 类实现该接口。

class JeepWrangler
{
    public function __construct(Fuel $fuel)
    {
        $this->fuel = $fuel;
    }
	
    public function refuel($litres)
    {
        return $litres * $this->fuel->getPrice();
    }
}

class Petrol implements Fuel
{
    public function getPrice()
    {
        return 130.7;
    }
}

现在,我们可以将 Fuel 接口绑定到容器,并让它解析 Petrol 的新实例。

$this->app->bind('Fuel', 'Petrol');

// Or, we could instantiate it ourselves.
$this->app->bind('Fuel', function ($app) {
    return new Petrol;
});

现在,当我们创建 JeepWrangler 的新实例时,容器会看到它请求 Fuel ,并且它会知道自动注入 Petrol

这也使得更换实现变得非常容易,因为我们可以简单地更改容器中的绑定。为了进行演示,我们可能会开始使用优质汽油为汽车加油,这种汽油价格稍贵一些。

class PremiumPetrol implements Fuel
{
    public function getPrice()
    {
        return 144.3;
    }
}

// In our service provider...
$this->app->bind('Fuel', 'PremiumPetrol');

上下文绑定

请注意,上下文绑定仅在 Laravel 5 中可用。

上下文绑定允许您将实现(就像我们上面所做的那样)绑定到特定的类。

abstract class Car
{
    public function __construct(Fuel $fuel)
    {
        $this->fuel = $fuel;
    }

    public function refuel($litres)
    {
        return $litres * $this->fuel->getPrice();
    }
}

然后,我们将创建一个新的 NissanPatrol 类来扩展抽象类,并且我们将更新 JeepWrangler 来扩展它。

class JeepWrangler extends Car
{
    //
}

class NissanPatrol extends Car
{
    //
}

最后,我们将创建一个新的 Diesel 类,该类实现 Fuel 接口。

class Diesel implements Fuel
{
    public function getPrice()
    {
        return 135.3;
    }
}

现在,我们的吉普牧马人将使用汽油加油,我们的日产途乐将使用柴油加油。如果我们尝试使用与之前相同的方法,将实现绑定到接口,那么这两辆车都会获得相同类型的燃料,这不是我们想要的。

因此,为了确保每辆车都使用正确的燃料加油,我们可以通知容器在每种情况下使用哪种实现。

$this->app->when('JeepWrangler')->needs('Fuel')->give('Petrol');
$this->app->when('NissanPatrol')->needs('Fuel')->give('Diesel');

标记

请注意,标记仅在 Laravel 5 中可用。

能够解析容器中的绑定非常重要。通常,只有知道某些内容如何绑定到容器时,我们才能解决该问题。在 Laravel 5 中,我们现在可以为绑定添加标签,以便开发人员可以轻松解析具有相同标签的所有绑定。

如果您正在开发一个允许其他开发人员构建插件的应用程序,并且您希望能够轻松解析所有这些插件,那么标签将非常有用。

$this->app->tag('awesome.plugin', 'plugin');

// Or an array of tags.

$tags = ['plugin', 'theme'];

$this->app->tag('awesome.plugin', $tags);

现在,要解析给定标记的所有绑定,我们可以使用 tagged 方法。

$plugins = $this->app->tagged('plugin');

foreach ($plugins as $plugin) {
    $plugin->doSomethingFunky();
}

篮板和重新绑定

当您将某些内容多次绑定到同名容器时,称为重新绑定。 Laravel 会注意到你再次绑定了一些东西并会触发反弹。

这里最大的好处是当您开发一个包时,允许其他开发人员通过重新绑定容器中的组件来扩展它。要使用它,我们需要在 Car 摘要上实现 setter 注入。

abstract class Car
{
    public function __construct(Fuel $fuel)
    {
        $this->fuel = $fuel;
    }

    public function refuel($litres)
    {
        return $litres * $this->fuel->getPrice();
    }
    
    public function setFuel(Fuel $fuel)
    {
        $this->fuel = $fuel;
    }
    
}

假设我们将 JeepWrangler 像这样绑定到容器。

$this->app->bindShared('fuel', function ($app) {
    return new Petrol;
});

$this->app->bindShared('car', function ($app) {
	return new JeepWrangler($app['fuel']);
});

这完全没问题,但假设另一位开发人员出现并希望扩展此功能并在汽车中使用优质汽油。因此,他们使用 setFuel 方法将新燃料注入汽车。

$this->app['car']->setFuel(new PremiumPetrol);

在大多数情况下,这可能就是所需要的;但是,如果我们的包变得更加复杂并且 fuel 绑定被注入到其他几个类中怎么办?这将导致其他开发人员必须多次设置他们的新实例。因此,为了解决这个问题,我们可以利用重新绑定:

$this->app->bindShared('car', function ($app) {
    return new JeepWrangler($app->rebinding('fuel', function ($app, $fuel) {
		$app['car']->setFuel($fuel);
	}));
});

重新绑定 方法将立即返回给我们已经绑定的实例,以便我们能够在 JeepWrangler 的构造函数中使用它。提供给 rebinding 方法的闭包接收两个参数,第一个是 IoC 容器,第二个是新绑定。然后,我们可以自己使用 setFuel 方法将新绑定注入到我们的 JeepWrangler 实例中。

剩下的就是其他开发人员只需在容器中重新绑定 fuel 即可。他们的服务提供商可能如下所示:

$this->app->bindShared('fuel', function () {
    return new PremiumPetrol;
});

一旦绑定在容器中反弹,Laravel 将自动触发关联的闭包。在我们的示例中,新的 PremiumPetrol 实例将在我们的 JeepWrangler 实例上设置。

扩展

如果您想将依赖项注入核心绑定之一或由包创建的绑定,那么容器上的 extend 方法是最简单的方法之一。

此方法将解析来自容器的绑定,并以容器和解析的实例作为参数执行闭包。这使您可以轻松解析和注入您自己的绑定,或者简单地实例化一个新类并注入它。

$this->app->extend('car', function ($app, $car) {
    $car->setFuel(new PremiumPetrol);
});

与重新绑定不同,这只会设置对单个绑定的依赖关系。

Laravel 之外的使用

与构成 Laravel 框架的许多 Illuminate 组件一样,Container 可以在 Laravel 之外的独立应用程序中使用。为此,您必须首先将其作为 composer.json 文件中的依赖项。

{
    "require": {
        "illuminate/container": "4.2.*"
   }
}

这将安装容器的最新 4.2 版本。现在,剩下要做的就是实例化一个新容器。

require 'vendor/autoload.php';

$app = new Illuminate\Container\Container;

$app->bindShared('car', function () {
    return new JeepWrangler;
});

在所有组件中,当您需要灵活且功能齐全的 IoC 容器时,这是最容易使用的组件之一。

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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn