См. исходный код Laravel, чтобы понять загрузку ServiceProvider.

задняя часть PHP Laravel Bootstrap

Начиная с Laravel, мы не можем обойти концепцию ServiceProvider. Фреймворк Laravel изобилует ServiceProviders — AppServiceProvider, AuthServiceProvider, BroadcastServiceProvider, EventServiceProvider и RouteServiceProvider, и это лишь некоторые из них.

И когда мы устанавливаем какие-то сторонние плагины, у нас время от времени появляется это предложение, добавляя ****ServiceProvider в массив провайдеров config/app.php.

Разве мы не хотим знать, как Laravel загружает эти ServiceProviders?

Так что сегодня посмотрим, как реализована загрузка из работы исходников?

Смотрите класс приложений

Мы все знаем, что входной файл 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__.'/../')Объект приложения.

Этот объект является «контейнером» 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()Методы - «ядра ядра Laravave» - то есть на основе ввода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);

Это также раскрывает три элемента сетевого запроса: запрос, маршрутизатор и ответ.

/**
 * 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,
    ];

Функции этих шести классов в основном: загрузка переменных среды, конфигурация, обработка исключений, регистрация фасадов, и, наконец, нас интересует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.phpproviders, все упомянутые выше третьи лица удовлетворяют требованиям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);
    });
}

После регистрации можем посмотреть способ загрузки.

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смысл и роль. Быть в курсе!

Продолжение следует