Начиная с 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
смысл и роль. Быть в курсе!
Продолжение следует