控制反轉(IoC)是一種與經典過程程式碼相比允許反轉控制的技術。當然,IoC 最突出的形式是依賴注入(DI)。 Laravel 的 IoC 容器是最常用的 Laravel 功能之一,但可能是最不被理解的。
#這是一個使用依賴注入實現控制反轉的非常簡單的範例。
#<?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);
透過使用建構函式註入,我們現在將 Petrol
實例的建立委託給呼叫者本身,從而實現控制反轉。我們的 JeepWrangler
不需要知道 Petrol
是從哪裡來的,只要取得到即可。
那麼這一切與 Laravel 有什麼關係呢?實際上相當多。如果你不知道,Laravel 實際上是一個 IoC 容器。正如您所料,容器是一個包含事物的物件。 Laravel 的 IoC 容器用於包含許多不同的綁定。你在 Laravel 所做的一切都會在某個時刻與 IoC 容器互動。這種交互通常採用正在解析的綁定的形式。
#如果您開啟任何現有的 Laravel 服務提供程序,您很可能會在 register
方法中看到類似的內容(範例已簡化很多)。
$this->app['router'] = $this->app->share(function($app) { return new Router; });
這是一個非常非常基本的綁定。它由綁定的名稱(router
)和解析器(閉包)組成。當從容器中解析該綁定時,我們將傳回一個 Router
的實例。
Laravel 通常會將類似的綁定名稱分組,例如session
和session.store
。
要解析綁定,我們可以直接呼叫方法,或在容器上使用 make
方法。
$router = $this->app->make('router');
這就是容器最基本的形式所做的事情。但是,就像 Laravel 的大多數東西一樣,它不僅僅是綁定和解析類別。
#如果您瀏覽過幾個 Laravel 服務提供者,您會注意到大多數綁定的定義與前面的範例類似。又來了:
$this->app['router'] = $this->app->share(function($app) { return new Router; });
此綁定在容器上使用 share
方法。 Laravel 使用靜態變數來儲存先前解析的值,並在再次解析綁定時簡單地重複使用該值。這基本上就是 share
方法的作用。
$this->app['router'] = function($app) { static $router; if (is_null($router)) { $router = new Router; } return $router; };
另一種寫法是使用 bindShared
方法。
$this->app->bindShared('router', function($app) { return new Router; });
您也可以使用 singleton
和 instance
方法來實現共享綁定。那麼,如果它們都實現了相同的目標,那麼有什麼區別呢?實際上並不是很多。我個人喜歡使用 bindShared
方法。
有時您可能想要將某些內容綁定到容器,但前提是它之前尚未綁定過。有幾種方法可以解決此問題,但最簡單的方法是使用 bindIf
方法。
$this->app->bindIf('router', function($app) { return new ImprovedRouter; });
只有當 router
綁定尚不存在時,才會綁定到容器。這裡唯一需要注意的是如何共享條件綁定。為此,您需要向 bindIf
方法提供第三個參數,其值為 true
。
IoC 容器最常用的功能之一是它能夠自動解析未綁定類別的依賴關係。這到底是什麼意思?首先,我們實際上不需要將某些東西綁定到容器來解析實例。我們可以簡單地 make
幾乎任何類別的實例。
class Petrol { public function getPrice() { return 130.7; } } // In our service provider... $petrol = $this->app->make('Petrol');
容器將為我們實例化 Petrol
類別。最好的部分是它還將為我們解決建構函數的依賴關係。
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');
容器做的第一件事是檢查 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 框架的许多 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中文網其他相關文章!