Для тех, кто не использовал активно поставщиков услуг в Laravel, это мистический «термин»: какую «услугу» они на самом деле «предоставляют» и как именно все это работает? Я объясню это в этой статье.
Поставщики услуг Laravel по умолчанию
Начнем с поставщиков услуг по умолчанию, включенных в Laravel, все они находятся в папке app/Providers
:
- AppServiceProvider
- AuthServiceProvider
- BroadcastServiceProvider
- EventServiceProvider
- RouteServiceProvider
Все они представляют собой классы PHP, каждый из которых связан со своей темой: общее «приложение», аутентификация, трансляция, события и маршруты. Но все они имеют одну общую черту: метод boot()
.
Внутри этого метода вы можете написать любой код, связанный с одним из этих разделов: аутентификацией, событиями, маршрутами и т. д. Другими словами, поставщики услуг — это просто классы для регистрации некоторых глобальных функций.
Они разделены как «поставщики», потому что они выполняются очень рано в жизненном цикле приложения, поэтому здесь удобно что-то глобальное, прежде чем сценарий выполнения дойдет до моделей или контроллеров.
Наибольшая функциональность содержится в RouteServiceProvider, давайте взглянем на его код:
class RouteServiceProvider extends ServiceProvider
{
public const HOME = '/dashboard';
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
}
Это класс, в котором настраиваются файлы маршрутов с включенными по умолчанию routes/web.php
и routes/api.php
.Обратите внимание, что для API также существуют разные конфигурации: префикс конечной точки /api
и промежуточное ПО api
для всех маршрутов.
Вы можете редактировать этих поставщиков услуг по своему усмотрению, они не находятся в папке /vendor
. Типичная настройка этого файла происходит, когда у вас много маршрутов и вы хотите разделить их в своем пользовательском файле.Вы создаете routes/auth.php
и помещаете туда маршруты, а затем «включаете» этот файл в методе boot()
в RouteServiceProvider
, просто добавьте третье предложение:
`Route::middleware('web') // or maybe you want another middleware?
->group(base_path('routes/auth.php'));
Другие поставщики услуг по умолчанию имеют другие функции, вы можете проанализировать их самостоятельно.За исключением AppServiceProvider
, он пуст, как заполнитель для добавления любого кода, связанного с некоторыми глобальными настройками приложения.
Одним из популярных примеров добавления кода в AppServiceProvider
является отключение ленивая загрузка в Eloquent.Для этого вам просто нужно добавить две строки в Метод загрузки()
:
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;
public function boot()
{
Model::preventLazyLoading(! $this->app->isProduction());
}
Это вызовет исключение, если какая-либо модель отношений не загружена, что вызывает так называемую проблему запроса N+1 с производительностью.
Когда казнят поставщиков услуг?
Если вы посмотрите на официальную документацию о жизненном цикле запросов, то увидите, что в самом начале выполняются следующие действия:
- public/index.php
- bootstrap/app.php
- app/Http/Kernel.php and its Middlewares
- Service Providers: exactly our topic of this article
Какие поставщики загружаются? Он определен в файле config/app.массив php
:
return [
// ... other configuration values
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
// ... other framework providers from /vendor
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* PUBLIC Service Providers - the ones we mentioned above
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
];
Как видите, в папке /vendor
есть список поставщиков услуг, не являющихся общедоступными, их не нужно трогать/редактировать. Те, которые нас интересуют, находятся внизу, а BroadcastServicerProvider
отключен по умолчанию, вероятно, потому, что он редко используется.
Все эти поставщики услуг выполняются сверху вниз, повторяя этот список дважды.
Первая итерация ищет необязательный метод register()
, который можно использовать для инициации чего-либо перед методом boot()
. Я никогда не использовал его в своем опыте.
Затем во второй итерации выполняется метод boot()
всех поставщиков. Опять же, один за другим, сверху вниз по массиву 'providers'
.
И затем, после обработки всех поставщиков услуг, Laravel переходит к разбору маршрута, выполнению контроллера, использованию моделей и т. д.
Создайте своего поставщика услуг
В дополнение к существующим файлам по умолчанию вы можете легко создать своего поставщика услуг, связанного с некоторыми другими темами, отличными от стандартных, таких как auth/event/routes.
Вполне типичным примером является конфигурация, связанная с представлениями Blade.Если вы хотите создать свою директиву Blade, вы можете добавить этот код в метод boot()
любого поставщика услуг, включая метод AppServiceProvider
по умолчанию, но довольно часто разработчики создают отдельный ViewServiceProvider .
Вы можете сгенерировать его с помощью этой команды:
php artisan make:provider ViewServiceProvider
Будет создан шаблон по умолчанию:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}
Вы можете удалить метод register()
и внутри boot()
добавить код директивы Blade:
use Illuminate\Support\Facades\Blade;
public function boot()
{
Blade::directive('datetime', function ($expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
}
Еще один пример ViewServiceProvider касается View Composers, вот фрагмент из официальной документации Laravel:
use App\View\Composers\ProfileComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
// Using class based composers...
View::composer('profile', ProfileComposer::class);
// Using closure based composers...
View::composer('dashboard', function ($view) {
//
});
}
}
Для выполнения этот новый провайдер должен быть добавлен в массив провайдеров в config/app.php
, как указано выше:
return [
// ... other configuration values
'providers' => [
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
// Add your provider here
App\Providers\ViewServiceProvider::class,
],
];
Примеры из проектов с открытым исходным кодом
Наконец, я хочу упомянуть несколько примеров из свободно доступных проектов Laravel.
1. spatie/freek.
namespace App\Providers;
use App\Http\Components\AdComponent;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class BladeComponentServiceProvider extends ServiceProvider
{
public function boot()
{
Blade::component('ad', AdComponent::class);
Blade::component('front.components.inputField', 'input-field');
Blade::component('front.components.submitButton', 'submit-button');
Blade::component('front.components.textarea', 'textarea');
Blade::component('front.components.textarea', 'textarea');
Blade::component('front.components.shareButton', 'share-button');
Blade::component('front.components.lazy', 'lazy');
Blade::component('front.components.postHeader', 'post-header');
Blade::component('front.layouts.app', 'app-layout');
}
}
namespace App\Providers;
use App\Helpers\CollectionHelper;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
class MacroServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
if (! Collection::hasMacro('sortByCollator')) {
Collection::macro('sortByCollator', function ($callback, $options = \Collator::SORT_STRING, $descending = false) {
/** @var Collection */
$collect = $this;
return CollectionHelper::sortByCollator($collect, $callback, $options, $descending);
});
}
if (! Collection::hasMacro('groupByItemsProperty')) {
Collection::macro('groupByItemsProperty', function ($property) {
/** @var Collection */
$collect = $this;
return CollectionHelper::groupByItemsProperty($collect, $property);
});
}
if (! Collection::hasMacro('mapUuid')) {
Collection::macro('mapUuid', function () {
/** @var Collection */
$collect = $this;
return $collect->map(function ($item) {
return $item->uuid;
})->toArray();
});
}
}
}
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
use App\Helpers\FileUpload\UploadComponents;
class DashboardComponentsServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Blade::directive('uploadForm', function () {
$component = UploadComponents::getUploadForm();
$html = '<?php echo \'' . $component . '\'; ?>';
return ('<?php echo "' . $component . '"; ?>');
});
}
}