在閱讀這篇文章以前,我但願您對 Laravel 的容器具備必定的使用和了解,若是不熟悉的話,請閱讀Laravel 容器,這方面的知識對於理解我今天要講的東西很是有必要,再次提醒一下各位,這篇博文容量很大,仔細體會消化,但願能有所收穫。php
若是你使用過第三方的 composer 包,它會提醒你,把它的 ServiceProvider 和 Facade 寫入到 config/app.php 文件中,以下:laravel
固然了,註冊 Facade 和 ServiceProvider 不止這麼一種方式,你感興趣的話,能夠看看官方的文檔有很清楚的描述,開發第三方 Laravel 包。面試
這些東西都沒啥可說的,若是我就說這些,也許兄弟們會說,我都知道,你還說個啥?也確實,若是就說這個,我也很差意思了,但是,後面的內容就不那麼容易了。sql
爲了給兄弟們講 Facade,我仍是大體的給你們講解一下 Laravel 的引導過程(只關注與 Facade 有關的部分),你們都知道 Laravel 的入口文件爲 public/index.php,下面的這行代碼很關鍵。shell
那麼這個文件幹啥了呢?如今咱們只關心下面這兩行:json
上面建立了一個 Application 類的對象,這個類表明了咱們當前的應用程序,也是整個 Laravel 最爲核心的類,注意了 Application 類繼承自 Container 類(這個類就是 Laravel 容器的核心),因此咱們能夠在 Application 類的對象上操做容器的方法,這就是爲啥我在這篇博文的開頭,提醒你們須要必定的容器的知識。bootstrap
上面的這段代碼,向容器中添加了一個單例,因此當咱們建立 Illuminate\Contracts\Http\Kernel::class 對象的時候,其實是建立的 App\Http\Kernel::class 類的對象,這一點極其重要,但願你們必定要記住,這在後面會使用到。數組
回到 index.php 文件中,繼續看下面這行代碼:服務器
Illuminate\Contracts\Http\Kernel::class 指向的是誰啊?不就是咱們上面說的 App\Http\Kernel::class,這個文件的位置以下:架構
這個 Kernel 就是咱們的目標了,咱們打開看一下這個文件:
這個 Kernel 類繼承自了 Illuminate\Foundation\Http\Kernel 類,兄弟們記住這一點,後面會使用到,下面咱們回到 index.php 文件中,laravel 調用:
$kernel 的 handle 方法被調用,經過上面的分析,咱們知道這個 handle 方法屬於 Illuminate\Foundation\Http\Kernel 類的,對於咱們分析 Facade 來講,只有一行代碼是關鍵的,就是下面:
咱們看一下這個方法 sendRequestThroughRouter,咱們沒必要關心它的參數 $request 是啥,它對咱們分析當前的目標一點兒關係都沒有,這個函數中也只有一行是咱們關心的,以下:
bootstrap 方法很簡單,以下:
這裏首先調用 bootstrappers 方法,這個方法的返回值是一個數組,它的內容以下:
上面這個咱們只須要關心 RegisterFacades 類,回到 bootstrap 方法中,它調用 app->bootstrapWith 方法,這裏的 app 是誰呢?他就是 Application 類的對象,這個對象在整個 Laravel 的生命週期中是惟一的,由於他是單例的,既然知道這個了,咱們看 Application 對象的 bootstrapWith 方法。
還記得咱們的 Application 類繼承自 Container 類麼?因此它可使用 make 方法,上面說了,咱們當前只關心 RegisterFacades 這個 bootstrapper,因此,咱們進入到這個類的 bootstrap 方法:
由於這篇博文主要給你們講解 Facade 的整個實現的,因此我會忽略掉一些細節,關於這些細節,之後我會給你們講解,可是在這裏我會先說明他們的做用。上面這張圖,我已經標註了序號,序號 1 這行代碼返回了 config/app.php 文件的 aliases 字段值,咱們本身的 Facade 就註冊在了這個地方,在這篇博文的開頭,我已經給你們說過了。序號 2 的做用是幹啥呢?還記得我開始說的麼?當咱們開發 laravel 包的時候,可讓 laravel 自動加載咱們的 ServiceProvider 和 Facade,咱們所要作的就是在咱們的 composer.json 中,加入下面的這段:
上面這個截圖,你們應該能夠看的很清楚,我就再也不詳述了,PackageManifest 類的做用就是負責自動加載咱們在 composer.json 文件中的 ServiceProvider 和 Facade。這麼說你們應該明白了吧。
回到 RegisterFacades 類的 bootstrap 方法中,array_merge 方法合併 1 和 2 的 Facade,並把它傳遞給 AliasLoader 的 getInstance 靜態方法。
這個 getInstance 方法返回了一個 AliasLoader 類的對象,下面咱們看它的 register 方法:
這個方法很簡單,直接調用方法 prependToLoaderStack,以下:
spl_autoload_register 這個方法可能不少人不知道,由於如今都是使用成熟的框架了,簡單來講,它的做用就是負責加載咱們的類文件的,你有沒有好奇過,php 是如何找到並加載咱們的 php 類文件的,這當中的功臣就是 spl_autoload_register 了,若是你不知道它,請參考 php 的官方文檔spl_autoload_register,它的第一個參數是一個回調方法,做用就是負責加載類文件的,咱們的程序中能夠屢次調用 spl_autoload_register 方法,也就是說能夠註冊多個加載函數,關於 spl_autoload_register 的介紹就這麼多了,回到當前的代碼中,laravel 註冊的自動加載函數爲 AliasLoader 對象的 load 方法,咱們看哈:
這個方法咱們只須要看標註出來的部分,aliases 屬性存儲着以前解析的全部的 Facade,部分截圖以下:
之因此我會把部分標出來,是由於我後面會用到,上面的代碼中使用到了 class_alias 方法,這個方法是給一個類取個別名,好比說對於 Illuminate\Support\Facades\Route::class 這個類,它的別名爲 Route,爲了證明這一點,咱們來測試一下,在咱們的路由文件中,咱們常常這麼作:
注意了咱們並無引入 Route 這麼一個東西,可是爲啥 php 沒報錯呢?這就是咱們上面給 Illuminate\Support\Facades\Route::class 取了 Route 這個別名的緣由,你能夠把 class_alias 這段代碼刪除掉,確定會報錯的:
你再刷新一下頁面,頁面報錯了,哈哈,就是這麼刺激:
上面分析了 Laravel 的整個 Facade 註冊的過程,是否是有點兒懵?不要慌,後面還有,任重而道遠啊。
在 Facade 註冊一節中,咱們標註了 Route 這個 Facade,因此這一節,就以它爲例來進行講解,Route 類以下:
全部的 Facade 都繼承自 Illuminate\Support\Facades\Facade 類,而且都必須實現 getFacadeAccessor 這個方法,否則會拋出異常的,咱們看 Route 的 getFacadeAccessor 方法以下,他返回字符串」router」,至於它的做用,咱們後面會講到:
爲了給你們講解後面的問題,我寫了一個很簡單的例子,以下:
在路由文件中,我用 Route 註冊了一個路由,這裏調用了 get 方法,可是咱們打開 Illuminate\Support\Facades\Facade\Route 類,這個方法是不存在的,它的父類也沒有,然而咱們注意到了 Illuminate\Support\Facades\Facade 類實現了__callStatic 方法,以下:
callStatic 簡單來講就是若是你調用某個類的靜態方法,可是這個靜態方法不存在的話,就會調用這個類的 callStatic 方法,若是你仍是不清楚,能夠網上查閱相關資料,這裏再也不闡述。好了,廢話很少說了,咱們回到 Facade 的__callStatic 方法中,這個方法首先調用 getFacadeRoot 方法,以下:
看到沒,這裏就是我上面說的 Facade 爲啥必須實現 getFacadeAccessor 方法,在當前的實例中,它返回的是 「router「.。
resolveFacadeInstance 方法是啥呢?很簡單,可是我仍是準備貼出來:
由於 Illuminate\Support\Facades\Facade 類是全部的 Facade 的父類,因此任何的 Facade 調用靜態方法,都會進入到這個方法中,靜態屬性 $resolvedInstance 存儲着當前全部被解析的 Facade 對應的實例對象,你要記住任何的 Facade 後面都有一個對象的,並且這個對象在整個 Laravel 程序的生命週期中是惟一的,只有這麼一個實例,上面標註的 1 首先檢查以前是否已經解析過這麼一個對象,若是解析過了,直接返回就是了,這是單例的常見手法。若是以前沒有解析過的話,那麼代碼就會走到 2 整個地方了,咱們知道 $app 就是全局惟一的 Application 類對象,它繼承了 Illuminate\Container\Container,而 Illuminate\Container\Container 又實現了接口 ArrayAccess,對於 ArrayAccess 接口不熟悉的同窗,能夠查閱相關的資料,簡單來講,若是你的類實現了 ArrayAccess 接口,那麼你就能夠像獲取數組元素同樣,獲取對象的內容而不會出錯。
這個接口有幾個方法必須實現,offsetGet 方法是其中之一,當你採用數組的寫法做用在對象上時,offsetGet 會被調用,咱們看 Illuminate\Container\Container 類的 offsetGet 方法,以下
在當前狀況下咱們獲取的是 $app \[‘router’\],因此這裏的參數 $key 就是」router」,關於容器的 make 方法,請你們參考文檔,很是簡單:
make 是容器暴露給咱們獲取容器註冊內容的少有幾個方法,好了,如今咱們的疑問是咱們何時註冊了一個」router」 這麼一個東西,你們若是使用的是 phpstorm 的話,能夠這麼作:
搜索內容爲」router」,以下:
經過搜索咱們知道,在 Illuminate\Routing\RoutingServiceProvider 這個類中,註冊了 router 的單例,你可能會問,這段代碼是啥時候調用的,也就是 registerRouter 方法是啥時候被調用的,當前的 RoutingServiceProvider 中的 register 方法以下:
那麼 register 方法是怎麼被調用的呢?不知道不要緊,我細細道來,在以前的 laravel 框架引導過程當中,建立 Application 類實例的時候,它的構造函數以下:
registerBaseServiceProviders 方法以下:
看見沒,這個地方出現了 RoutingServiceProvider 類對象,咱們再進入到 Application 類的 register 方法中,以下:
啊哈,register 方法被調用了,這個時候名爲 router 的單例就被註冊了,分析了這些,咱們回到 RoutingServiceProvider 類的 registerRouter 方法中。
這裏直接返回了 Illuminate\Routing\Router 類的實例,這個實際上就是 Laravel 全局惟一的路由器對象,路由就是靠它來實現的,分析了這些,咱們再一次回到 Illuminate\Support\Facades\Facade 類的 resolveFacadeInstance 方法中。
這裏把解析的實例存儲到 $resolvedInstance 屬性中,這樣下次就不須要解析了,resolveFacadeInstance 方法調用完畢以後,返回到 Facade 的 getFacadeRoot 方法中。
上面也是直接返回剛纔獲取到的對象 Router 實例對象,getFacadeRoot 方法調用完畢以後,繼續返回到__callStatic 方法中。
紅色的代碼就是翻譯一下就是:
$router->get('/',function () { echo "Hello World"; })
使人欣喜的是奇蹟出現了,Illuminate\Routing\Router 類有以下的代碼:
Laravel 的源代碼錯綜複雜,理解起來不是那麼容易,上面給你們標註出了主要的脈絡,但願你們仔細體會和理解。
更多學習內容能夠訪問【對標大廠】精品PHP架構師教程目錄大全,只要你能看完保證薪資上升一個臺階(持續更新)
以上內容但願幫助到你們,不少PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提高,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨須要的能夠免費分享給你們,須要的能夠點擊下方連接領取進階PHP月薪30k>>>架構師成長路線【視頻、面試文檔免費獲取】