使用 Laravel 开始,我们总是绕不开 ServiceProvider 这个概念。在 Laravel 框架里到处充斥着 ServiceProvider —— AppServiceProvider、AuthServiceProvider、BroadcastServiceProvider、EventServiceProvider 和 RouteServiceProvider 等等。
还有我们在安装一些第三方插件时,都时不时有这么句话,将ServiceProvider 加入到 config/app.php 的 providers 数组中。
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers... */
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers... */
/* * Application Service Providers... */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Coding01\GptAPI\GptServiceProvider::class,
],
难道咱们就不想知道 Laravel 是如何加载这些 ServiceProviders 的吗?
所以今天从源代码的运行来看看是怎么实现加载的?
看 Application 类
我们都知道 Laravel 的入口文件在 public/index.php
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->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(
$_ENV['APP_BASE_PATH'] ?? dirname(__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()
Application 对象。 这个对象就是 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();
}
主要是完成这四个方法。第一个和最后一个方法暂且不表;我们主要看:
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
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->singleton(Mix::class);
$this->singleton(PackageManifest::class, fn () => 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->normalizeCachePath('APP_PACKAGES_CACHE', 'cache/packages.php');
}
如果没有配置 APP_PACKAGES_CACHE
缓存路径,那就使用默认的 bootstrap/cache/packages.php
文件,这个文件的内容我们看看:
<?php return array (
'laravel/sail' =>
array (
'providers' =>
array (
0 => 'Laravel\\Sail\\SailServiceProvider',
),
),
'laravel/sanctum' =>
array (
'providers' =>
array (
0 => 'Laravel\\Sanctum\\SanctumServiceProvider',
),
),
'laravel/scout' =>
array (
'providers' =>
array (
0 => 'Laravel\\Scout\\ScoutServiceProvider',
),
),
'laravel/tinker' =>
array (
'providers' =>
array (
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
),
),
'nesbot/carbon' =>
array (
'providers' =>
array (
0 => 'Carbon\\Laravel\\ServiceProvider',
),
),
'nunomaduro/collision' =>
array (
'providers' =>
array (
0 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
),
),
'nunomaduro/termwind' =>
array (
'providers' =>
array (
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
),
),
'orchid/blade-icons' =>
array (
'providers' =>
array (
0 => 'Orchid\\Icons\\IconServiceProvider',
),
),
'orchid/platform' =>
array (
'providers' =>
array (
0 => 'Orchid\\Platform\\Providers\\FoundationServiceProvider',
),
'aliases' =>
array (
'Alert' => 'Orchid\\Support\\Facades\\Alert',
'Dashboard' => 'Orchid\\Support\\Facades\\Dashboard',
),
),
'pestphp/pest-plugin-laravel' =>
array (
'providers' =>
array (
0 => 'Pest\\Laravel\\PestServiceProvider',
),
),
'spatie/laravel-ignition' =>
array (
'providers' =>
array (
0 => 'Spatie\\LaravelIgnition\\IgnitionServiceProvider',
),
'aliases' =>
array (
'Flare' => 'Spatie\\LaravelIgnition\\Facades\\Flare',
),
),
'spatie/laravel-ray' =>
array (
'providers' =>
array (
0 => 'Spatie\\LaravelRay\\RayServiceProvider',
),
),
'tabuna/breadcrumbs' =>
array (
'providers' =>
array (
0 => 'Tabuna\\Breadcrumbs\\BreadcrumbsServiceProvider',
),
'aliases' =>
array (
'Breadcrumbs' => 'Tabuna\\Breadcrumbs\\Breadcrumbs',
),
),
'watson/active' =>
array (
'providers' =>
array (
0 => 'Watson\\Active\\ActiveServiceProvider',
),
'aliases' =>
array (
'Active' => 'Watson\\Watson\\Facades\\Active',
),
),
);
通过分析,可以看出这个文件主要是放着我们自己引入第三方的 ServiceProviders
和 aliases
,我们对照项目根路径的 composer.json
你就可以证实了:
"require": {
"php": "^8.1",
"firebase/php-jwt": "^6.5",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.18.0",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"orchid/platform": "^14.17",
"textalk/websocket": "^1.6"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"orchestra/testbench": "^8.0",
"pestphp/pest": "^2.16",
"pestphp/pest-plugin-laravel": "v2.x",
"phpunit/phpunit": "^10.0",
"spatie/laravel-ignition": "^2.0"
},
至于这个 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')) {
$installed = json_decode($this->files->get($path), true);
$packages = $installed['packages'] ?? $installed;
}
$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(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);
这个 $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)
{
$this->requestStartedAt = Carbon::now();
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new 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 string[] $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
到此我们知道,实际上遍历执行 $bootstrappers->bootstrap($this)
此时我们看看 $bootstrappers
:
/**
* The bootstrap classes for the application.
*
* @var string[]
*/
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->make('config')->get('app.providers'))
->partition(fn ($provider) => str_starts_with($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 之后,我们可以看看 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->isBooted()) {
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 void
*/
protected function bootProvider(ServiceProvider $provider)
{
$provider->callBootingCallbacks();
if (method_exists($provider, 'boot')) {
$this->call([$provider, 'boot']);
}
$provider->callBootedCallbacks();
}
也就是说遍历执行所有 ServiceProviders
的 boot()
(前提是该 ServiceProvider
有定义该方法)。
总结
通过分析 index.php
执行过程,发现 Application
主要是发现各种 ServiceProviders
,而 $kernel
更多的是在处理 Request
请求之前,把所有的 ServiceProvider
进行注册 (register
),然后再 boot
。把所有的 ServiceProviders
装载进系统内存中,供处理各式各样的 Request
使用。
而每一个 ServiceProvider
各司其职,负责各自不同的职能,来满足 Laravel 系统的各种需要。
下文我们将重点说说这些 ServiceProvider
的含义和作用。敬请期待!
未完待续