使用 Laravel 開始,咱們老是繞不開 ServiceProvider 這個概念。在 Laravel 框架裏處處充斥着 ServiceProvider —— AppServiceProvider、AuthServiceProvider、BroadcastServiceProvider、EventServiceProvider 和 RouteServiceProvider 等等。php
還有咱們在安裝一些第三方插件時,都時不時有這麼句話,將****ServiceProvider 加入到 config/app.php 的 providers 數組中。laravel
難道我們就不想知道 Laravel 是如何加載這些 ServiceProviders 的嗎?web
因此今天從源代碼的運行來看看是怎麼實現加載的?json
複製代碼
咱們都知道 Laravel 的入口文件在 public/index.php
bootstrap
<?php
...
require __DIR__.'/../vendor/autoload.php';
...
$app = require_once __DIR__.'/../bootstrap/app.php';
/* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
複製代碼
這裏先看載入 require_once __DIR__.'/../bootstrap/app.php'
,建立 app
對象。數組
<?php
/* |-------------------------------------------------------------------------- | Create The Application |-------------------------------------------------------------------------- | | The first thing we will do is create a new Laravel application instance | which serves as the "glue" for all the components of Laravel, and is | the IoC container for the system binding all of the various parts. | */
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
/* |-------------------------------------------------------------------------- | Bind Important Interfaces |-------------------------------------------------------------------------- | | Next, we need to bind some important interfaces into the container so | we will be able to resolve them when needed. The kernels serve the | incoming requests to this application from both the web and CLI. | */
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/* |-------------------------------------------------------------------------- | Return The Application |-------------------------------------------------------------------------- | | This script returns the application instance. The instance is given to | the calling script so we can separate the building of the instances | from the actual running of the application and sending responses. | */
return $app;
複製代碼
直接返回的是 new Illuminate\Foundation\Application( realpath(__DIR__.'/../')
Application 對象。bash
這個對象就是 Laravel 的「容器」。咱們開始看看 Application
是怎麼發現 ServiceProvider 的?網絡
/** * Create a new Illuminate application instance. * * @param string|null $basePath * @return void */
public function __construct($basePath = null) {
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
複製代碼
主要是完成這四個方法。第一個和最後一個方法暫且不表;咱們主要看:app
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
composer
registerBaseBindings()
/** * Register the basic bindings into the container. * * @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()
));
}
複製代碼
前兩個主要是綁定 Application
對象和 Container
對象。重點分析 PackageManifest
對象以前,咱們看看 $this->getCachedPackagesPath()
這個函數:
/** * Get the path to the cached packages.php file. * * @return string */
public function getCachedPackagesPath() {
return $this->bootstrapPath().'/cache/packages.php';
}
複製代碼
這個就是咱們 bootstrap/cache/packages.php
文件,這個文件的內容咱們看看:
<?php return array (
'fideloper/proxy' =>
array (
'providers' =>
array (
0 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
),
),
'encore/laravel-admin' =>
array (
'providers' =>
array (
0 => 'Encore\\Admin\\AdminServiceProvider',
),
'aliases' =>
array (
'Admin' => 'Encore\\Admin\\Facades\\Admin',
),
),
'laravel/tinker' =>
array (
'providers' =>
array (
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
),
),
'rebing/graphql-laravel' =>
array (
'providers' =>
array (
0 => 'Rebing\\GraphQL\\GraphQLServiceProvider',
),
'aliases' =>
array (
'GraphQL' => 'Rebing\\GraphQL\\Support\\Facades\\GraphQL',
),
),
'tymon/jwt-auth' =>
array (
'aliases' =>
array (
'JWTAuth' => 'Tymon\\JWTAuth\\Facades\\JWTAuth',
'JWTFactory' => 'Tymon\\JWTAuth\\Facades\\JWTFactory',
),
'providers' =>
array (
0 => 'Tymon\\JWTAuth\\Providers\\LaravelServiceProvider',
),
),
'noh4ck/graphiql' =>
array (
'providers' =>
array (
0 => 'Graphiql\\GraphiqlServiceProvider',
),
),
'rollbar/rollbar-laravel' =>
array (
'providers' =>
array (
0 => 'Rollbar\\Laravel\\RollbarServiceProvider',
),
'aliases' =>
array (
'Rollbar' => 'Rollbar\\Laravel\\Facades\\Rollbar',
),
),
'fanly/log2dingding' =>
array (
'providers' =>
array (
0 => 'Fanly\\Log2dingding\\FanlyLog2dingdingServiceProvider',
),
),
);
複製代碼
經過分析,能夠看出這個文件主要是放着咱們本身引入第三方的 ServiceProviders
和 aliases
,咱們對照項目根路徑的 composer.json
你就能夠證明了:
"require": {
"php": ">=7.0.0",
"encore/laravel-admin": "1.5.*",
"fanly/log2dingding": "^0.0.2",
"fideloper/proxy": "~3.3",
"guzzlehttp/guzzle": "^6.3",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0",
"noh4ck/graphiql": "@dev",
"overtrue/phplint": "^1.1",
"rebing/graphql-laravel": "^1.10",
"rollbar/rollbar-laravel": "^2.3",
"tymon/jwt-auth": "^1.0@dev"
},
複製代碼
至於這個 bootstrap/cache/packages.php
文件內容怎麼產生的,咱們後面會說到。
咱們回來分析 new PackageManifest()
,類中的幾個函數的做用,顯而易見:
/** * 這個函數是將 package.php 文件的插件數組的 `providers`整合成一個 Collection 輸出 */
public function providers() {}
/** * 插件中的 `aliases` 整合成 Collection 輸出。 * * @return array */
public function aliases() {}
/** * 這個是關鍵,從 verdor/composer/installed.json 文件中獲取全部經過 composer 安裝的插件數組,而後再經過用 `name` 綁定對應的 `ServiceProvider`,構成數組,而後再排除每一個插件的 `dont-discover` 和項目 composer.json 填入的 `dont-discover`。 * 這也是 Laravel 包自動發現的核心所在。 * */
public function build() {
$packages = [];
if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) {
$packages = json_decode($this->files->get($path), true);
}
$ignoreAll = in_array('*', $ignore = $this->packagesToIgnore());
$this->write(collect($packages)->mapWithKeys(function ($package) {
return [$this->format($package['name']) => $package['extra']['laravel'] ?? []];
})->each(function ($configuration) use (&$ignore) {
$ignore = array_merge($ignore, $configuration['dont-discover'] ?? []);
})->reject(function ($configuration, $package) use ($ignore, $ignoreAll) {
return $ignoreAll || in_array($package, $ignore);
})->filter()->all());
}
/** * 最後就把上面的知足的 ServiceProvider 寫入到文件中,就是上文咱們說的 `bootstrap/cache/packages.php` */
protected function write(array $manifest) {}
複製代碼
到目前爲止,咱們找到了須要加載的第三方的 ServiceProvider
了。
registerBaseServiceProviders()
接下來咱們看看這個 registerBaseServiceProviders()
方法了。
/** * 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));
}
複製代碼
這裏主要註冊三個 ServiceProvider
,具體功能後面詳聊。
Kernel
咱們簡單過了一遍 new Application
,咱們回到 index.php
繼續往下看:
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
複製代碼
這個 $kernel
就是 Laravel 的「核」,而 $kernel->handle()
方法就是 Laravel 的「核中之核」了 —— 即,根據輸入的 Request
,輸出 response
。完成請求到響應的過程。
咱們進入 $kernel->handle()
方法。
/** * Handle an incoming HTTP request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */
public function handle($request) {
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
複製代碼
排除其它「干擾」東西,眼睛關注到這行代碼:
$response = $this->sendRequestThroughRouter($request);
複製代碼
這也暴露了,網絡請求的三要素:request、router 和 response。
/** * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */
protected function sendRequestThroughRouter($request) {
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
複製代碼
但今天咱們說的不是考慮執行的問題,咱們須要知道何時加載咱們的 ServiceProviders
因此在 return
執行以前的代碼 ($this->bootstrap();) 就是初始化 ServiceProviders
等信息的過程
/** * Bootstrap the application for HTTP requests. * * @return void */
public function bootstrap() {
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
// Application 類:
/** * Run the given array of bootstrap classes. * * @param array $bootstrappers * @return void */
public function bootstrapWith(array $bootstrappers) {
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
複製代碼
到此咱們知道,實際上遍歷執行 $bootstrappers->bootstrap($this)
此時咱們看看 $bootstrappers
:
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\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
複製代碼
這六個類的做用主要是:加載環境變量、config、異常處理、註冊 facades、和最後咱們關注的 ServiceProvider
的 register
和 boot
。
咱們分別來看看。
RegisterProviders
class RegisterProviders {
/** * Bootstrap the given application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */
public function bootstrap(Application $app) {
$app->registerConfiguredProviders();
}
}
複製代碼
其實是調用 Application
的 registerConfiguredProviders()
:
/** * 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\\');
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
複製代碼
這個就厲害了。加載全部已配置的 ServiceProvider
,主要包含了在配置文件 config/app.php 裏的 providers
,上文講述的第三方全部知足的 ServiceProviders
,以及在 boostrap/cached/service.php
中的全部 Providers
。
最後執行 ProviderRepository::load
方法,進行遍歷 register
:
/** * Register the application service providers. * * @param array $providers * @return void */
public function load(array $providers) {
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}
$this->app->addDeferredServices($manifest['deferred']);
}
/** * Register the load events for the given provider. * * @param string $provider * @param array $events * @return void */
protected function registerLoadEvents($provider, array $events) {
if (count($events) < 1) {
return;
}
$this->app->make('events')->listen($events, function () use ($provider) {
$this->app->register($provider);
});
}
複製代碼
register 以後,咱們能夠看看 boot 方法了。
BootProviders
class BootProviders {
/** * Bootstrap the given application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */
public function bootstrap(Application $app) {
$app->boot();
}
}
複製代碼
咱們按圖索驥:
/** * Boot the application's service providers. * * @return void */
public function boot() {
if ($this->booted) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
...
/** * 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']);
}
}
複製代碼
也就是說遍歷執行全部 ServiceProviders
的 boot()
(前提是該 ServiceProvider
有定義該方法)。
經過分析 index.php
執行過程,發現 Application
主要是發現各類 ServiceProviders
,而 $kernel
更多的是在處理 Request
請求以前,把全部的 ServiceProvider
進行註冊 (register
),而後再 boot
。把全部的 ServiceProviders
裝載進系統內存中,供處理各式各樣的 Request
使用。
而每個 ServiceProvider
各司其職,負責各自不一樣的職能,來知足 Laravel 系統的各類須要。
下文咱們將重點說說這些 ServiceProvider
的含義和做用。敬請期待!
未完待續