本文首發於 深刻剖析 Laravel 服務容器,轉載請註明出處。喜歡的朋友不要吝嗇大家的贊同,謝謝。
以前在 深度挖掘 Laravel 生命週期 一文中,咱們有去探究 Laravel 到底是如何接收 HTTP 請求,又是如何生成響應並最終呈現給用戶的工做原理。php
本章將帶領你們研究另外一個 Laravel 框架的核心內容:「服務容器」。有閱讀過 Laravel 文檔 的朋友應該有注意到在「核心架構」篇章中包含了幾個主題:生命週期、服務容器、服務提供者、Facades 和 Concracts.html
今天就讓咱們一塊兒來揭開「Laravel 服務容器」的神祕面紗。laravel
提示:本文內容較長可能須要耗費較多的閱讀時間,另外文中包含 Laravel 內核代碼建議選擇合適的 IDE 或文本編輯器進行源碼閱讀。
依賴注入基本概念git
Laravel 服務容器是什麼github
Laravel 服務容器的使用方法redis
經常使用綁定方法編程
Laravel 服務容器實現原理設計模式
註冊基礎服務緩存
管理所需建立的類及其依賴cookie
若是您有閱讀個人前做 深度挖掘 Laravel 生命週期 一文,你應該已經注意到「APP 容器」、「服務容器」、「綁定」和「解析」這些字眼。沒錯這些技術都和「Laravel 服務容器」有着緊密的聯繫。
在學習什麼是「Laravel 服務容器」以前,若是您對「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」等相關知識還不夠了解的話,建議先學習一下這些資料:
雖然,這些學習資料都有細緻的講解容器相關的概念。但介紹一下與「Laravel 服務容器」有關的基本概念仍然有必要。
這個小節會捎帶講解下「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」這些概念。
應用程序對須要使用的依賴「插件」在編譯(編碼)階段僅依賴於接口的定義,到運行階段由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,並將其「注射」到應用程序中稱之爲「依賴注入」。
一言以蔽之:面向接口編程。
至於如何實現面向接口編程,在 依賴注入系列教程 的前兩篇中有實例演示,感興趣的朋友能夠去閱讀這個教程。更多細節能夠閱讀 Inversion of Control Containers and the Dependency Injection pattern 和 深刻淺出依賴注入。
在依賴注入過程當中,由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,那麼這個組裝模塊就是「依賴注入容器」。
通俗一點講,使用「依賴注入容器」時無需人肉使用 new 關鍵字去實例化所依賴的「插件」,轉而由「依賴注入容器」自動的完成一個模塊的組裝、配置、實例化等工做。
IoC 是 Inversion of Control 的簡寫,一般被稱爲控制反轉,控制反轉從字面上來講比較不容易被理解。
要掌握什麼是「控制反轉」須要整明白項目中「控制反轉」究竟「反轉」了哪方面的「控制」,它須要解決如何去定位(獲取)服務所須要的依賴的實現。
實現控制反轉時,經過將原先在模塊內部完成具體實現類的實例化,移至模塊的外部,而後再經過「依賴注入」的方式將具體實例「注入」到模塊內即完成了對控制的反轉操做。
「依賴注入」的結果就是「控制反轉」的目的,也就說 控制反轉 的最終目標是爲了 實現項目的高內聚低耦合,而 實現這種目標 的方式則是經過 依賴注入 這種設計模式。
以上就是一些有關服務容器的一些基本概念。和我前面說的同樣,本文不是一篇講解依賴注入的文章,因此更多的細節須要你們自行去學習我以前列出的參考資料。
接下來纔是今天的正餐,我將從如下幾個角度講解 Laravel 服務容器的相關內容:
在 Laravel 文檔 中,有一段關於 Laravel 服務容器的介紹:
Laravel 服務容器是用於管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項經過構造函數,或者某些狀況下經過「setter」方法「注入」到類中。
劃下重點,「Laravel 服務容器」是用於 管理類的依賴 和 執行依賴注入 的 工具。
經過前一節「依賴注入基本概念」相關闡述,咱們不可貴出這樣一個簡單的結論「Laravel 服務容器」就是「依賴注入容器」。
其實,服務容器做爲「依賴注入容器」去完成 Laravel 所需依賴的註冊、綁定和解析工做只是 「Laravel 服務容器」核心功能之一;另外,「Laravel 服務容器」還擔綱 Laravel 應用的註冊程序的功能。
節選一段「深度挖掘 Laravel 生命週期」一文中有關服務容器的內容:
建立應用實例即實例化 Illuminate\Foundation\Application 這個服務容器,後續咱們稱其爲 APP 容器。在建立 APP 容器主要會完成:註冊應用的基礎路徑並將路徑綁定到 APP 容器 、註冊基礎服務提供者至 APP 容器 、註冊核心容器別名至 APP 容器 等基礎服務的註冊工做。
因此要了解 Larvel 服務容器必然須要研究 Illuminate\Foundation\Application 的構造函數:
/** * 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 個操做的處理功能:
這裏所說的「註冊」歸根到底仍是在執行「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 服務容器在使用時通常分爲兩個階段:使用以前進行綁定(bind)完成將實現綁定到接口;使用時對經過接口解析(make)出服務。
Laravel 內置多種不一樣的綁定方法以用於不一樣的使用場景。但不管哪一種綁定方式,它們的最終目標是一致的:綁定接口到實現。
這樣的好處是在項目的編碼階段創建起接口和實現的映射關係,到使用階段經過抽象類(接口)解析出它的具體實現,這樣就實現了項目中的解耦。
在講解這些綁定方法前,先講一個 Laravel 服務容器的使用場景。
經過向服務容器中綁定須要建立的類及其依賴,當須要使用這個類時直接從服務容器中解析出這個類的實例。類的實例化及其依賴的注入,徹底由服務容器自動的去完成。
舉個示例,相比於經過 new 關鍵詞建立類實例:
<?php $dependency = new ConfigDependency(config('cache.config.setting')); $cache = new MemcachedCache($dependency);
每次實例化時咱們都須要手動的將依賴 $dependency 傳入到構造函數內。
而若是咱們經過「Laravel 服務容器」綁定來管理依賴的話:
<?php App::bind(Cache::class, function () { $dependency = new ConfigDependency(config('cache.config.setting')); return $cache = new MemcachedCache($dependency); });
僅需在匿名函數內一次建立所需依賴 $dependency,再將依賴傳入到服務進行實例化,並返回服務實例。
此時,使用 Cache 服務時只要從「Laravel 服務容器」中解析(make)出來便可,而無需每次手動傳入 ConfigDependency 依賴再實例化服務。由於,全部的依賴注入工做此時都由 Laravel 服務容器 自動的給咱們作好了,這樣就簡化了服務處理。
下面演示瞭如何解析出 Cache 服務:
<?php $cache = App::make(Cache::class);
先了解 Laravel 服務容器的一個使用場景,會對學習服務容器的 綁定方式 大有裨益。
從 Laravel 服務容器解析 - 綁定 這部分的文檔咱們知道經常使用的綁定方式有:
接下來咱們將學習這些綁定方法。
bind 方法的功能是將服務的實現綁定到抽象類,而後在每次執行服務解析操做時,Laravel 容器都會從新建立實例對象。
bind 的使用方法已經在「管理待建立類的依賴」一節中有過簡單的演示,它會在每次使用 App::make(Cache::class) 去解析 Cache 服務時,從新執行「綁定」操做中定義的閉包而從新建立 MemcachedCache 緩存實例。
bind 方法除了可以接收閉包做爲實現外,還能夠:
採用單例綁定時,僅在首次解析時建立實例,後續使用 make 進行解析服務操做都將直接獲取這個已解析的對象,實現了 共享 操做。
綁定處理相似 bind 綁定,只需將 bind 方法替換成 singleton 方法便可:
App::singleton(Cache::class, function () { $dependency = new ConfigDependency(config('cache.config.setting')); return $cache = new MemcachedCache($dependency); });
實例綁定的功能是將已經建立的實例對象綁定到接口以供後續使用,這種使用場景相似於 註冊表。
好比用於存儲用戶模型:
<?php // 建立一個用戶實例 $artisan = new User('柳公子'); // 將實例綁定到服務容器 App::instance('login-user', $artisan); // 獲取用戶實例 $artisan = App::make('login-user');
在瞭解上下文綁定以前,先解釋下什麼是上下文,引用「輪子哥」的一段解釋:
每一段程序都有不少外部變量。只有像Add這種簡單的函數纔是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨立運行。你爲了使他們運行,就要給全部的外部變量一個一個寫一些值進去。這些值的集合就叫上下文。 「編程中什麼是「Context(上下文)」?」 - vczh的回答。
上下文綁定在 Laravel 服務容器解析 - 上下文綁定 文檔中給出了相關示例:
use Illuminate\Support\Facades\Storage; use App\Http\Controllers\PhotoController; use App\Http\Controllers\VideoController; use Illuminate\Contracts\Filesystem\Filesystem; $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 模型做爲依賴:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\User; class UserController { private $user = null; public function __construct(User $user) { $this->user = $user; } }
而後,當訪問用戶模塊時 Laravel 會自動解析出 User 模型,而無需手動的常見模型示例。
除了以上幾種數據綁定方法外還有 tag(標籤綁定) 和 extend(擴展綁定) 等,毫無疑問這些內容在 Laravel 文檔 也有介紹,因此這裏就再也不過多介紹了。
下一節,咱們將深刻到源碼中去窺探下 Laravel 服務容器是如何進行綁定和解析處理的。
要了解一項技術的實現原理,免不了去探索源碼,源碼學習是個有意思的事情。這個過程不但讓咱們理解它是如何工做的,或許還會帶給咱們一些意外驚喜。
咱們知道 Laravel 服務容器其實會處理如下兩方面的工做:
關於註冊基礎服務,在「深度挖掘 Laravel 生命週期」一文中其實已經有所涉及,但並並不深刻。
本文將進一步的研究註冊基礎服務的細節。除了研究這些服務究竟如何被註冊到服務容器,還將學習它們是如何被使用的。全部的這些都須要咱們深刻到 Illuminate\Foundation\Application 類的內部:
/** * 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 \Illuminate\Support\ServiceProvider|string $provider * @param array $options * @param bool $force * @return \Illuminate\Support\ServiceProvider */ 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 \Illuminate\Support\ServiceProvider $provider * @return mixed */ protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, 'boot')) { return $this->call([$provider, 'boot']); } }
Laravel 服務容器在執行註冊方法時,須要進行以下處理:
值得指出的是在服務提供者的 register 方法中,最好僅執行「綁定」操做。
爲了更好的說明服務提供者僅完成綁定操做,仍是讓咱們來瞧瞧 EventServiceProvider 服務,看看它究竟作了什麼:
<?php namespace Illuminate\Events; use Illuminate\Support\ServiceProvider; use Illuminate\Contracts\Queue\Factory as QueueFactoryContract; class EventServiceProvider extends ServiceProvider { /** * Register the service provider. 註冊服務提供者 * * @return void */ public function register() { $this->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' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], 'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class], 'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class], 'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class], 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class], 'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class], 'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class], 'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class], 'db' => [\Illuminate\Database\DatabaseManager::class], 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 'files' => [\Illuminate\Filesystem\Filesystem::class], 'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class], 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], 'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], 'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class], 'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class], 'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class], 'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class], 'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class], 'redirect' => [\Illuminate\Routing\Redirector::class], 'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class], 'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class], 'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class], 'session' => [\Illuminate\Session\SessionManager::class], 'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class], 'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class], 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class], 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], ] as $key => $aliases) { foreach ($aliases as $alias) { $this->alias($key, $alias); } } }
對於 Laravel 服務容器來說,其內部實現上不管是 bind、singleton、tag 仍是 extend 它們的基本原理大體相似。因此本文中咱們僅研究 bind 綁定來管中窺豹。
咱們知道綁定方法定義在 Laravel 服務容器 Illuminate\Foundation\Application 類內,而 Application繼承自 Illuminate\Container\Container 類。這些與服務容器綁定相關的方法便直接繼承自 Container 類。
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 方法中,主要完成如下幾個方面的處理:
在綁定過程當中,服務容器並不會執行服務的解析操做,這樣有利於提高服務的性能。直到在項目運行期間,被使用時纔會真正解析出須要使用的對應服務,實現「按需加載」。
解析處理和綁定同樣定義在 Illuminate\Container\Container 類中,不管是手動解析仍是經過自動注入的方式,實現原理都是基於 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 \Illuminate\Contracts\Container\BindingResolutionException */ 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 \Illuminate\Contracts\Container\BindingResolutionException */ 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 \Illuminate\Contracts\Container\BindingResolutionException */ 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 的反射機制,實現了自動依賴注入和服務解析處理,歸納起來包含如下步驟:
更多細節處理仍是須要咱們進一步深刻的內核中才能發掘出來,但到這其實已經差不太多了。有興趣的朋友能夠親自了解下其它綁定方法的源碼解析處理。
以上即是今天 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/...