>백엔드 개발 >PHP 튜토리얼 >Laravel의 IoC 컨테이너에 대한 심층 분석

Laravel의 IoC 컨테이너에 대한 심층 분석

WBOY
WBOY원래의
2023-09-01 18:49:101387검색

深入探索 Laravel 的 IoC 容器

IoC(Inversion of Control)는 기존 절차 코드에 비해 제어 반전을 허용하는 기술입니다. 물론 IoC의 가장 두드러진 형태는 DI(종속성 주입)입니다. Laravel의 IoC 컨테이너는 가장 일반적으로 사용되는 Laravel 기능 중 하나이지만 아마도 가장 잘 이해되지 않을 것입니다.

이것은 종속성 주입을 사용하여 제어 반전을 구현하는 매우 간단한 예입니다.

으아아아

생성자 주입을 사용하여 이제 Petrol 实例的创建委托给调用者本身,从而实现控制反转。我们的 JeepWrangler 不需要知道 Petrol가 어디서 왔는지 알고 바로 가져옵니다.

그럼 이 모든 것이 Laravel과 어떤 관련이 있나요? 실제로 꽤 많습니다. 몰랐다면 Laravel은 실제로 IoC 컨테이너입니다. 예상할 수 있듯이 컨테이너는 항목을 포함하는 객체입니다. Laravel의 IoC 컨테이너는 다양한 바인딩을 포함하는 데 사용됩니다. Laravel에서 수행하는 모든 작업은 어느 시점에서는 IoC 컨테이너와 상호 작용합니다. 이 상호 작용은 일반적으로 바인딩이 해결되는 형태를 취합니다.

기존 Laravel 서비스 제공자를 열면 register 방법에서 비슷한 내용을 볼 수 있을 것입니다(예제는 훨씬 단순화되었습니다).

으아아아

아주 기본적인 바인딩입니다. 바운드 이름(router)和解析器(闭包)组成。当从容器中解析该绑定时,我们将返回一个 Router.

의 인스턴스)으로 구성됩니다.
Laravel은 종종 session 和 session.store와 같은 유사한 바인딩 이름을 그룹화합니다.

바인딩을 해결하려면 메서드를 직접 호출하거나 컨테이너에서 make 메서드를 사용할 수 있습니다.

으아아아

이것이 가장 기본적인 형태의 컨테이너가 수행하는 작업입니다. 그러나 Laravel의 대부분의 기능과 마찬가지로 여기에는 클래스 바인딩 및 구문 분석 이상의 기능이 있습니다.

공유 및 비공유 바인딩

여러 Laravel 서비스 제공자를 찾아보면 대부분의 바인딩이 이전 예와 유사하게 정의되어 있음을 알 수 있습니다. 다시 시작합니다:

으아아아

이 바인딩은 컨테이너에서 share 方法。 Laravel 使用静态变量来存储先前解析的值,并在再次解析绑定时简单地重用该值。这基本上就是 share 메서드를 사용합니다.

으아아아

작성하는 또 다른 방법은 bindShared 메소드를 사용하는 것입니다.

으아아아

싱글톤인스턴스 메서드를 사용하여 공유 바인딩을 구현할 수도 있습니다. 그렇다면 둘 다 동일한 목표를 달성한다면 차이점은 무엇입니까? 실제로는 많지 않습니다. 저는 개인적으로 singleton 和 instance 方法来实现共享绑定。那么,如果它们都实现了相同的目标,那么有什么区别呢?实际上并不是很多。我个人更喜欢使用 bindShared 방법을 사용하는 것을 선호합니다.

조건부 바인딩

때때로 무언가를 컨테이너에 바인딩하고 싶을 수도 있지만 이전에 바인딩된 적이 없는 경우에만 가능합니다. 이 문제를 해결하는 방법에는 여러 가지가 있지만 가장 쉬운 방법은 bindIf 방법을 사용하는 것입니다.

으아아아

라우터 바인딩은 컨테이너가 아직 존재하지 않는 경우에만 컨테이너에 바인딩됩니다. 여기서 주목할 점은 조건부 바인딩이 공유되는 방식입니다. 이렇게 하려면 true 값을 사용하여 router 绑定尚不存在时,才会绑定到容器。这里唯一需要注意的是如何共享条件绑定。为此,您需要向 bindIf 方法提供第三个参数,其值为 true 메소드에 세 번째 매개변수를 제공해야 합니다.

자동 종속성 해결

IoC 컨테이너의 가장 일반적으로 사용되는 기능 중 하나는 바인딩되지 않은 클래스의 종속성을 자동으로 해결하는 기능입니다. 이것은 무엇을 의미 하는가? 첫째, 인스턴스를 해결하기 위해 실제로 무언가를 컨테이너에 바인딩할 필요가 없습니다. 거의 모든 클래스의 인스턴스를 간단하게 make 인스턴스화할 수 있습니다.

으아아아

컨테이너는 Petrol 클래스를 인스턴스화합니다. 가장 좋은 점은 생성자 종속성도 해결해 준다는 것입니다.

으아아아

컨테이너가 가장 먼저 하는 일은 JeepWrangler 클래스의 종속성을 확인하는 것입니다. 그런 다음 이러한 종속성을 해결하려고 시도합니다. 따라서 JeepWrangler 유형이 JeepWrangler 类的依赖项。然后它将尝试解决这些依赖关系。因此,因为我们的 JeepWrangler 类型提示了 Petrol 클래스를 암시하기 때문에 컨테이너는 이를 자동으로 확인하고 종속성으로 주입합니다.

컨테이너는 유형 힌트가 없는 종속성을 자동으로 주입할 수 없습니다. 따라서 종속성 중 하나가 배열인 경우 이를 수동으로 인스턴스화하거나 매개변수에 대한 기본값을 지정해야 합니다.

바인딩 구현

Laravel이 종속성을 자동으로 해결하도록 하는 것은 훌륭하고 수동으로 클래스를 인스턴스화하는 프로세스를 단순화합니다. 그러나 때로는 특히 인터페이스를 사용할 때 특정 구현을 주입하고 싶을 때가 있습니다. 이는 클래스의 정규화된 이름을 바인딩으로 사용하여 쉽게 달성할 수 있습니다. 이를 시연하기 위해 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 容器时,这是最容易使用的组件之一。

위 내용은 Laravel의 IoC 컨테이너에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.