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

本文字数: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',
    ),
  ),
);

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

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

总结

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

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

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

未完待续

原文发布于微信公众号 - coding01(coding01)

原文发表时间:2018-05-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逆向技术

x64内核HOOK技术之拦截进程.拦截线程.拦截模块

            x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么...

5057
来自专栏祝威廉

Kafka Zero-Copy 使用分析

Kafka 我个人感觉是性能优化的典范。而且使用Scala开发,代码写的也很漂亮的。重点我觉得有四个

2072
来自专栏JadePeng的技术博客

XNginx - nginx 集群可视化管理工具

之前团队的nginx管理,都是运维同学每次去修改配置文件,然后重启,非常不方便,一直想找一个可以方便管理nginx集群的工具,翻遍web,未寻到可用之物,于是自...

1.9K4
来自专栏岑玉海

hbase源码系列(十)HLog与日志恢复

HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面...

4048
来自专栏生信宝典

分子对接简明教程 (二)

用PyMOL展示配体和受体相互作用的原子和氢键 为了简化展示过程,我们设计了一个pml脚本 (脚本内有很详细的解释),只需要修改脚本里面受体和配体的名字,然后在...

5105
来自专栏分布式系统进阶

ReplicaManager源码解析1-消息同步线程管理

基本上就是作三件事: 构造FetchRequest, 同步发送FetchRequest并接收FetchResponse, 处理FetchResponse, 这三...

1632
来自专栏黑泽君的专栏

day50_BOS项目_02

我们再补上IUserDao和UserDaoImpl的示例代码: IUserDao.java

852
来自专栏向治洪

Hibernate入门

Hibernate是什么     Hibernate是一个轻量级的ORMapping框架     ORMapping原理(Object Relational M...

2126
来自专栏芋道源码1024

熔断器 Hystrix 源码解析 —— 命令合并执行

本文主要基于 Hystrix 1.5.X 版本 1. 概述 2. HystrixCollapser 2.1 构造方法 2.2 执行命令方式 2.3 核心方法 3...

3817
来自专栏菩提树下的杨过

Oracle中使用Entity Framework 6.x Code-First方式开发

去年写过一篇EF的简单学习笔记,当时EF还不支持Oracle的Code-First开发模式,今天无意又看了下Oracle官网,发现EF6.X已经支持了,并且给出...

2635

扫码关注云+社区

领取腾讯云代金券