内容简介:上一篇文章『其中上面的ILogService 的類別,那他就會實體化 AWSLogServcie 出來。
什麼是 Laravel Service Provider ?
上一篇文章『 PHP Laravel 的 Container 理解 』中咱們學習到了 Laravel 的 Container 是一種用來解決依賴與耦合的概念,它建立了一個容器並且在裡面定義好抽像與實際類別的對應,最後就可以自動的進行依賴性注入。如下偽程式碼。
<?php $containter = require('Container'); // 建立抽象與實體類別的對應 $containter->bind(ILogService, AWSLogServcie::class); $log = $container->make(Log::class); $log->send('log....');
其中上面的 bind
就是可以在這個容器內建立一個抽象類別舉實體類別的對應,也就是說如果後來要實體化有實作
ILogService 的類別,那他就會實體化 AWSLogServcie 出來。
那 Service Provider 是什麼 ?
它就個註冊與管理 Container 內服務的地方。
下面的程式碼為 Laravel 專案的 Service Provider,其中有兩個重要的方法 boot
與 register
。
- register : 它就是用來寫 bind 的地方。
- boot : 它就是當 register 結束以後會執行的方法。
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { // } /** * Register any application services. * * @return void */ public function register() { $this->app->bind( PostRepositoryInterface::class, PostRepository::class ); } }
那什麼時後會需要使用 boot ?
boot 這個方法的執行時機為 register 執行完以後,那它什麼時後要用到他呢 ? 我覺得比較好的定義如下 :
在實際上使用這個 service 前,所需要做的前處理。
例如下面的 Laravel 官網授權章節所寫的範例,在要使用 AuthService 前它需要先將一些 policy 先註冊,這時就很適合寫在 boot 這個方法裡面。
<?php namespace App\Providers; use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** * 註冊任何應用程式的認證或授權服務。 * * @param \Illuminate\Contracts\Auth\Access\Gate $gate * @return void */ public function boot(GateContract $gate) { $this->registerPolicies($gate); $gate->define('update-post', function ($user, $post) { return $user->id === $post->user_id; }); } }
或是
<?php class DatabaseServiceProvider extends ServiceProvider { /** * Bootstrap the application events. * * @return void */ public function boot() { Model::setConnectionResolver($this->app['db']); Model::setEventDispatcher($this->app['events']); } public function register() { Model::clearBootedModels(); $this->registerConnectionServices(); $this->registerEloquentFactory(); $this->registerQueueableEntityResolver(); } }
為什麼要使用 Service Provider 呢 ?
上面我們大概理解了 Service Provider 以後,那接下來我們就來思考一件事情。
為什麼要使用 Service Provider 呢 ?
我們先假設沒有 Service Provider,然後來看看程式碼會變成什麼樣子。
假設我們已經產生了 container,就如下程式碼的 $app 。
<?php public/index.php $app = require_once __DIR__.'/../bootstrap/app.php'; $app->bind(IUserService:class, UserESrvice:class); $app->bind(IMessageService:class, MessageService:class);
事實上現在這樣是還沒什麼問題,那如果是這樣呢 ?
<?php public/index.php $app = require_once __DIR__.'/../bootstrap/app.php'; $app->bind(IUserService:class, UserService:class); $app->bind(IMessageService:class, MessageService:class); $app->singleton('redis', function ($app) { $config = $app->make('config')->get('database.redis', []); return new RedisManager($app, Arr::pull($config, 'client', 'predis'), $config); }); $app->bind('redis.connection', function ($app) { return $app['redis']->connection(); }); $app->singleton('cache', function ($app) { return new CacheManager($app); }); $app->singleton('cache.store', function ($app) { return $app['cache']->driver(); }); $this->app->singleton('db', function ($app) { return new DatabaseManager($app, $app['db.factory']); }); $this->app->singleton('db.factory', function ($app) { return new ConnectionFactory($app); });
你可以發現這個檔案已經開發有點腫大,而且這樣在多人開發時,你會發現一直的 merge conflict 。
然後接下來,你可能在使用 db 前還需要設定一下東西,然後這個檔案變的如下。
<?php public/index.php $app = require_once __DIR__.'/../bootstrap/app.php'; $app->bind(IUserService:class, UserService:class); $app->bind(IMessageService:class, MessageService:class); $app->singleton('redis', function ($app) { $config = $app->make('config')->get('database.redis', []); return new RedisManager($app, Arr::pull($config, 'client', 'predis'), $config); }); $app->bind('redis.connection', function ($app) { return $app['redis']->connection(); }); $app->singleton('cache', function ($app) { return new CacheManager($app); }); $app->singleton('cache.store', function ($app) { return $app['cache']->driver(); }); $this->app->singleton('db', function ($app) { return new DatabaseManager($app, $app['db.factory']); }); $this->app->singleton('db.factory', function ($app) { return new ConnectionFactory($app); }); Model::setConnectionResolver($this->app['db']); Model::setEventDispatcher($this->app['events']);
這時你就會很明顯的注意到有幾項缺點 :
- 這個檔案太腫大了,每個人都需要修改到他。
- 這個檔案做太多事情了,要產生 container、要註冊服務、註冊服務的前處理。
也就是因為這些原因,因此 Laravel 就產生了 Service Provider,並且它基本上是會根據模組來產生不同的 Provider,像以上面的範例就可以分拆成 db、cache、 redis 等 provider。
某些方面這就違反了 SRP(Single Responsibility Principle)單一責任原則
雖然上述的原則是用在類別或方法上,但是我覺得以概念上來看,也可以用在上面這種狀況。
Laravel Service Provider 流程原始碼分析
接下來這章節我們要來理解一下 Laravel 是什麼時後註冊 Service provider,並且它內部是如何執行。
在 Laravel 中所有一切的源頭就是創建 Container,所以就從這裡開始看。
php artisan serve
首先產生完 containter ($app) 以後,接下來 Laravel 會在實體化 $kernel,它可以說是所有操作的核心模式,然後接下來 handler 它會執行所有 input 進來的東西,而這裡面就有用來處理 service provider 的地方。
#!/usr/bin/env php <?php define('LARAVEL_START', microtime(true)); require __DIR__.'/vendor/autoload.php'; $app = require_once __DIR__.'/bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput ); $kernel->terminate($input, $status); exit($status);
Kernel 原始碼
這裡在執行 handler 裡面有個我們要注意的東西就是 bootstraps,這東東你可以想成它就是要完成一件事情所需要做的事情,其中我們要看的為 RegisterProviders 這個 bootstrap。
<?php namespace Illuminate\Foundation\Console; class Kernel implements KernelContract { /** * The bootstrap classes for the application. * * @var array */ protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\SetRequestForConsole::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; /** * Run the console application. * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output * @return int */ public function handle($input, $output = null) { try { // 執行每個 boostrap $this->bootstrap(); return $this->getArtisan()->run($input, $output); } catch (Exception $e) { $this->reportException($e); $this->renderException($output, $e); return 1; } catch (Throwable $e) { $e = new FatalThrowableError($e); $this->reportException($e); $this->renderException($output, $e); return 1; } } /** * Bootstrap the application for artisan commands. * * @return void */ public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } $this->app->loadDeferredProviders(); if (! $this->commandsLoaded) { $this->commands(); $this->commandsLoaded = true; } } }
RegisterProviders 原始碼
RegisterProviders 就是一個定義好的 boostrap 類別,它主要就是呼叫 container 來註冊 service provider。
<?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; class RegisterProviders { /** * Bootstrap the given application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */ public function bootstrap(Application $app) { $app->registerConfiguredProviders(); } }
Container registerConfiguredProviders 原始碼
這一段原始碼中,laravel 會先將在 config/app.php 裡面有的 provider 先組合出包含 namespace 的 service provider 陣列,然後最在在丟給 ProviderRepository 的 load 方法來進行讀取。
<?php /** * Register all of the configured providers. * * @return void */ public function registerConfiguredProviders() { $providers = Collection::make($this->config['app.providers']) ->partition(function ($provider) { return Str::startsWith($provider, 'Illuminate\\'); }); // 這裡看起來應該是去拿所有 packet 裡面的 provider $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray()); }
ProviderRepository 原始碼
這一段程式碼就是實際執行 service proivder 裡面方法的地方,它的執行流程如下。
- 讀取 boostrap/cache/services.php。
- 判斷是否重新產生 cache 檔。
- 從 cache 檔中判斷每個 servcie provider 是那一類型,然後分別執行。
基本上 cache 檔中 service provider 被分為三種類別,它們的特點如下 :
- when : 當某個事件被執行的時後,才會執行 service provider。
- eager : 直接執行 service provider。
- deferred : 等到要執行 make 前,才會執行 service provider。
<?php /** * Register the application service providers. * * @param array $providers * @return void */ public function load(array $providers) { // 讀取 boostrap/cache/services.php $manifest = $this->loadManifest(); // 產生 boostrap/cache/services.php if ($this->shouldRecompile($manifest, $providers)) { $manifest = $this->compileManifest($providers); } foreach ($manifest['when'] as $provider => $events) { $this->registerLoadEvents($provider, $events); } foreach ($manifest['eager'] as $provider) { $this->app->register($provider); } $this->app->addDeferredServices($manifest['deferred']); }
備註
關於 boostrap/cache/services.php
這個檔案基本上組合如下圖,共有分為四個部份,它們的功用如下。
- provider : 包含所有 config/app.php 裡有註冊的 service providers,基本上它的功用就是用來判斷 cache 檔案要不要進行修。
- eager : service provider 類型,它會在 load 時執行 service provider。
- deferred : service provider 類型,它會在 make 時執行 service provider。
- when : service provider 類型,它會在收到某個事件時執行 service provider。
將 servcie provider 修改為 deferred 時記得砍 cache
Laravel 在判斷直接使用 cache 時有兩個必要條件 :
- 有 cache 檔。
- cache 檔的 providers 於 config/app.php 的 providers 是相同的。
所以如果你這時修改了某個 provider 的 deferred 時,依然符合上述兩個條件,因為你只是改變 proivder 的屬性而不是名稱,因此還會繼續使用 cache 檔。
所以這時記得要砍掉 cache 檔讓它進行重建。
參考資料
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 理解原型其实是理解原型链
- 要理解深度学习,必须突破常规视角去理解优化
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 荐 【C++100问】深入理解理解顶层const和底层const
- 深入理解 HTTPS
- 深入理解 HTTPS
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
王牌创业者:风口游戏
澈言 / 百花洲文艺出版社 / 2018-2 / 48.00
《王牌创业者:风口游戏》是一部围绕互联网创业展开的商战小说:故事的主人公莫飞是“毕业即创业”的当代年轻创业者的典型代表,他大学在校时就凭借创业项目拿到了天使融资,创业几年后,当产品估值越做越大时,他却忽然遭遇创业伙伴及投资人的联手陷害,失去了自己一手建立的公司。 此时, 莫飞的女友林姿参加了一场声势浩大的创业比赛,并一举夺魁,直进决赛。可在决赛中,突如其来的一场新闻事件让她名誉扫地。最终,为......一起来看看 《王牌创业者:风口游戏》 这本书的介绍吧!
XML、JSON 在线转换
在线XML、JSON转换工具
RGB HSV 转换
RGB HSV 互转工具