laravel5.5源碼筆記(二、服務提供者provider)

来源:https://www.cnblogs.com/wyycc/archive/2018/11/01/9866834.html
-Advertisement-
Play Games

laravel里所謂的provider服務提供者,其實是對某一類功能進行整合,與做一些使用前的初始化引導工作。laravel里的服務提供者也分為,系統核心服務提供者、與一般系統服務提供者。例如上一篇博文里介紹的,最早在application中進行註冊的event、log、routing這些就是系統的 ...


laravel里所謂的provider服務提供者,其實是對某一類功能進行整合,與做一些使用前的初始化引導工作。laravel里的服務提供者也分為,系統核心服務提供者、與一般系統服務提供者。例如上一篇博文里介紹的,最早在application中進行註冊的event、log、routing這些就是系統的核心服務,laravel的初始化需要他們。那麼現在就先來看一下provider的運行流程。

1     protected function registerBaseServiceProviders()
2     {
3         $this->register(new EventServiceProvider($this));
4 
5         $this->register(new LogServiceProvider($this));
6 
7         $this->register(new RoutingServiceProvider($this));
8     }

其他的serviceProvider則是指config/app.php中providers數組所配置的provider了,基本都是些laravel系統提供的工具型provider

 1     'providers' => [
 2 
 3         /*
 4          * Laravel Framework Service Providers...
 5          */
 6         Illuminate\Auth\AuthServiceProvider::class,
 7         Illuminate\Broadcasting\BroadcastServiceProvider::class,
 8         Illuminate\Bus\BusServiceProvider::class,
 9         Illuminate\Cache\CacheServiceProvider::class,
10         Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
11         Illuminate\Cookie\CookieServiceProvider::class,
12         Illuminate\Database\DatabaseServiceProvider::class,
13         Illuminate\Encryption\EncryptionServiceProvider::class,
14         Illuminate\Filesystem\FilesystemServiceProvider::class,
15         Illuminate\Foundation\Providers\FoundationServiceProvider::class,
16         Illuminate\Hashing\HashServiceProvider::class,
17         Illuminate\Mail\MailServiceProvider::class,
18         Illuminate\Notifications\NotificationServiceProvider::class,
19         Illuminate\Pagination\PaginationServiceProvider::class,
20         Illuminate\Pipeline\PipelineServiceProvider::class,
21         Illuminate\Queue\QueueServiceProvider::class,
22         Illuminate\Redis\RedisServiceProvider::class,
23         Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
24         Illuminate\Session\SessionServiceProvider::class,
25         Illuminate\Translation\TranslationServiceProvider::class,
26         Illuminate\Validation\ValidationServiceProvider::class,
27         Illuminate\View\ViewServiceProvider::class,
28         //Maatwebsite\Excel\ExcelServiceProvider::class,  這個是我自己測試的時候加的
29 
30         /*
31          * Package Service Providers...
32          */
33 
34         /*
35          * Application Service Providers...
36          */
37         App\Providers\AppServiceProvider::class,
38         App\Providers\AuthServiceProvider::class,
39         // App\Providers\BroadcastServiceProvider::class,
40         App\Providers\EventServiceProvider::class,
41         App\Providers\RouteServiceProvider::class,
42 
43     ],

那麼這些配置中的provider會在什麼時候載入呢?上一篇博文中介紹的當$kernel對象通過handle方法傳入request時,會執行sendRequestThroughRouter方法,這個方法中的bootstrap方法會載入laravel系統初始化所需的對象並運行,其中RegisterProviders類便是用來註冊剛剛config文件內所記錄的provider的

 1     public function bootstrap()
 2     {
 3         if (! $this->app->hasBeenBootstrapped()) {
 4             $this->app->bootstrapWith($this->bootstrappers());
 5         }
 6     }
 7 
 8     protected $bootstrappers = [
 9         \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
10         \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
11         \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
12         //註冊facade門面類
13         \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
14         //註冊provider
15         \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
16         //引導provider執行其中boot方法內的代碼
17         \Illuminate\Foundation\Bootstrap\BootProviders::class,
18     ];    

這幾個文件的內容都很簡單,並且都是調用了application中的方法

 1     public function bootstrapWith(array $bootstrappers)
 2     {
 3         $this->hasBeenBootstrapped = true;
 4 
 5         foreach ($bootstrappers as $bootstrapper) {
 6             $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
 7             //make了剛剛傳入的$bootstrappers數組,並執行了其中的bootstrap方法,暫且只看provider
 8             $this->make($bootstrapper)->bootstrap($this);
 9 
10             $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
11         }
12     }
13     
14     //Illuminate\Foundation\Bootstrap\RegisterProviders.php
15     public function bootstrap(Application $app)
16     {
17         $app->registerConfiguredProviders();
18     }
19 
20     //Illuminate\Foundation\Bootstrap\BootProviders.php
21     public function bootstrap(Application $app)
22     {
23         $app->boot();
24     }

這裡繞了一大圈,最終還是回到了application文件中,還記得上一篇博文中介紹的registerConfiguredProviders方法嗎?

application的registerConfiguredProviders()方法對服務提供者進行了註冊,通過框架的文件系統收集了配置文件中的各種provicers並轉化成數組,在G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php類的load方法中進行載入,但最終還是會在application類中的register()方法中通過字元串的方式new出對象,在執行provider中自帶的register()方法

 1     public function registerConfiguredProviders()
 2     {
 3         //laravel的集合類,將之前初始化時存入的config中的數組取出
 4         $providers = Collection::make($this->config['app.providers'])
 5                         ->partition(function ($provider) {
 6                             //並過濾出系統providers
 7                             return Str::startsWith($provider, 'Illuminate\\');
 8                         });
 9         //之前在registerBaseBindings方法中綁定在PackageManifest類中的providers數組拼接,通過load方法載入它們
10         $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
11         //new了provider庫,傳入服務容器、文件系統操作對象、與之前緩存的服務提供者路徑
12         (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
13                     ->load($providers->collapse()->toArray());
14     }
 1  //Illuminate\Foundation\ProviderRepository.php
 2 
 3    public function load(array $providers)
 4     {
 5         // 查看bootstrap/cache/services.php有沒有這個緩存文件
 6         // 第一次啟動時是沒有的
 7         $manifest = $this->loadManifest();
 8         // 開始沒有這個緩存文件,那就把$providers[ ]里的值
 9         if ($this->shouldRecompile($manifest, $providers)) {
10             // 然後根據$providers[ ]編譯出services.php這個緩存文件
11             $manifest = $this->compileManifest($providers);
12         }
13 
14         foreach ($manifest['when'] as $provider => $events) {
15             // 註冊包含有事件監聽的service provider
16             // 包含有事件監聽的service provider都要有when()函數返回
17             $this->registerLoadEvents($provider, $events);
18         }
19 
20         foreach ($manifest['eager'] as $provider) {
21             // 把'eager'欄位中service provider註冊進容器中,
22             // 即遍歷每一個service provider,調用其中的register()方法
23             // 向容器中註冊具體的服務
24             $this->app->register($this->createProvider($provider));
25         }
26 
27         // 註冊延遲的service provider,
28         // deferred的service provider, 一是要設置$defer = true,二是要提供provides()方法返回綁定到容器中服務的名稱
29         $this->app->addDeferredServices($manifest['deferred']);
30     }

 

而boot操作就更簡單了

 1     public function boot()
 2     {
 3         if ($this->booted) {
 4             return;
 5         }
 6 
 7         // Once the application has booted we will also fire some "booted" callbacks
 8         // for any listeners that need to do work after this initial booting gets
 9         // finished. This is useful when ordering the boot-up processes we run.
10         //調用引導方法的鉤子函數
11         $this->fireAppCallbacks($this->bootingCallbacks);
12         //使每個provider運行bootProvider,$p為provider
13         array_walk($this->serviceProviders, function ($p) {
14             $this->bootProvider($p);
15         });
16         //改變引導狀態
17         $this->booted = true;
18         //調用引導方法的鉤子函數
19         $this->fireAppCallbacks($this->bootedCallbacks);
20     }
21 
22     protected function bootProvider(ServiceProvider $provider)
23     {
24         //判斷傳入的provier,運行它們的boot方法完成引導
25         if (method_exists($provider, 'boot')) {
26             return $this->call([$provider, 'boot']);
27         }
28     }

到這裡,provider通過register註冊在了服務容器內,provider的初始化工作也由boot函數完成,這個provider所提供的對象便可以直接拿來使用了。

還記得學習laravel框架使用方式的時候,文檔建議我們把所有在應用初始化時需要完成的事情,都寫在AppServiceProvider的boot方法里嗎?看到這裡我們能明白作為系統核心prvider的app是最早被載入的,因此也充當了一個鉤子函數的角色。

在瞭解了provider的註冊流程之後,就可以自己來自定義一個provider了。我們上一篇博客里還有一個契約的概念沒有說明,這裡簡單舉一個小例子來說明。

1、新建一個介面。

 

1 namespace App\Contracts;
2 
3 interface Test
4 {
5     public function doing();
6 }

2、新建兩個介面的實現

 1 namespace App\Services;
 2 
 3 use App\Contracts\Test;
 4 
 5 class TestService implements Test
 6 {
 7     public function doing()
 8     {
 9         echo 'this is TestService';
10     }
11 }
12 
13 
14 namespace App\Services;
15 
16 use App\Contracts\Test;
17 
18 class SecondTestService implements Test
19 {
20     public function doing()
21     {
22         echo 'this is SecondTestService';
23     }
24 }

3、新建一個provider,可使用artisan 命令行   php artisan make:provider TestServiceProvider 創建一個provider,契約上下文就在這個地方進行綁定。上一篇博文里講到make方法的時候,容器在解析類的時候,有一個獲取上下文的步驟,所要獲取的concrete就是在provider中通過when方法綁定的類了,不過可惜這個綁定只能具體到類,不能具體到方法。

 1 namespace App\Providers;
 2 
 3 use Illuminate\Support\ServiceProvider;
 4 
 5 class TestServiceProvider extends ServiceProvider
 6 {
 7     /**
 8      * Bootstrap any application services.
 9      *
10      * @return void
11      */
12     public function boot()
13     {
14         //
15     }
16 
17     public function register()
18     {
19         $this->app->bind('App\Contracts\Test', 'App\services\TestService');
20         //重點在於when方法確定運行環境,也就是執行上下文,needs為make所需的abstract類名或別名,give所傳入的參數則是實際調用的實現類了
21         $this->app->when('App\Http\Controllers\IndexController')
22                 ->needs('App\Contracts\Test')
23                 ->give('App\Services\SecondTestService');
24     }
25 }

4、在config/app.php文件的providers數組中添加剛剛生成的provider

 1     'providers' => [
 2 
 3         /*
 4          * Laravel Framework Service Providers...
 5          */
 6         Illuminate\Auth\AuthServiceProvider::class,
 7         Illuminate\Broadcasting\BroadcastServiceProvider::class,
 8         Illuminate\Bus\BusServiceProvider::class,
 9         Illuminate\Cache\CacheServiceProvider::class,
10         Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
11         Illuminate\Cookie\CookieServiceProvider::class,
12         Illuminate\Database\DatabaseServiceProvider::class,
13         Illuminate\Encryption\EncryptionServiceProvider::class,
14         Illuminate\Filesystem\FilesystemServiceProvider::class,
15         Illuminate\Foundation\Providers\FoundationServiceProvider::class,
16         Illuminate\Hashing\HashServiceProvider::class,
17         Illuminate\Mail\MailServiceProvider::class,
18         Illuminate\Notifications\NotificationServiceProvider::class,
19         Illuminate\Pagination\PaginationServiceProvider::class,
20         Illuminate\Pipeline\PipelineServiceProvider::class,
21         Illuminate\Queue\QueueServiceProvider::class,
22         Illuminate\Redis\RedisServiceProvider::class,
23         Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
24         Illuminate\Session\SessionServiceProvider::class,
25         Illuminate\Translation\TranslationServiceProvider::class,
26         Illuminate\Validation\ValidationServiceProvider::class,
27         Illuminate\View\ViewServiceProvider::class,
28 
29         /*
30          * Package Service Providers...
31          */
32 
33         /*
34          * Application Service Providers...
35          */
36         App\Providers\AppServiceProvider::class,
37         App\Providers\AuthServiceProvider::class,
38         // App\Providers\BroadcastServiceProvider::class,
39         App\Providers\EventServiceProvider::class,
40         App\Providers\RouteServiceProvider::class,
41         //添加剛剛生成的provider
42         App\Providers\TestServiceProvider::class,
43     ],

5、在IndexController文件中添加執行代碼

 1 namespace App\Http\Controllers;
 2 
 3 use App\Contracts\Test;
 4 
 5 class IndexController extends Controller
 6 {
 7 
 8     public function __construct(Test $test)
 9     {
10         $this->test = $test;
11     }
12 
13     public function index(Test $test)
14     {
15         app()->make('App\Contracts\Test')->doing();
16 
17         echo '<br>';
18         //只有通過構造方法進行自動載入依賴的方式才能觸發契約的when綁定
19         $this->test->doing();
20     
21         echo '<br>';
22     //因為laravel中的上下文綁定只能具體到類,所以這裡的$test實例依然為普通綁定
23         $test->doing();
24 
25     }
26 }

運行後,會發現只有通過構造函數實例化的對象,才能觸發額外的分支綁定。通過這個小例子,我們可以很清楚的理解契約了,就是在不同情況下的一個對介面的動態調用,算是java中多態和策略模式的另一實現方式。使用了這種實現方式,可以使我們在開發過程中的代碼更加靈活,在改變實現方式的時候,只需改變provider中的實現綁定,即可快速實現需求變更。

可能有人會發現我們的demo在執行時需要顯示的使用make方法,一點也不優雅,這和laravel所宣揚的思想還是有差距。那是因為還有一個facade門面功能還沒有用上,後面我們會來探尋一下facade到底是個什麼東西。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 代理模式詳解 1 什麼是代理模式? 一句話描述:代理模式是一種使用代理對象來執行目標對象的方法併在代理對象中增強目標對象方法的一種設計模式。 詳細描述: 1、理論基礎-代理模式是設計原則中的“開閉原則(對擴展開放、對修改關閉)”的具體實踐,代理對象代為執行目標對象的方法,併在此基礎上進行相應的擴展。 ...
  • ...
  • 最近在Java技術棧微信公眾號粉絲微信群里看到一張圖,說是剛寫完這段下麵這段代碼就被開除了。 開除的原因是因為沒寫註釋嗎? 顯然不是,休眠的邏輯,大家都懂,不需要寫註釋,你註釋寫休眠 1 天也沒意義啊。。。 這個程式員的思維不是一般的牛啊,獲取下一天的日期,居然要休眠等到下一天再獲取,欲哭無淚。。。 ...
  • 集合:{},可變的數據類型,他裡面的元素必須是不可變的數據類型,無序,不重覆。(不重要)集合的書寫 set = {'alex','wusir','ritian','egon','barry'} 增 add update 刪 pop remove clear set 查 for 交集 & inters ...
  • struct的導出和暴露問題 關於struct的導出 struct的屬性是否被導出,也遵循大小寫的原則:首字母大寫的被導出,首字母小寫的不被導出。 所以: 1. 如果struct名稱首字母是小寫的,這個struct不會被導出。連同它裡面的欄位也不會導出,即使有首字母大寫的欄位名 。 2. 如果str ...
  • 多線程 std::lock 當要同時操作2個對象時,就需要同時鎖定這2個對象,而不是先鎖定一個,然後再鎖定另一個。同時鎖定多個對象的方法:std::lock(對象1.鎖,對象2.鎖...) 額外說明:lock_guard\ lock_a(d1.m, std::adopt_lock); 上面這句是為了 ...
  • 1、遞歸與迭代: 遞歸和迭代都是迴圈的一種。簡單地說,遞歸是重覆調用函數自身實現迴圈。迭代是函數內某段代碼實現迴圈,而迭代與普通迴圈的區別是:迴圈代碼中參與運算的變數同時是保存結果的變數,當前保存的結果作為下一次迴圈計算的初始值。 遞歸迴圈中,遇到滿足終止條件的情況時逐層返回來結束。迭代則使用計數器 ...
  • 問題: 由於公司業務擴大,各個子系統陸續遷移和部署在不同的數據源上,這樣方便擴容,但是因此引出了一些問題。 舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時需要查詢出所關聯的"用戶"(位於賬戶子系統)的姓名,而這時由於數據存儲在不同的數據源上,沒有辦法通過一條連表的sql獲取到全部的數據,而是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...