内容简介:上一篇文章『其中上面的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
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Is Parallel Programming Hard, And, If So, What Can You Do About
Paul E. McKenney
The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!