cover_image

看 Laravel 源代码了解 ServiceProvider 的加载过程

叶梅树 coding01 2018年05月18日 16:13
图片
本文字数:4832,大概需要 9.66 分钟。

使用 Laravel 开始,我们总是绕不开 ServiceProvider 这个概念。在 Laravel 框架里到处充斥着 ServiceProvider —— AppServiceProvider、AuthServiceProvider、BroadcastServiceProvider、EventServiceProvider 和 RouteServiceProvider 等等。

还有我们在安装一些第三方插件时,都时不时有这么句话,将****ServiceProvider 加入到 config/app.php 的 providers 数组中。

图片

难道咱们就不想知道 Laravel 是如何加载这些 ServiceProviders 的吗?

所以今天从源代码的运行来看看是怎么实现加载的?

看 Application 类


 

我们都知道 Laravel 的入口文件在 public/index.php

<?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 对象。

这个对象就是 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->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 的含义和作用。敬请期待!

未完待续


coding01 期待您继续关注图片

PHP 框架知识集 · 目录
上一篇简单两步就能将 Laravel Log 信息发到其他平台上下一篇看 Laravel 源代码了解 Container
继续滑动看下一个
coding01
向上滑动看下一个