摘要:但是服務(wù)通常由服務(wù)提供者來(lái)管理的。小結(jié)通過(guò)上述的例子,基本上可以理解服務(wù)容器和服務(wù)提供者的使用。懂得了服務(wù)容器和服務(wù)提供者,理解門面也就不難了。
自動(dòng)依賴注入
什么是依賴注入,用大白話將通過(guò)類型提示的方式向函數(shù)傳遞參數(shù)。
實(shí)例 1首先,定義一個(gè)類:
/routes/web.php class Bar {}
假如我們?cè)谄渌胤揭褂玫?Bar 提供的功能(服務(wù)),怎么辦,直接傳入?yún)?shù)即可:
/routes/web.php Route::get("bar", function(Bar $bar) { dd($bar); });
訪問(wèn) /bar,顯示 $bar 的實(shí)例:
Bar {#272}
也就是說(shuō),我們不需要先對(duì)其進(jìn)行實(shí)例!如果學(xué)過(guò) PHP 的面向?qū)ο?,都知道,正常做法是這樣:
class Bar {} $bar = new Bar(); dd($bar);實(shí)例 2
可以看一個(gè)稍微復(fù)雜的例子:
class Baz {} class Bar { public $baz; public function __construct(Baz $baz) { $this->baz = $baz; } } $baz = new Baz(); $bar = new Bar($baz); dd($bar);
為了在 Bar 中能夠使用 Baz 的功能,我們需要實(shí)例化一個(gè) Baz,然后在實(shí)例化 Bar 的時(shí)候傳入 Baz 實(shí)例。
在 Laravel 中,不僅僅可以自動(dòng)注入 Bar,也可以自動(dòng)注入 Baz:
/routes/web.php class Baz {} class Bar { public $baz; public function __construct(Baz $baz) { $this->baz = $baz; } } Route::get("bar", function(Bar $bar) { dd($bar->baz); });
顯示結(jié)果:
Baz {#276}小結(jié)
通過(guò)上述兩個(gè)例子,可以看出,在 Laravel 中,我們要在類或者函數(shù)中使用其他類體用的服務(wù),只需要通過(guò)類型提示的方式傳遞參數(shù),而 Laravel 會(huì)自動(dòng)幫我們?nèi)ふ翼憣?duì)應(yīng)的依賴。
那么,Laravel 是如何完成這項(xiàng)工作的呢?答案就是通過(guò)服務(wù)容器。
服務(wù)容器 什么是服務(wù)容器服務(wù)容器,很好理解,就是裝著各種服務(wù)實(shí)例的特殊類。可以通過(guò)「去餐館吃飯」來(lái)進(jìn)行類比:
吃飯 - 使用服務(wù),即調(diào)用該服務(wù)的地方
飯 - 服務(wù)
盤子 - 裝飯的容器,即服務(wù)容器
服務(wù)員 - 服務(wù)提供者,負(fù)責(zé)裝飯、上飯
這個(gè)過(guò)程在 Laravel 中如何實(shí)現(xiàn)呢?
飯
定義 Rice 類:
/app/Rice.php把飯裝盤子
在容器中定義了名為 rice 的變量(你也可以起其他名字,比如 rice_container),綁定了 Food 的實(shí)例:
app()->bind("rice", function (){ return new AppRice(); });也可以寫成:
app()->bind("rice",AppRice::class);現(xiàn)在,吃飯了,通過(guò) make 方法提供吃飯的服務(wù):
Route::get("eat", function() { return app()->make("rice")->food(); // 或者 return resolve("rice")->food(); });make 方法傳入我們剛才定義的變量名即可調(diào)用該服務(wù)。
訪問(wèn) /eat,返回 香噴噴的白米飯。
為了方便起見,我們?cè)诼酚晌募兄苯訉?shí)現(xiàn)了該過(guò)程,相當(dāng)于自給自足。但是服務(wù)通常由服務(wù)提供者來(lái)管理的。
因此,我們可以讓 AppServiceProvider 這個(gè)服務(wù)員來(lái)管理該服務(wù):
/app/Providers/AppServiceProvider.php namespace AppProviders; public function register() { $this->app->bind("food_container",Rice::class); }更為常見的是,我們自己創(chuàng)建一個(gè)服務(wù)員:
$ php artisan make:provider RiceServiceProvider注冊(cè):
/app/Providers/RiceServiceProvider.php app->bind("rice",Rice::class); }這里定義了 register() 方法,但是還需要調(diào)用該方法才能真正綁定服務(wù)到容器,因此,需要將其添加到 providers 數(shù)組中:
/config/app.php "providers" => [ AppProvidersRiceServiceProvider::class, ],這一步有何作用呢?Laravel 在啟動(dòng)的時(shí)候會(huì)訪問(wèn)該文件,然后調(diào)用里面的所有服務(wù)提供者的 register() 方法,這樣我們的服務(wù)就被綁定到容器中了。
小結(jié)通過(guò)上述的例子,基本上可以理解服務(wù)容器和服務(wù)提供者的使用。當(dāng)然了,我們更為常見的還是使用類型提示來(lái)傳遞參數(shù):
use AppRice; Route::get("eat", function(Rice $rice) { return $rice->food(); });在本例中,使用自動(dòng)依賴注入即可。不需要在用 bind 來(lái)手動(dòng)綁定以及 make 來(lái)調(diào)用服務(wù)。那么,為什么還需要 bind 和 make 呢? make 比較好理解,我們有一些場(chǎng)合 Laravel 不能提供自動(dòng)解析,那么這時(shí)候手動(dòng)使用 make 解析就可以了,而 bind 的學(xué)問(wèn)就稍微大了點(diǎn),后面將會(huì)詳細(xì)說(shuō)明。
門面門面是什么,我們回到剛才的「吃飯」的例子:
Route::get("eat", function(Rice $rice) { return $rice->food(); });在 Laravel,通常還可以這么寫:
Route::get("eat", function() { return Rice::food(); });或者
Route::get("eat", function() { return rice()->food(); });那么,Laravel 是如何實(shí)現(xiàn)的呢?答案是通過(guò)門面。
門面方法實(shí)現(xiàn)先來(lái)實(shí)現(xiàn) Rice::food(),只需要一步:
/app/RiceFacade.php現(xiàn)在,RiceFacade 就代理了 Rice 類了,這就是門面的本質(zhì)了。我們就可以直接使用:
Route::get("eat", function() { dd(AppRiceFacade::food()); });因?yàn)?AppRiceFacade 比較冗長(zhǎng),我們可以用 php 提供的 class_alias 方法起個(gè)別名吧:
/app/Providers/RiceServiceProvider.php public function register() { $this->app->bind("rice",AppRice::class); class_alias(AppRiceFacade::class, "Rice"); }這樣做的話,就實(shí)現(xiàn)了一開始的用法:
Route::get("eat", function() { return Rice::food(); });看上去就好像直接調(diào)用了 Rice 類,實(shí)際上,調(diào)用的是 RiceFacade 類來(lái)代理,因此,個(gè)人覺(jué)得Facade 翻譯成假象比較合適。
最后,為了便于給代理類命名,Laravel 提供了統(tǒng)一命名別名的地方:
/config/app.php "aliases" => [ "Rice" => AppRiceFacade::class, ],門面實(shí)現(xiàn)過(guò)程分析首先:
Rice::food();因?yàn)?Rice 是別名,所以實(shí)際上執(zhí)行的是:
AppRiceFacade::food()但是我們的 RiceFacade 類里面并沒(méi)有定義靜態(tài)方法 food ???怎么辦呢?直接拋出異常嗎?不是,在 PHP 里,如果訪問(wèn)了不可訪問(wèn)的靜態(tài)方法,會(huì)先調(diào)用 __callstatic,所以執(zhí)行的是:
AppRiceFacade::__callStatic()雖然我們?cè)?RiceFacade 中沒(méi)有定義,但是它的父類 Facade 已經(jīng)定義好了:
/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php public static function __callStatic($method, $args) { // 實(shí)例化 Rice {#270} $instance = static::getFacadeRoot(); // 實(shí)例化失敗,拋出異常 if (! $instance) { throw new RuntimeException("A facade root has not been set."); } // 調(diào)用該實(shí)例的方法 return $instance->$method(...$args); }主要工作就是第一步實(shí)例化:
public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); // 本例中:static::resolveFacadeInstance("rice") }進(jìn)一步查看 resolveFacadeInstance() 方法:
protected static function resolveFacadeInstance($name) { // rice 是字符串,因此跳過(guò)該步驟 if (is_object($name)) { return $name; } // 是否設(shè)置了 `rice` 實(shí)例 if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } return static::$resolvedInstance[$name] = static::$app[$name]; }第一步比較好理解,如果我們之前在 RiceFacade 這樣寫:
protected static function getFacadeAccessor() { return new AppRice; }那么就直接返回 Rice 實(shí)例了,這也是一種實(shí)現(xiàn)方式。
主要難點(diǎn)在于最后這行:
return static::$resolvedInstance[$name] = static::$app[$name];看上去像是在訪問(wèn) $app數(shù)組,實(shí)際上是使用 數(shù)組方式來(lái)訪問(wèn)對(duì)象,PHP 提供了這種訪問(wèn)方式接口,而 Laravel 實(shí)現(xiàn)了該接口。
也就是說(shuō),$app 屬性其實(shí)就是對(duì) Laravel 容器的引用,因此這里實(shí)際上就是訪問(wèn)容器上名為 rice 的對(duì)象。而我們之前學(xué)習(xí)容器的時(shí)候,已經(jīng)將 rice 綁定了 Rice 類:
public function register() { $this->app->bind("rice",AppRice::class); // class_alias(AppRiceFacade::class, "Rice"); }所以,其實(shí)就是返回該類的實(shí)例了。懂得了服務(wù)容器和服務(wù)提供者,理解門面也就不難了。
輔助方法實(shí)現(xiàn)輔助方法的實(shí)現(xiàn),更簡(jiǎn)單了。不就是把 app->make("rice") 封裝起來(lái)嘛:
/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php if (! function_exists("rice")) { function rice() { return app()->make("rice"); // 等價(jià)于 return app("rice"); // 等價(jià)于 return app()["rice"]; } }然后我們就可以使用了:
Route::get("eat", function() { dd(rice()->food()); });小結(jié)Laravel 提供的三種訪問(wèn)類的方式:
依賴注入:通過(guò)類型提示的方式實(shí)現(xiàn)自動(dòng)依賴注入
門面:通過(guò)代理來(lái)訪問(wèn)類
輔助方法:通過(guò)方法的方式來(lái)訪問(wèn)類
本質(zhì)上,這三種方式都是借助于服務(wù)容器和服務(wù)提供者來(lái)實(shí)現(xiàn)。那么,服務(wù)容器本身有什么好處呢?我們接下來(lái)著重介紹下。
IOC 不好的實(shí)現(xiàn)我們來(lái)看另外一個(gè)例子(為了方便測(cè)試,該例子都寫在路由文件中),假設(shè)有三種類型的插座:USB、雙孔、三孔插座,分別提供插入充電的服務(wù):
class UsbsocketService { public function insert($deviceName){ return $deviceName." 正在插入 USB 充電"; } } class DoubleSocketService { public function insert($deviceName){ return $deviceName." 正在插入雙孔插座充電"; } } class ThreeSocketService { public function insert($deviceName){ return $deviceName." 正在插入三孔插座充電"; } }設(shè)備要使用插座的服務(wù)來(lái)充電:
class Device { protected $socketType; // 插座類型 public function __construct() { $this->socketType = new UsbSocketService(); } public function power($deviceName) { return $this->socketType->insert($deviceName); } }現(xiàn)在有一臺(tái)手機(jī)要進(jìn)行充電:
Route::get("/charge",function(){ $device = new Device(); return $device->power("手機(jī)"); });因?yàn)?Laravel 提供了自動(dòng)依賴注入功能,因此可以寫成:
Route::get("/charge/{device}",function(Device $device){ return $device->power("手機(jī)"); });訪問(wèn) /charge/phone,頁(yè)面顯示 phone 正在插入 USB 充電。
假如,現(xiàn)在有一臺(tái)電腦要充電,用的是三孔插座,那么我們就需要去修改 Device 類:
$this->socketType = new ThreeSocketService();這真是糟糕的設(shè)計(jì),設(shè)備類對(duì)插座服務(wù)類產(chǎn)生了依賴。更換設(shè)備類型時(shí),經(jīng)常就要去修改類的內(nèi)部結(jié)構(gòu)。
好的實(shí)現(xiàn)為了解決上面的問(wèn)題,可以參考「IOC」思路:即將依賴轉(zhuǎn)移到外部。來(lái)看看具體怎么做。
首先定義插座類型接口:
interface SocketType { public function insert($deviceName); }讓每一種插座都實(shí)現(xiàn)該接口:
class UsbsocketService implements SocketType { public function insert($deviceName){ return $deviceName." 正在插入 USB 充電"; } } class DoubleSocketService implements SocketType { public function insert($deviceName){ return $deviceName." 正在插入雙孔插座充電"; } } class ThreeSocketService implements SocketType { public function insert($deviceName){ return $deviceName." 正在插入三孔插座充電"; } }最后,設(shè)備中傳入接口類型而非具體的類:
class Device { protected $socketType; // 插座類型 public function __construct(SocketType $socketType) // 傳入接口 { $this->socketType = $socketType; } public function power($deviceName) { return $this->socketType->insert($deviceName); } }實(shí)例化的時(shí)候再?zèng)Q定使用哪種插座類型,這樣依賴就轉(zhuǎn)移到了外部:
Route::get("/charge",function(){ $socketType = new ThreeSocketService(); $device = new Device($socketType); echo $device->power("電腦"); });我們現(xiàn)在可以再不修改類結(jié)構(gòu)的情況下,方便的更換插座來(lái)滿足不同設(shè)備的充電需求:
Route::get("/charge",function(){ $socketType = new DoubleSocketService(); $device = new Device($socketType); echo $device->power("臺(tái)燈"); });自動(dòng)依賴注入的失效上面舉的例子,我們通過(guò) Laravel 的自動(dòng)依賴注入可以進(jìn)一步簡(jiǎn)化:
Route::get("/charge",function(Device $device){ echo $device->power("電腦"); });這里的類型提示有兩個(gè),一個(gè)是 Device $device,一個(gè)是 Device 類內(nèi)部構(gòu)造函數(shù)傳入的 SocketType $sockType。第一個(gè)沒(méi)有問(wèn)題,之前也試過(guò)。但是第二個(gè) SocketType 是接口,而 Laravel 會(huì)將其當(dāng)成類試圖去匹配 SocketType 的類并將其實(shí)例化,因此訪問(wèn) /charge 時(shí)候就會(huì)報(bào)錯(cuò):
Target [SocketType] is not instantiable while building [Device].
錯(cuò)誤原因很明顯,Laravel 沒(méi)法自動(dòng)綁定接口。因此,我們就需要之前的 bind 方法來(lái)手動(dòng)綁定接口啦:
app()->bind("SocketType",ThreeSocketService::class); Route::get("/charge",function(Device $device){ echo $device->power("電腦"); });現(xiàn)在,如果要更換設(shè)備,我們只需要改變綁定的值就可以了:
app()->bind("SocketType",DoubleSocketService::class); Route::get("/charge",function(Device $device){ echo $device->power("臺(tái)燈"); });也就是說(shuō),我們將依賴轉(zhuǎn)移到了外部之后,進(jìn)一步由第三方容器來(lái)管理,這就是 IOC。
契約契約,不是什么新奇的概念。其實(shí)就是上一個(gè)例子中,我們定義的接口:
interface SocketType { public function insert($deviceName); }通過(guò)契約,我們就可以保持松耦合了:
public function __construct(SocketType $socketType) // 傳入接口而非具體的插座類型 { $this->socketType = $socketType; }然后服務(wù)容器再根據(jù)需要去綁定哪種服務(wù)即可:
app()->bind("SocketType",UsbSocketService::class); app()->bind("SocketType",DoubleSocketService::class); app()->bind("SocketType",ThreeSocketService::class);Laravel 5.4 入門系列告一段落,接下來(lái)準(zhǔn)備學(xué)習(xí) Vue :)
參考資料:
Patrick Stephan :: A Simple Look At The Laravel Service Container
Unraveling Laravel Facades – Alan Storm
2.7. 外觀模式 — DesignPatternsPHP 1.0 文檔
簡(jiǎn)單理解laravel框架中的服務(wù)容器,服務(wù)提供者以及怎樣調(diào)用服務(wù) - xiake2014的博客 - 博客頻道 - CSDN.NET
laravel 學(xué)習(xí)筆記 —— 神奇的服務(wù)容器 - 靈感 - 來(lái)自生活的饋贈(zèng)
Laravel 的請(qǐng)求生命周期 | Laravel 5.4 中文文檔
PHP: 重載 - Manual
PHP: 數(shù)組式訪問(wèn) - Manual
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/22846.html
摘要:成本函數(shù)成本對(duì)于線性回歸,成本函數(shù)是表示每個(gè)預(yù)測(cè)值與其預(yù)期結(jié)果之間的聚合差異的某些函數(shù)對(duì)于邏輯回歸,是計(jì)算每次預(yù)測(cè)的正確或錯(cuò)誤的某些函數(shù)。成本函數(shù)的變換涉及到預(yù)測(cè)結(jié)果和實(shí)際結(jié)果之間數(shù)值距離的任何函數(shù)都不能作為成本函數(shù)。 矩陣和多特征線性回歸快速回顧之前文章的前提是:給定特征——任何房屋面積(sqm),我們需要預(yù)測(cè)結(jié)果,也就是對(duì)應(yīng)房?jī)r(jià)($)。為了做到這一點(diǎn),我們:我們找到一條「最擬合」所有數(shù)據(jù)...
摘要:最適合入門的初級(jí)教程一為什么選擇曾經(jīng)要跟白頭到老沒(méi)想到它升了個(gè)級(jí)就拋了錨把我等拋棄了痛定思痛重新審視了一遍框架是世界上最好的語(yǔ)言這個(gè)沒(méi)有疑問(wèn)吧如果有那絕對(duì)是個(gè)異教徒這是要被拖出去燒死的信仰的問(wèn)題神圣不可侵犯那最好的語(yǔ)言中最流行的框架是哪個(gè)呢 最適合入門的 Laravel 初級(jí)教程 (一) 為什么選擇 laravel 曾經(jīng)要跟 thinkphp 白頭到老;沒(méi)想到它升了個(gè)級(jí)就拋了錨;把我等...
摘要:最佳實(shí)踐良好的編碼規(guī)范單元測(cè)試持續(xù)集成文檔,從一開始就形成良好的編碼習(xí)慣。真實(shí)的電商業(yè)務(wù)所有的業(yè)務(wù)需求來(lái)自真實(shí)的客戶,并且線上良好運(yùn)營(yíng)中。 重要通知: Laravel + 小程序的開源電商版本源碼已經(jīng)在 github 上拉,歡迎提交 issue 和 star :) 開源電商 Server 端: Laravel API源碼 開源電商 client 端:小程序源碼 iBrand 簡(jiǎn)介...
摘要:上面這三種均不造成重載,現(xiàn)在來(lái)說(shuō)明原因。結(jié)論對(duì)于引用返回,返回的對(duì)象必須是棧幀銷毀后還存在的。全局,靜態(tài),未銷毀的函數(shù)棧幀當(dāng)中的都是可以的指針與引用如圖兩者底層實(shí)現(xiàn)差不多,引用是用指針模擬的。不建議聲明和定義分離,分離會(huì)導(dǎo)致鏈接錯(cuò)誤。 ...
摘要:在我以為我和緣分尚淺的時(shí)候,搬來(lái)救兵,智能指針橫空出世,打敗了內(nèi)存泄漏,拯救了我們的關(guān)系。智能指針引入了智能指針的概念,使用了引用計(jì)數(shù)的想法,讓程序員不再需要關(guān)心手動(dòng)釋放內(nèi)存。它還增加了一個(gè)成員函數(shù)用于交換兩個(gè)智能指針的值。 ...
閱讀 3285·2023-04-25 20:35
閱讀 3632·2019-08-30 15:54
閱讀 2017·2019-08-30 15:43
閱讀 2200·2019-08-29 15:14
閱讀 1906·2019-08-29 11:17
閱讀 3400·2019-08-26 13:36
閱讀 722·2019-08-26 10:15
閱讀 2861·2019-08-23 15:41