摘要:劃下重點,服務容器是用于管理類的依賴和執行依賴注入的工具。類的實例化及其依賴的注入,完全由服務容器自動的去完成。
本文首發于 深入剖析 Laravel 服務容器,轉載請注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。
之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請求,又是如何生成響應并最終呈現給用戶的工作原理。
本章將帶領大家研究另一個 Laravel 框架的核心內容:「服務容器」。有閱讀過 Laravel 文檔 的朋友應該有注意到在「核心架構」篇章中包含了幾個主題:生命周期、服務容器、服務提供者、Facades 和 Concracts.
今天就讓我們一起來揭開「Laravel 服務容器」的神秘面紗。
提示:本文內容較長可能需要耗費較多的閱讀時間,另外文中包含 Laravel 內核代碼建議選擇合適的 IDE 或文本編輯器進行源碼閱讀。目錄結構
序章
依賴注入基本概念
什么是依賴注入
什么是依賴注入容器
什么是控制反轉(IoC)
Laravel 服務容器是什么
小結
Laravel 服務容器的使用方法
管理待創建類的依賴
常用綁定方法
bind 簡單綁定
singleton 單例綁定
instance 實例綁定
contextual-binding 上下文綁定
自動注入和解析
Laravel 服務容器實現原理
注冊基礎服務
注冊基礎服務提供者
注冊核心服務別名到容器
管理所需創建的類及其依賴
bind 方法執行原理
make 解析處理
資料
序章如果您有閱讀我的前作 深度挖掘 Laravel 生命周期 一文,你應該已經注意到「APP 容器」、「服務容器」、「綁定」和「解析」這些字眼。沒錯這些技術都和「Laravel 服務容器」有著緊密的聯系。
在學習什么是「Laravel 服務容器」之前,如果您對「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」等相關知識還不夠了解的話,建議先學習一下這些資料:
Inversion of Control Containers and the Dependency Injection pattern:學習依賴注入必讀經典;
依賴注入系列教程:原教程由 Symfony 框架的創造者所寫,我給出的是我翻譯的文章。原教程一共分 6 篇,前兩篇講解了依賴注入基礎知識,后 4 篇講解依賴注入在 Symfony 中的應用,所以可作為選讀材料;
深入淺出依賴注入:這是本人所寫的關于依賴注入的文章,試圖以一種易于理解的行文講解什么是「依賴注入」這種設計模式。
雖然,這些學習資料都有細致的講解容器相關的概念。但介紹一下與「Laravel 服務容器」有關的基本概念仍然有必要。
依賴注入基本概念這個小節會捎帶講解下「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」這些概念。
什么是依賴注入應用程序對需要使用的依賴「插件」在編譯(編碼)階段僅依賴于接口的定義,到運行階段由一個獨立的組裝模塊(容器)完成對實現類的實例化工作,并將其「注射」到應用程序中稱之為「依賴注入」。
一言以蔽之:面向接口編程。
至于如何實現面向接口編程,在 依賴注入系列教程 的前兩篇中有實例演示,感興趣的朋友可以去閱讀這個教程。更多細節可以閱讀 Inversion of Control Containers and the Dependency Injection pattern 和 深入淺出依賴注入。
什么是依賴注入容器在依賴注入過程中,由一個獨立的組裝模塊(容器)完成對實現類的實例化工作,那么這個組裝模塊就是「依賴注入容器」。
通俗一點講,使用「依賴注入容器」時無需人肉使用 new 關鍵字去實例化所依賴的「插件」,轉而由「依賴注入容器」自動的完成一個模塊的組裝、配置、實例化等工作。
什么是控制反轉(IoC)IoC 是 Inversion of Control 的簡寫,通常被稱為控制反轉,控制反轉從字面上來說比較不容易被理解。
要掌握什么是「控制反轉」需要整明白項目中「控制反轉」究竟「反轉」了哪方面的「控制」,它需要解決如何去定位(獲取)服務所需要的依賴的實現。
實現控制反轉時,通過將原先在模塊內部完成具體實現類的實例化,移至模塊的外部,然后再通過「依賴注入」的方式將具體實例「注入」到模塊內即完成了對控制的反轉操作。
「依賴注入」的結果就是「控制反轉」的目的,也就說 控制反轉 的最終目標是為了 實現項目的高內聚低耦合,而 實現這種目標 的方式則是通過 依賴注入 這種設計模式。
以上就是一些有關服務容器的一些基本概念。和我前面說的一樣,本文不是一篇講解依賴注入的文章,所以更多的細節需要大家自行去學習我之前列出的參考資料。
接下來才是今天的正餐,我將從以下幾個角度講解 Laravel 服務容器的相關內容:
Laravel 服務容器是什么;
Laravel 服務容器的使用方法;
Laravel 服務容器技術原理。
Laravel 服務容器是什么在 Laravel 文檔 中,有一段關于 Laravel 服務容器的介紹:
Laravel 服務容器是用于管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項通過構造函數,或者某些情況下通過「setter」方法「注入」到類中。
劃下重點,「Laravel 服務容器」是用于 管理類的依賴 和 執行依賴注入 的 工具。
通過前一節「依賴注入基本概念」相關闡述,我們不難得出這樣一個簡單的結論「Laravel 服務容器」就是「依賴注入容器」。
其實,服務容器作為「依賴注入容器」去完成 Laravel 所需依賴的注冊、綁定和解析工作只是 「Laravel 服務容器」核心功能之一;另外,「Laravel 服務容器」還擔綱 Laravel 應用的注冊程序的功能。
節選一段「深度挖掘 Laravel 生命周期」一文中有關服務容器的內容:
創建應用實例即實例化 IlluminateFoundationApplication 這個服務容器,后續我們稱其為 APP 容器。在創建 APP 容器主要會完成:注冊應用的基礎路徑并將路徑綁定到 APP 容器 、注冊基礎服務提供者至 APP 容器 、注冊核心容器別名至 APP 容器 等基礎服務的注冊工作。
所以要了解 Larvel 服務容器必然需要研究 IlluminateFoundationApplication 的構造函數:
/** * Create a new Illuminate application instance. * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27 * @param string|null $basePath * @return void */ public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }
沒錯在 Application 類的構造函數一共完成 3 個操作的處理功能:
通過 registerBaseBindings() 方法將「App 實例(即 Laravel 服務容器)」自身注冊到「Laravel 服務容器」;
通過 registerBaseServiceProviders() 注冊應用 Laravel 框架的基礎服務提供者;
通過 registerCoreContainerAliases() 將具體的「依賴注入容器」及其別名注冊到「Laravel 服務容器」。
這里所說的「注冊」歸根到底還是在執行「Laravel 服務容器」的「綁定(bind)」操作,完成綁定接口到實現。
為了表名我所言非虛,讓我們看看 registerBaseBindings() 方法:
/** * Register the basic bindings into the container. 注冊 App 實例本身到 App 容器 * * @return void */ protected function registerBaseBindings() { static::setInstance($this); $this->instance("app", $this); $this->instance(Container::class, $this); $this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() )); }
我們知道 instance() 方法會將對象實例 $this** 綁定到容器的 **app** 和 **Container::class** 接口。后續無論是通過 **app()->make("app")** 還是 **app()->make(ontainer::class)** 獲取到的實現類都是 **$this(即 Laravel 服務容器實例) 對象。有關 instance 的使用方法可以查閱 Laravel 服務容器解析文檔,不過我也會在下文中給出相關使用說明。
到這里相信大家對「Laravel 服務容器」有了一個比較清晰的理解了。
小結我們所說的「Laravel 服務容器」除了擔綱「依賴注入容器」職能外;同時,還會作為 Laravel 項目的注冊中心去完成基礎服務的注冊工作。直白一點講在它的內部會將諸多服務的實現類「綁定」到「Laravel 服務容器」。總結起來它的作用主要可以歸為以下 2 方面:
注冊基礎服務;
管理所需創建的類及其依賴。
Laravel 服務容器的使用方法Laravel 服務容器在使用時一般分為兩個階段:使用之前進行綁定(bind)完成將實現綁定到接口;使用時對通過接口解析(make)出服務。
Laravel 內置多種不同的綁定方法以用于不同的使用場景。但無論哪種綁定方式,它們的最終目標是一致的:綁定接口到實現。
這樣的好處是在項目的編碼階段建立起接口和實現的映射關系,到使用階段通過抽象類(接口)解析出它的具體實現,這樣就實現了項目中的解耦。
在講解這些綁定方法前,先講一個 Laravel 服務容器的使用場景。
管理待創建類的依賴通過向服務容器中綁定需要創建的類及其依賴,當需要使用這個類時直接從服務容器中解析出這個類的實例。類的實例化及其依賴的注入,完全由服務容器自動的去完成。
舉個示例,相比于通過 new 關鍵詞創建類實例:
每次實例化時我們都需要手動的將依賴 $dependency 傳入到構造函數內。
而如果我們通過「Laravel 服務容器」綁定來管理依賴的話:
僅需在匿名函數內一次創建所需依賴 $dependency,再將依賴傳入到服務進行實例化,并返回服務實例。
此時,使用 Cache 服務時只要從「Laravel 服務容器」中解析(make)出來即可,而無需每次手動傳入 ConfigDependency 依賴再實例化服務。因為,所有的依賴注入工作此時都由 Laravel 服務容器 自動的給我們做好了,這樣就簡化了服務處理。
下面演示了如何解析出 Cache 服務:
先了解 Laravel 服務容器的一個使用場景,會對學習服務容器的 綁定方式 大有裨益。
從 Laravel 服務容器解析 - 綁定 這部分的文檔我們知道常用的綁定方式有:
bind($abstract, $concrete) 簡單綁定:將實現綁定到接口,解析時每次返回新的實例;
singleton($abstract, $concrete) 單例綁定:將實現綁定到接口,與 bind 方法不同的是首次解析是創建實例,后續解析時直接獲取首次解析的實例對象;
instance($abstract, $instance) 實例綁定:將實現實例綁定到接口;
上下文綁定和自動注入。
接下來我們將學習這些綁定方法。
常用綁定方法 bind 簡單綁定bind 方法的功能是將服務的實現綁定到抽象類,然后在每次執行服務解析操作時,Laravel 容器都會重新創建實例對象。
bind 的使用方法已經在「管理待創建類的依賴」一節中有過簡單的演示,它會在每次使用 App::make(Cache::class) 去解析 Cache 服務時,重新執行「綁定」操作中定義的閉包而重新創建 MemcachedCache 緩存實例。
bind 方法除了能夠接收閉包作為實現外,還可以:
接收具體實現類的類名;
接收 null 值以綁定自身。
singleton 單例綁定采用單例綁定時,僅在首次解析時創建實例,后續使用 make 進行解析服務操作都將直接獲取這個已解析的對象,實現了 共享 操作。
綁定處理類似 bind 綁定,只需將 bind 方法替換成 singleton 方法即可:
App::singleton(Cache::class, function () { $dependency = new ConfigDependency(config("cache.config.setting")); return $cache = new MemcachedCache($dependency); });instance 實例綁定實例綁定的功能是將已經創建的實例對象綁定到接口以供后續使用,這種使用場景類似于 注冊表。
比如用于存儲用戶模型:
contextual-binding 上下文綁定在了解上下文綁定之前,先解釋下什么是上下文,引用「輪子哥」的一段解釋:
每一段程序都有很多外部變量。只有像Add這種簡單的函數才是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨立運行。你為了使他們運行,就要給所有的外部變量一個一個寫一些值進去。這些值的集合就叫上下文。 「編程中什么是「Context(上下文)」?」 - vczh的回答。上下文綁定在 Laravel 服務容器解析 - 上下文綁定 文檔中給出了相關示例:
use IlluminateSupportFacadesStorage; use AppHttpControllersPhotoController; use AppHttpControllersVideoController; use IlluminateContractsFilesystemFilesystem; $this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("local"); }); $this->app->when(VideoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("s3"); });在項目中常會用到存儲功能,得益于 Laravel 內置集成了 FlySystem 的 Filesystem 接口,我們很容易實現多種存儲服務的項目。
示例中將用戶頭像存儲到本地,將用戶上傳的小視頻存儲到云服務。那么這個時就需要區分這樣不同的使用場景(即上下文或者說環境)。
當用戶存儲頭像(PhotoController::class)需要使用存儲服務(Filesystem::class)時,我們將本地存儲驅動,作為實現給到 PhotoController::class:
function () { return Storage::disk("local"); }而當用戶上傳視頻 VideoController::class,需要使用存儲服務(Filesystem::class)時,我們則將云服務驅動,作為實現給到 VideoController::class:
function () { return Storage::disk("s3"); }這樣就實現了基于不同的環境獲取不同的服務實現。
自動注入和解析「Laravel 服務容器」功能強大的原因在于除了提供手動的綁定接口到實現的方法,還支持自動注入和解析的功能。
我們在編寫控制器時,經常會使用類型提示功能將某個類作為依賴傳入構造函數;但在執行這個類時卻無需我們去實例化這個類所需的依賴,這一切歸功于自動解析的能力。
比如,我們的用戶控制器需要獲取用戶信息,然后在構造函數中定義 User 模型作為依賴:
user = $user; } }然后,當訪問用戶模塊時 Laravel 會自動解析出 User 模型,而無需手動的常見模型示例。
除了以上幾種數據綁定方法外還有 tag(標簽綁定) 和 extend(擴展綁定) 等,毫無疑問這些內容在 Laravel 文檔 也有介紹,所以這里就不再過多介紹了。
下一節,我們將深入到源碼中去窺探下 Laravel 服務容器是如何進行綁定和解析處理的。
Laravel 服務容器實現原理要了解一項技術的實現原理,免不了去探索源碼,源碼學習是個有意思的事情。這個過程不但讓我們理解它是如何工作的,或許還會帶給我們一些意外驚喜。
我們知道 Laravel 服務容器其實會處理以下兩方面的工作:
注冊基礎服務;
管理所需創建的類及其依賴。
注冊基礎服務關于注冊基礎服務,在「深度挖掘 Laravel 生命周期」一文中其實已經有所涉及,但并并不深入。
本文將進一步的研究注冊基礎服務的細節。除了研究這些服務究竟如何被注冊到服務容器,還將學習它們是如何被使用的。所有的這些都需要我們深入到 IlluminateFoundationApplication 類的內部:
/** * Create a new Illuminate application instance. * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27 * @param string|null $basePath * @return void */ public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }前面我們已經研究過 registerBaseBindings() 方法,了解到該方法主要是將自身綁定到了服務容器,如此我們便可以在項目中使用 $this->app->make("something") 去解析一項服務。
現在讓我們將焦點集中到 registerBaseServiceProviders 和 registerCoreContainerAliases 這兩個方法。
注冊基礎服務提供者打開 registerBaseServiceProviders 方法將發現在方法體中僅有 3 行代碼,分別是注冊 EventServiceProvider、LogServiceProvider 和 RoutingServiceProvider 這 3 個服務提供者:
/** * Register all of the base service providers. 注冊應用基礎服務提供者 * * @return void */ protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); } /** * Register a service provider with the application. * * @param IlluminateSupportServiceProvider|string $provider * @param array $options * @param bool $force * @return IlluminateSupportServiceProvider */ public function register($provider, $options = [], $force = false) { if (($registered = $this->getProvider($provider)) && ! $force) { return $registered; } // If the given "provider" is a string, we will resolve it, passing in the // application instance automatically for the developer. This is simply // a more convenient way of specifying your service provider classes. if (is_string($provider)) { $provider = $this->resolveProvider($provider); } // 當服務提供者存在 register 方法時,執行 register 方法,完成綁定處理 if (method_exists($provider, "register")) { $provider->register(); } $this->markAsRegistered($provider); // If the application has already booted, we will call this boot method on // the provider class so it has an opportunity to do its boot logic and // will be ready for any usage by this developer"s application logic. // 執行服務提供者 boot 方法啟動程序 if ($this->booted) { $this->bootProvider($provider); } return $provider; } /** * Boot the given service provider. 啟動給定服務提供者 * * @param IlluminateSupportServiceProvider $provider * @return mixed */ protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } }Laravel 服務容器在執行注冊方法時,需要進行如下處理:
如果服務提供者存在 register 方法,會將服務實現綁定到容器操作 $provider->register();;
如果服務提供者存在 boot 方法,會在 bootProvider 方法內執行啟動方法來啟動這個服務。
值得指出的是在服務提供者的 register 方法中,最好僅執行「綁定」操作。
為了更好的說明服務提供者僅完成綁定操作,還是讓我們來瞧瞧 EventServiceProvider 服務,看看它究竟做了什么:
app->singleton("events", function ($app) { return (new Dispatcher($app))->setQueueResolver(function () use ($app) { return $app->make(QueueFactoryContract::class); }); }); } }沒錯 EventServiceProvider 所做的全部事情,僅僅通過 register 方法將閉包綁定到了服務容器,除此之外就什么都沒有了。
注冊核心服務別名到容器用過 Laravel 框架的朋友應該知道在 Laravel 中有個別名系統。最常見的使用場景就是設置路由時,可以通過 Route 類完成一個新路由的注冊,如:
Route::get("/", function() { return "Hello World"; });得益于 Laravel Facades 和別名系統我們可以很方便的通過別名來使用 Laravel 內置提供的各種服務。
注冊別名和對應服務的映射關系,便是在 registerCoreContainerAliases 方法內來完成的。由于篇幅所限本文就不做具體細節的展開,后續會多帶帶出一篇講解別名系統的文章。
不過現在還是有必要瀏覽下 Laravel 提供了哪些別名服務:
/** * Register the core class aliases in the container. 在容器中注冊核心服務的別名 * * @return void */ public function registerCoreContainerAliases() { foreach ([ "app" => [IlluminateFoundationApplication::class, IlluminateContractsContainerContainer::class, IlluminateContractsFoundationApplication::class, PsrContainerContainerInterface::class], "auth" => [IlluminateAuthAuthManager::class, IlluminateContractsAuthFactory::class], "auth.driver" => [IlluminateContractsAuthGuard::class], "blade.compiler" => [IlluminateViewCompilersBladeCompiler::class], "cache" => [IlluminateCacheCacheManager::class, IlluminateContractsCacheFactory::class], "cache.store" => [IlluminateCacheRepository::class, IlluminateContractsCacheRepository::class], "config" => [IlluminateConfigRepository::class, IlluminateContractsConfigRepository::class], "cookie" => [IlluminateCookieCookieJar::class, IlluminateContractsCookieFactory::class, IlluminateContractsCookieQueueingFactory::class], "encrypter" => [IlluminateEncryptionEncrypter::class, IlluminateContractsEncryptionEncrypter::class], "db" => [IlluminateDatabaseDatabaseManager::class], "db.connection" => [IlluminateDatabaseConnection::class, IlluminateDatabaseConnectionInterface::class], "events" => [IlluminateEventsDispatcher::class, IlluminateContractsEventsDispatcher::class], "files" => [IlluminateFilesystemFilesystem::class], "filesystem" => [IlluminateFilesystemFilesystemManager::class, IlluminateContractsFilesystemFactory::class], "filesystem.disk" => [IlluminateContractsFilesystemFilesystem::class], "filesystem.cloud" => [IlluminateContractsFilesystemCloud::class], "hash" => [IlluminateContractsHashingHasher::class], "translator" => [IlluminateTranslationTranslator::class, IlluminateContractsTranslationTranslator::class], "log" => [IlluminateLogWriter::class, IlluminateContractsLoggingLog::class, PsrLogLoggerInterface::class], "mailer" => [IlluminateMailMailer::class, IlluminateContractsMailMailer::class, IlluminateContractsMailMailQueue::class], "auth.password" => [IlluminateAuthPasswordsPasswordBrokerManager::class, IlluminateContractsAuthPasswordBrokerFactory::class], "auth.password.broker" => [IlluminateAuthPasswordsPasswordBroker::class, IlluminateContractsAuthPasswordBroker::class], "queue" => [IlluminateQueueQueueManager::class, IlluminateContractsQueueFactory::class, IlluminateContractsQueueMonitor::class], "queue.connection" => [IlluminateContractsQueueQueue::class], "queue.failer" => [IlluminateQueueFailedFailedJobProviderInterface::class], "redirect" => [IlluminateRoutingRedirector::class], "redis" => [IlluminateRedisRedisManager::class, IlluminateContractsRedisFactory::class], "request" => [IlluminateHttpRequest::class, SymfonyComponentHttpFoundationRequest::class], "router" => [IlluminateRoutingRouter::class, IlluminateContractsRoutingRegistrar::class, IlluminateContractsRoutingBindingRegistrar::class], "session" => [IlluminateSessionSessionManager::class], "session.store" => [IlluminateSessionStore::class, IlluminateContractsSessionSession::class], "url" => [IlluminateRoutingUrlGenerator::class, IlluminateContractsRoutingUrlGenerator::class], "validator" => [IlluminateValidationFactory::class, IlluminateContractsValidationFactory::class], "view" => [IlluminateViewFactory::class, IlluminateContractsViewFactory::class], ] as $key => $aliases) { foreach ($aliases as $alias) { $this->alias($key, $alias); } } }管理所需創建的類及其依賴對于 Laravel 服務容器來講,其內部實現上無論是 bind、singleton、tag 還是 extend 它們的基本原理大致類似。所以本文中我們僅研究 bind 綁定來管中窺豹。
我們知道綁定方法定義在 Laravel 服務容器 IlluminateFoundationApplication 類內,而 Application繼承自 IlluminateContainerContainer 類。這些與服務容器綁定相關的方法便直接繼承自 Container 類。
bind 方法執行原理bind 綁定作為最基本的綁定方法,可以很好的說明 Laravel 是如何實現綁定服務處理的。
下面摘出 Container 容器中 bind 方法及其相關聯的方法。由于綁定處理中涉及較多方法,所以我直接將重要的代碼片段相關注釋做了翻譯及補充說明,以便閱讀:
/** * Register a binding with the container. * * @param string $abstract * @param Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { // 如果未提供實現類 $concrete,我們直接將抽象類作為實現 $abstract。 // 這之后,我們無需明確指定 $abstract 和 $concrete 是否為單例模式, // 而是通過 $shared 標識來決定它們是單例還是每次都需要實例化處理。 $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } // 如果綁定時傳入的實現類非閉包,即綁定時是直接給定了實現類的類名, // 這時要稍微處理下將類名封裝成一個閉包,保證解析時處理手法的統一。 if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact("concrete", "shared"); // 最后如果抽象類已經被容器解析過,我們將觸發 rebound 監聽器。 // 并且通過觸發 rebound 監聽器回調,將任何已被解析過的服務更新最新的實現到抽象接口。 if ($this->resolved($abstract)) { $this->rebound($abstract); } } /** * Get the Closure to be used when building a type. 當綁定實現為類名時,則封裝成閉包并返回。 * * @param string $abstract * @param string $concrete * @return Closure */ protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } return $container->make($concrete, $parameters); }; } /** * Fire the "rebound" callbacks for the given abstract type. 依據給定的抽象服務接口,觸發其 "rebound" 回調 * * @param string $abstract * @return void */ protected function rebound($abstract) { $instance = $this->make($abstract); foreach ($this->getReboundCallbacks($abstract) as $callback) { call_user_func($callback, $this, $instance); } } /** * Get the rebound callbacks for a given type. 獲取給定抽象服務的回調函數。 * * @param string $abstract * @return array */ protected function getReboundCallbacks($abstract) { if (isset($this->reboundCallbacks[$abstract])) { return $this->reboundCallbacks[$abstract]; } return []; }在 bind 方法中,主要完成以下幾個方面的處理:
干掉之前解析過的服務實例;
將綁定的實現類封裝成閉包,以確保后續處理的統一;
針對已解析過的服務實例,再次觸發重新綁定回調函數,同時將最新的實現類更新到接口里面。
在綁定過程中,服務容器并不會執行服務的解析操作,這樣有利于提升服務的性能。直到在項目運行期間,被使用時才會真正解析出需要使用的對應服務,實現「按需加載」。
make 解析處理解析處理和綁定一樣定義在 IlluminateContainerContainer 類中,無論是手動解析還是通過自動注入的方式,實現原理都是基于 PHP 的反射機制。
所有我們還是直接從 make 方法開始去挖出相關細節:
/** * Resolve the given type from the container. 從容器中解析出給定服務具體實現 * * @param string $abstract * @param array $parameters * @return mixed */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); } /** * Resolve the given type from the container. 從容器中解析出給定服務具體實現 * * @param string $abstract * @param array $parameters * @return mixed */ protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); // 如果綁定時基于上下文綁定,此時需要解析出上下文實現類 $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); // 如果給定的類型已單例模式綁定,直接從服務容器中返回這個實例而無需重新實例化 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); // 已準備就緒創建這個綁定的實例。下面將實例化給定實例及內嵌的所有依賴實例。 // 到這里我們已經做好創建實例的準備工作。只有可以構建的服務才可以執行 build 方法去實例化服務; // 否則也就是說我們的服務還存在依賴,然后不斷的去解析嵌套的依賴,知道它們可以去構建(isBuildable)。 if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // 如果我們的服務存在擴展(extend)綁定,此時就需要去執行擴展。 // 擴展綁定適用于修改服務的配置或者修飾(decorating)服務實現。 foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // 如果我們的服務已單例模式綁定,此時無要將已解析的服務緩存到單例對象池中(instances), // 后續便可以直接獲取單例服務對象了。 if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; array_pop($this->with); return $object; } /** * Determine if the given concrete is buildable. 判斷給定的實現是否立馬進行構建 * * @param mixed $concrete * @param string $abstract * @return bool */ protected function isBuildable($concrete, $abstract) { // 僅當實現類和接口相同或者實現為閉包時可構建 return $concrete === $abstract || $concrete instanceof Closure; } /** * Instantiate a concrete instance of the given type. 構建(實例化)給定類型的實現類(匿名函數)實例 * * @param string $concrete * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ public function build($concrete) { // 如果給定的實現是一個閉包,直接執行并閉包,返回執行結果 if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); } $reflector = new ReflectionClass($concrete); // 如果需要解析的類無法實例化,即試圖解析一個抽象類類型如: 接口或抽象類而非實現類,直接拋出異常。 if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; // 通過反射獲取實現類構造函數 $constructor = $reflector->getConstructor(); // 如果實現類并沒有定義構造函數,說明這個實現類沒有相關依賴。 // 我們可以直接實例化這個實現類,而無需自動解析依賴(自動注入)。 if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } // 獲取到實現類構造函數依賴參數 $dependencies = $constructor->getParameters(); // 解析出所有依賴 $instances = $this->resolveDependencies( $dependencies ); array_pop($this->buildStack); // 這是我們就可以創建服務實例并返回。 return $reflector->newInstanceArgs($instances); } /** * Resolve all of the dependencies from the ReflectionParameters. 從 ReflectionParameters 解析出所有構造函數所需依賴 * * @param array $dependencies * @return array */ protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { // If this dependency has a override for this particular build we will use // that instead as the value. Otherwise, we will continue with this run // of resolutions and let reflection attempt to determine the result. if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } // 構造函數參數為非類時,即參數為 string、int 等標量類型或閉包時,按照標量和閉包解析; // 否則需要解析類。 $results[] = is_null($dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; } /** * Resolve a non-class hinted primitive dependency. 依據類型提示解析出標量類型(閉包)數據 * * @param ReflectionParameter $parameter * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ protected function resolvePrimitive(ReflectionParameter $parameter) { if (! is_null($concrete = $this->getContextualConcrete("$".$parameter->name))) { return $concrete instanceof Closure ? $concrete($this) : $concrete; } if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } $this->unresolvablePrimitive($parameter); } /** * Resolve a class based dependency from the container. 從服務容器中解析出類依賴(自動注入) * * @param ReflectionParameter $parameter * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }以上,便是 Laravel 服務容器解析的核心,得益于 PHP 的反射機制,實現了自動依賴注入和服務解析處理,概括起來包含以下步驟:
對于單例綁定數據如果一解析過服務則直接返回,否則繼續執行解析;
非單例綁定的服務類型,通過接口獲取綁定實現類;
接口即服務或者閉包時進行構建(build)處理,構建時依托于 PHP 反射機制進行自動依賴注入解析出完整的服務實例對象;否則繼續解析(make)出所有嵌套的依賴;
如果服務存在擴展綁定,解析出擴展綁定結果;
如果綁定服務為單例綁定類型(singleton),將解析到的服務加入到單例對象池;
其它處理如觸發綁定監聽器、將服務標記為已解析狀態等,并返回服務實例。
更多細節處理還是需要我們進一步深入的內核中才能發掘出來,但到這其實已經差不太多了。有興趣的朋友可以親自了解下其它綁定方法的源碼解析處理。
以上便是今天 Laravel 服務容器的全部內容,希望對大家有所啟發。
資料感謝一下優秀的學習資料:
https://www.insp.top/learn-la...
https://laravel-china.org/art...
https://laravel-china.org/art...
https://hk.saowen.com/a/6c880...
http://rrylee.github.io/2015/...
https://blog.tanteng.me/2016/...
https://juejin.im/entry/5916a...
https://laravel-china.org/top...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28702.html
摘要:服務提供者啟動原理之前我們有學習深度挖掘生命周期和深入剖析服務容器,今天我們將學習服務提供者。的所有核心服務都是通過服務提供者進行引導啟動的,所以想深入了解那么研究服務提供者的原理是個繞不開的話題。 本文首發于 深入剖析 Laravel 服務提供者實現原理,轉載請注明出處。 今天我們將學習 Laravel 框架另外一個核心內容「服務提供者(Service Provider)」。服務提供...
摘要:外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。將使用者與子系統從直接耦合,轉變成由外觀類提供統一的接口給使用者使用,以降低客戶端與子系統之間的耦合度。接下來將深入分析外觀服務的加載過程。引導程序將在處理請求是完成引導啟動。 本文首發于 深入淺出 Laravel 的 Facade 外觀系統,轉載請注明出處。 今天我們將學習 Laravel 核心架構中的另一個主題「Fac...
摘要:一旦這一切完成,方法會運行在類屬性在命令構造后設置容器解析實例,在中我們設置了將使用的緩存驅動,我們也根據命令來決定我們調用什么方法。作業只在以上起效在上也無效處理作業方法調用觸發事件觸發事件。 譯文GitHub https://github.com/yuansir/diving-laravel-zh 原文鏈接https://divinglaravel.com/queue-system...
摘要:有幾種有用的方法可以使用將作業推送到特定的隊列在給定的秒數之后推送作業延遲后將作業推送到特定的隊列推送多個作業推送特定隊列上的多個作業調用這些方法之后,所選擇的隊列驅動會將給定的信息存儲在存儲空間中,供按需獲取。 原文鏈接https://divinglaravel.com/queue-system/pushing-jobs-to-queue There are several ways...
1. 預備知識 1.1 composer 基本用法 1.1.1 參考文章 composer 基本用法 1.1.2 要求掌握的知識點 composer 依賴管理 composer 自動加載(關鍵) 1.2 DIP、IOC、DI、IOC 容器 詳情文章 2. Laravel 運行機制剖析 2.1 場景 范例:http://laravel.com/test?name=chenxuelong 2.2 ...
閱讀 3670·2021-09-07 09:59
閱讀 725·2019-08-29 15:12
閱讀 811·2019-08-29 11:14
閱讀 1316·2019-08-26 13:27
閱讀 2670·2019-08-26 10:38
閱讀 3140·2019-08-23 18:07
閱讀 1282·2019-08-23 14:40
閱讀 1931·2019-08-23 12:38