摘要:容器主要的作用就是生產各種零件,就是提供各個服務。的原理我們以為例,來講解一下門面的原理與實現。當運行時,發現門面沒有靜態函數,就會調用這個魔術函數。我們看到這個魔術函數做了兩件事獲得對象實例,利用對象調用函數。
前言
在開始之前,歡迎關注我自己的博客:www.leoyang90.cn
這篇文章我們開始講 laravel 框架中的門面 Facade,什么是門面呢?官方文檔:
Facades(讀音:/f??s?d/ )為應用程序的服務容器中可用的類提供了一個「靜態」接口。Laravel 自帶了很多 facades ,幾乎可以用來訪問到 Laravel 中所有的服務。Laravel facades 實際上是服務容器中那些底層類的「靜態代理」,相比于傳統的靜態方法, facades 在提供了簡潔且豐富的語法同時,還帶來了更好的可測試性和擴展性。
什么意思呢?首先,我們要知道 laravel 框架的核心就是個 Ioc 容器即 服務容器,功能類似于一個工廠模式,是個高級版的工廠。laravel 的其他功能例如路由、緩存、日志、數據庫其實都是類似于插件或者零件一樣,叫做 服務。Ioc 容器主要的作用就是生產各種零件,就是提供各個服務。在 laravel 中,如果我們想要用某個服務,該怎么辦呢?最簡單的辦法就是調用服務容器的 make 函數,或者利用依賴注入,或者就是今天要講的門面 Facade。門面相對于其他方法來說,最大的特點就是簡潔,例如我們經常使用的 Router,如果利用服務容器的 make:
App::make("router")->get("/", function () { return view("welcome"); });
如果利用門面:
Route::get("/", function () { return view("welcome"); });
可以看出代碼更加簡潔。其實,下面我們就會介紹門面最后調用的函數也是服務容器的 make 函數。
Facade 的原理我們以 Route 為例,來講解一下門面 Facade 的原理與實現。我們先來看 Route 的門面類:
class Route extends Facade { protected static function getFacadeAccessor() { return "router"; } }
很簡單吧?其實每個門面類也就是重定義一下 getFacadeAccessor 函數就行了,這個函數返回服務的唯一名稱:router。需要注意的是要確保這個名稱可以用服務容器的 make 函數創建成功(App::make("router")),原因我們馬上就會講到。
那么當我們寫出 Route::get() 這樣的語句時,到底發生了什么呢?奧秘就在基類 Facade中。
public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException("A facade root has not been set."); } return $instance->$method(...$args); }
當運行 Route::get() 時,發現門面 Route 沒有靜態 get() 函數,PHP 就會調用這個魔術函數 __callStatic。我們看到這個魔術函數做了兩件事:獲得對象實例,利用對象調用 get() 函數。首先先看看如何獲得對象實例的:
public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } protected static function getFacadeAccessor() { throw new RuntimeException("Facade does not implement getFacadeAccessor method."); } protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } return static::$resolvedInstance[$name] = static::$app[$name]; }
我們看到基類 getFacadeRoot() 調用了 getFacadeAccessor(),也就是我們的服務重載的函數,如果調用了基類的 getFacadeAccessor,就會拋出異常。在我們的例子里 getFacadeAccessor() 返回了 “router”,接下來 getFacadeRoot() 又調用了 resolveFacadeInstance()。在這個函數里重點就是
return static::$resolvedInstance[$name] = static::$app[$name];
我們看到,在這里利用了 app 也就是服務容器創建了 “router”,創建成功后放入 resolvedInstance作為緩存,以便以后快速加載。
好了,Facade 的原理到這里就講完了,但是到這里我們有個疑惑,為什么代碼中寫 Route 就可以調用 IlluminateSupportFacadesRoute 呢?這個就是別名的用途了,很多門面都有自己的別名,這樣我們就不必在代碼里面寫 use IlluminateSupportFacadesRoute,而是可以直接用 Route 了。
為什么我們可以在 larval 中全局用 Route,而不需要使用 use IlluminateSupportFacadesRoute?其實奧秘在于一個 PHP 函數:class_alias,它可以為任何類創建別名。larval 在啟動的時候為各個門面類調用了 class_alias 函數,因此不必直接用類名,直接用別名即可。在 config 文件夾的 app 文件里面存放著門面與類名的映射:
"aliases" => [ "App" => IlluminateSupportFacadesApp::class, "Artisan" => IlluminateSupportFacadesArtisan::class, "Auth" => IlluminateSupportFacadesAuth::class, ... ]
下面我們來看看 laravel 是如何為門面類創建別名的。
啟動別名Aliases服務說到 larval 的啟動,我們離不開 index.php:
require __DIR__."/../bootstrap/autoload.php"; $app = require_once __DIR__."/../bootstrap/app.php"; $kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() ); ...
第一句就是我們前面博客說的 composer 的自動加載,接下來第二句獲取 laravel 核心的 Ioc 容器,第三句“制造”出 Http 請求的內核,第四句是我們這里的關鍵,這句牽扯很大,laravel 里面所有功能服務的注冊加載,乃至 Http 請求的構造與傳遞都是這一句的功勞。
$request = IlluminateHttpRequest::capture()
這句是 laravel 通過全局 _SERVER 數組構造一個 Http 請求的語句,接下來會調用 Http 的內核函數 handle:
public function handle($request) { try { $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { $this->reportException($e); $response = $this->renderException($request, $e); } catch (Throwable $e) { $this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e); } event(new EventsRequestHandled($request, $response)); return $response; }
在 handle 函數方法中 enableHttpMethodParameterOverride 函數是允許在表單中使用 delete、put 等類型的請求。我們接著看 sendRequestThroughRouter:
protected function sendRequestThroughRouter($request) { $this->app->instance("request", $request); Facade::clearResolvedInstance("request"); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
前兩句是在 larval 的 Ioc 容器設置 request 請求的對象實例,Facade 中清楚 request 的緩存實例。bootstrap:
public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } } protected $bootstrappers = [ IlluminateFoundationBootstrapLoadEnvironmentVariables::class, IlluminateFoundationBootstrapLoadConfiguration::class, IlluminateFoundationBootstrapHandleExceptions::class, IlluminateFoundationBootstrapRegisterFacades::class, IlluminateFoundationBootstrapRegisterProviders::class, IlluminateFoundationBootstrapBootProviders::class, ];
$bootstrappers 是 Http 內核里專門用于啟動的組件,bootstrap 函數中調用 Ioc 容器的 bootstrapWith 函數來創建這些組件并利用組件進行啟動服務。app->bootstrapWith:
public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this["events"]->fire("bootstrapping: ".$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); $this["events"]->fire("bootstrapped: ".$bootstrapper, [$this]); } }
可以看到 bootstrapWith 函數也就是利用 Ioc 容器創建各個啟動服務的實例后,回調啟動自己的函數 bootstrap,在這里我們只看我們 Facade 的啟動組件
IlluminateFoundationBootstrapRegisterFacades::class
RegisterFacades 的 bootstrap 函數:
class RegisterFacades { public function bootstrap(Application $app) { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); AliasLoader::getInstance($app->make("config")->get("app.aliases", []))->register(); } }
可以看出來,bootstrap 做了一下幾件事:
清除了 Facade 中的緩存
設置 Facade 的 Ioc 容器
獲得我們前面講的 config 文件夾里面 app 文件 aliases 別名映射數組
使用 aliases 實例化初始化 AliasLoader
調用 AliasLoader->register()
public function register() { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } protected function prependToLoaderStack() { spl_autoload_register([$this, "load"], true, true); }
我們可以看出,別名服務的啟動關鍵就是這個 spl_autoload_register,這個函數我們應該很熟悉了,在自動加載中這個函數用于解析命名空間,在這里用于解析別名的真正類名。
別名 Aliases 服務我們首先來看看被注冊到 spl_autoload_register 的函數,load:
public function load($alias) { if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) { $this->loadFacade($alias); return true; } if (isset($this->aliases[$alias])) { return class_alias($this->aliases[$alias], $alias); } }
這個函數的下面很好理解,就是 class_alias 利用別名映射數組將別名映射到真正的門面類中去,但是上面這個是什么呢?實際上,這個是 laravel5.4 版本新出的功能叫做實時門面服務。
實時門面服務其實門面功能已經很簡單了,我們只需要定義一個類繼承 Facade 即可,但是 laravel5.4 打算更近一步——自動生成門面子類,這就是實時門面。
實時門面怎么用?看下面的例子:
namespace AppServices; class PaymentGateway { protected $tax; public function __construct(TaxCalculator $tax) { $this->tax = $tax; } }
這是一個自定義的類,如果我們想要為這個類定義一個門面,在 laravel5.4 我們可以這么做:
use Facades { AppServicesPaymentGateway }; Route::get("/pay/{amount}", function ($amount) { PaymentGateway::pay($amount); });
那么這么做的原理是什么呢?我們接著看源碼:
protected static $facadeNamespace = "Facades"; if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) { $this->loadFacade($alias); return true; }
如果命名空間是以 Facades 開頭的,那么就會調用實時門面的功能,調用 loadFacade 函數:
protected function loadFacade($alias) { tap($this->ensureFacadeExists($alias), function ($path) { require $path; }); }
tap 是 laravel 的全局幫助函數,ensureFacadeExists 函數負責自動生成門面類,loadFacade 負責加載門面類:
protected function ensureFacadeExists($alias) { if (file_exists($path = storage_path("framework/cache/facade-".sha1($alias).".php"))) { return $path; } file_put_contents($path, $this->formatFacadeStub( $alias, file_get_contents(__DIR__."/stubs/facade.stub") )); return $path; }
可以看出來,laravel 框架生成的門面類會放到 stroge/framework/cache/ 文件夾下,名字以 facade 開頭,以命名空間的哈希結尾。如果存在這個文件就會返回,否則就要利用 file_put_contents 生成這個文件,formatFacadeStub:
protected function formatFacadeStub($alias, $stub) { $replacements = [ str_replace("/", "", dirname(str_replace("", "/", $alias))), class_basename($alias), substr($alias, strlen(static::$facadeNamespace)), ]; return str_replace( ["DummyNamespace", "DummyClass", "DummyTarget"], $replacements, $stub ); }
簡單的說,對于 FacadesAppServicesPaymentGateway,replacements 第一項是門面命名空間,將 FacadesAppServicesPaymentGateway 轉為 Facades/App/Services/PaymentGateway,取前面 Facades/App/Services/,再轉為命名空間 FacadesAppServices;第二項是門面類名,PaymentGateway;第三項是門面類的服務對象,AppServicesPaymentGateway,用這些來替換門面的模板文件:
替換后的文件是:
就是這么簡單!?。?/p> 結語
門面的原理就是這些,相對來說門面服務的原理比較簡單,和自動加載相互配合使得代碼更加簡潔,希望大家可以更好的使用這些門面!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/22924.html
摘要:根據單一責任開發原則來講,在的開發過程中每個表都應建立一個對外服務和調用。類似于這樣解析的數據操作分兩種它們除了有各自的特色外,基本的數據操作都是通過調用方法去完成整個。內并沒有太多的代碼,大多都是處理數據庫鏈接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前預祝猿人們國慶快樂,吃好、喝好、玩好,我會在...
摘要:服務提供者先看看定義服務提供者是所有應用程序啟動的中心所在。通過本文,希望大家能夠了解服務提供者,,和實際調用的類的實例之間的關系。 以 Laravel 自帶的文件系統為例,在 config/app.php 的配置文件的 providers 數組中,注冊了一個服務提供者: IlluminateFilesystemFilesystemServiceProvider::class, 在 a...
摘要:本文來自原文鏈接歡迎作客我們的學習群該篇屬于底層核心技術實戰揭秘這一課程底層核心概念解析這一章的擴展閱讀??紤]到學員們的基礎差異,為了避免視頻當中過于詳細而連篇累牘,故將一些底層實現相關的知識點以文章形式呈現,供大家預習和隨時查閱。 本文來自pilishen.com----原文鏈接; 歡迎作客我們的php&Laravel學習群:109256050該篇屬于《Laravel底層核心技術實戰...
摘要:可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。所有服務提供者都在配置文件中注冊。可以選擇推遲服務提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應用程序的性能。 本文最早發布于 Rootrl的Blog 導言 Laravel是一款先進的現代化框架,里面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
摘要:正確做法是給加索引,還有聯合索引,并不能避免全表掃描。 前言:有收獲的話請加顆小星星,沒有收獲的話可以 反對 沒有幫助 舉報三連 有心的同學應該會看到我這個noteBook下面的其它知識,希望對你們有些許幫助。 本文地址 時間點:2017-11 一個16年畢業生所經歷的php面試 一、什么是面試 二、面試準備 1. 問:什么時候開始準備? 2. 問:怎么準備? 三、面試...
閱讀 1904·2021-11-25 09:43
閱讀 1404·2021-11-22 14:56
閱讀 3280·2021-11-22 09:34
閱讀 2009·2021-11-15 11:37
閱讀 2255·2021-09-01 10:46
閱讀 1395·2019-08-30 15:44
閱讀 2293·2019-08-30 13:15
閱讀 2392·2019-08-29 13:07