使用 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',  
    ),  
  ),  
);

通过分析,可以看出这个文件主要是放着我们自己引入第三方的 ServiceProvidersaliases,我们对照项目根路径的 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、和最后我们关注的 ServiceProviderregisterboot

我们分别来看看。

RegisterProviders

class RegisterProviders  
{  
    /**  
     * Bootstrap the given application.     
     *     
     * @param  \Illuminate\Contracts\Foundation\Application  $app  
     * @return void  
     */  
    public function bootstrap(Application $app)  
    {  
        $app->registerConfiguredProviders();  
    }  
}

实际上是调用 ApplicationregisterConfiguredProviders()

/**  
 * 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();  
}

也就是说遍历执行所有 ServiceProvidersboot() (前提是该 ServiceProvider 有定义该方法)。

总结

通过分析 index.php 执行过程,发现 Application 主要是发现各种 ServiceProviders,而 $kernel 更多的是在处理 Request请求之前,把所有的 ServiceProvider进行注册 (register),然后再 boot。把所有的 ServiceProviders装载进系统内存中,供处理各式各样的 Request 使用。

而每一个 ServiceProvider 各司其职,负责各自不同的职能,来满足 Laravel 系统的各种需要。

下文我们将重点说说这些 ServiceProvider 的含义和作用。敬请期待!

未完待续

如果觉得文章内容对您有用
打赏

Buy Me A Coffee