Heim >Backend-Entwicklung >PHP-Tutorial >Ein tiefer Einblick in den IoC-Container von Laravel

Ein tiefer Einblick in den IoC-Container von Laravel

WBOY
WBOYOriginal
2023-09-01 18:49:101377Durchsuche

深入探索 Laravel 的 IoC 容器

Inversion of Control (IoC) ist eine Technik, die im Vergleich zum klassischen prozeduralen Code die Umkehrung der Kontrolle ermöglicht. Die bekannteste Form von IoC ist natürlich die Abhängigkeitsinjektion (DI). Der IoC-Container von Laravel ist eine der am häufigsten verwendeten Laravel-Funktionen, aber wahrscheinlich die am wenigsten verstandene.

Dies ist ein sehr einfaches Beispiel für die Verwendung der Abhängigkeitsinjektion zur Implementierung der Umkehrung der Kontrolle.

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

Durch die Verwendung der Konstruktorinjektion wissen wir jetzt, woher Petrol 实例的创建委托给调用者本身,从而实现控制反转。我们的 JeepWrangler 不需要知道 Petrol kommt, und können es einfach abrufen.

Was hat das alles mit Laravel zu tun? Eigentlich ziemlich viel. Falls Sie es nicht wussten: Laravel ist eigentlich ein IoC-Container. Wie zu erwarten ist, ist ein Container ein Objekt, das Dinge enthält. Der IoC-Container von Laravel wird zur Aufnahme vieler verschiedener Bindungen verwendet. Alles, was Sie in Laravel tun, wird irgendwann mit dem IoC-Container interagieren. Diese Interaktion erfolgt normalerweise in Form einer Bindung, die aufgelöst wird.

Wenn Sie einen bestehenden Laravel-Dienstanbieter öffnen, werden Sie höchstwahrscheinlich etwas Ähnliches in der register-Methode sehen (das Beispiel ist stark vereinfacht).

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

Dies ist eine sehr, sehr einfache Bindung. Es besteht aus dem Namen der Bindung (eine Instanz von router)和解析器(闭包)组成。当从容器中解析该绑定时,我们将返回一个 Router.

Laravel gruppiert oft ähnliche Bindungsnamen, wie zum Beispiel session 和 session.store.

Um eine Bindung aufzulösen, können wir die Methode direkt aufrufen oder die make-Methode für den Container verwenden.

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

Das ist es, was Container in ihrer einfachsten Form tun. Aber wie bei den meisten Dingen in Laravel steckt mehr dahinter als nur das Binden und Parsen von Klassen.

Freigegebene und nicht freigegebene Bindungen

Wenn Sie mehrere Laravel-Dienstanbieter durchsuchen, werden Sie feststellen, dass die meisten Bindungen ähnlich wie im vorherigen Beispiel definiert sind. Es geht wieder los:

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

Diese Bindung verwendet die share 方法。 Laravel 使用静态变量来存储先前解析的值,并在再次解析绑定时简单地重用该值。这基本上就是 share-Methode für den Container.

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

Eine andere Art zu schreiben ist die Verwendung der bindShared-Methode.

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

Sie können auch die Methoden singleton und instance verwenden, um eine gemeinsame Bindung zu implementieren. Wenn also beide das gleiche Ziel erreichen, wo liegt dann der Unterschied? Eigentlich nicht viel. Ich persönlich bevorzuge die singleton 和 instance 方法来实现共享绑定。那么,如果它们都实现了相同的目标,那么有什么区别呢?实际上并不是很多。我个人更喜欢使用 bindShared-Methode.

Bedingte Bindung

Manchmal möchten Sie vielleicht etwas an einen Container binden, aber nur, wenn es noch nie zuvor gebunden wurde. Es gibt mehrere Möglichkeiten, dieses Problem zu lösen, aber die einfachste ist die Verwendung der bindIf-Methode.

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

Die router-Bindung wird nur an den Container gebunden, wenn sie noch nicht vorhanden ist. Hier ist lediglich zu beachten, wie die bedingten Bindungen gemeinsam genutzt werden. Dazu müssen Sie der router 绑定尚不存在时,才会绑定到容器。这里唯一需要注意的是如何共享条件绑定。为此,您需要向 bindIf 方法提供第三个参数,其值为 true-Methode einen dritten Parameter mit dem Wert true bereitstellen.

Automatische Abhängigkeitsauflösung

Eine der am häufigsten verwendeten Funktionen des IoC-Containers ist seine Fähigkeit, Abhängigkeiten ungebundener Klassen automatisch aufzulösen. Was bedeutet das? Erstens müssen wir nicht wirklich etwas an den Container binden, um die Instanz aufzulösen. Wir können einfach make eine Instanz fast jeder Klasse instanziieren.

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

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

Der Container instanziiert die Petrol-Klasse für uns. Das Beste daran ist, dass es auch Konstruktorabhängigkeiten für uns auflöst.

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

Als Erstes prüft der Container die Abhängigkeiten der Klasse JeepWrangler. Anschließend wird versucht, diese Abhängigkeiten aufzulösen. Da unser JeepWrangler-Typ also auf die JeepWrangler 类的依赖项。然后它将尝试解决这些依赖关系。因此,因为我们的 JeepWrangler 类型提示了 Petrol-Klasse hinweist, löst der Container sie automatisch auf und fügt sie als Abhängigkeit ein.

Container können nicht automatisch Abhängigkeiten ohne Typhinweise einfügen. Wenn es sich bei einer Ihrer Abhängigkeiten also um ein Array handelt, müssen Sie es manuell instanziieren oder Standardwerte für die Parameter angeben.

Verbindliche Umsetzung

Die automatische Auflösung von Abhängigkeiten durch Laravel ist großartig und vereinfacht den Prozess der manuellen Instanziierung von Klassen. Manchmal möchten Sie jedoch eine bestimmte Implementierung einfügen, insbesondere wenn Sie Schnittstellen verwenden. Dies lässt sich leicht erreichen, indem der vollständig qualifizierte Name der Klasse als Bindung verwendet wird. Um dies zu demonstrieren, verwenden wir eine neue Schnittstelle namens 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 容器时,这是最容易使用的组件之一。

Das obige ist der detaillierte Inhalt vonEin tiefer Einblick in den IoC-Container von Laravel. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn