Laravel: bootstrap/app.php, providers.php и сервис-провайдеры
Glossary overview

Laravel: bootstrap/app.php, providers.php и сервис-провайдеры

Как Laravel запускается

Когда приходит HTTP-запрос или запускается artisan-команда, всё начинается с файла public/index.php. Он делает одно — подключает bootstrap/app.php. Это точка входа, где рождается приложение.

<?php

use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
    require $maintenance;
}

// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';

// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';

$app->handleRequest(Request::capture());

bootstrap/app.php — сборка приложения

Этот файл создаёт экземпляр Application и настраивает три вещи: роутинг, middleware и обработку ошибок.

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->redirectGuestsTo('/admin/login');
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

Что тут происходит

Application::configure()создаёт объект приложения и указывает корневую папку проекта. От неё Laravel вычисляет все остальные пути: app/, config/, storage/ и т.д.

withRouting() — подключает файлы маршрутов. Можно добавить свои или убрать ненужные:

->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    apiPrefix: 'api/v1',  // изменить префикс API
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    then: function () {
        // дополнительные файлы маршрутов
        Route::prefix('admin')
            ->middleware('auth')
            ->group(base_path('routes/admin.php'));
    },
)

withMiddleware() — настройка middleware. Здесь можно добавить глобальные middleware, назначить их на группы или исключить:

->withMiddleware(function (Middleware $middleware) {
    // добавить глобальный middleware
    $middleware->append(EnsureJsonResponse::class);

    // добавить middleware в группу web
    $middleware->web(append: [
        TrackVisits::class,
    ]);

    // добавить middleware в группу api
    $middleware->api(prepend: [
        RateLimiter::class,
    ]);

    // алиасы для middleware
    $middleware->alias([
        'admin' => EnsureUserIsAdmin::class,
        'subscribed' => EnsureUserIsSubscribed::class,
    ]);

    // исключить middleware
    $middleware->remove(TrimStrings::class);
})

withExceptions()настройка обработки ошибок. Можно кастомизировать рендеринг и репортинг исключений:

->withExceptions(function (Exceptions $exceptions) {
    // не логировать определённые исключения
    $exceptions->dontReport([
        PaymentDeclinedException::class,
    ]);

    // кастомный рендеринг
    $exceptions->render(function (NotFoundHttpException $e) {
        return response()->view('errors.custom-404', [], 404);
    });

    // отправить в Sentry
    $exceptions->report(function (Throwable $e) {
        if (app()->bound('sentry')) {
            app('sentry')->captureException($e);
        }
    });
})

Что было раньше

До Laravel 11 (Laravel 10 и ниже) bootstrap/app.php выглядел минимально — просто создавал объект Application. А настройка middleware, маршрутов и ошибок была разбросана по app/Http/Kernel.php, app/Exceptions/Handler.php и RouteServiceProvider. Теперь всё собрано в одном месте.

bootstrap/providers.php — список провайдеров

Этот файл появился в Laravel 11. Он возвращает массив сервис-провайдеров, которые нужно загрузить:

<?php

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\Filament\AdminPanelProvider::class,
    App\Providers\HorizonServiceProvider::class,
];

Раньше этот список жил в config/app.php в ключе 'providers'. Теперь его вынесли в отдельный файл для наглядности.

Когда добавлять провайдера сюда

Каждый раз, когда создаёшь новый провайдер:

php artisan make:provider PaymentServiceProvider

Он не подключится автоматически — нужно добавить в providers.php:

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\PaymentServiceProvider::class, // добавили
];

Пакеты (Telescope, Horizon, Sanctum и т.д.) регистрируют свои провайдеры через package discovery — их вручную добавлять не нужно.

Сервис-провайдеры — что это и зачем

Сервис-провайдер — это класс, который подготавливает часть приложения к работе. Он говорит Laravel: «вот такие сервисы существуют, вот как их создавать, вот какие события слушать».

Каждый провайдер — наследник Illuminate\Support\ServiceProvider и может содержать два метода:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Регистрация сервисов в контейнере.
     * Здесь только bind/singleton, никакой другой логики.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Загрузка: роуты, вьюхи, миграции, события, команды.
     * Вызывается ПОСЛЕ register() всех провайдеров.
     */
    public function boot(): void
    {
        // ...
    }
}

register() — привязка к контейнеру

Метод register() нужен для одного — сказать контейнеру, как создавать сервисы:

public function register(): void
{
    // Привязка интерфейса к реализации
    $this->app->bind(
        PaymentGatewayInterface::class,
        StripePaymentGateway::class
    );

    // Singleton — один экземпляр на весь запрос
    $this->app->singleton(CartService::class, function ($app) {
        return new CartService(
            $app->make(ProductRepository::class),
            config('cart.tax_rate')
        );
    });

    // Подключение конфига пакета
    $this->mergeConfigFrom(
        __DIR__.'/../../config/payment.php', 'payment'
    );
}

Важное правило: в register() нельзя использовать другие сервисы, потому что они могут быть ещё не зарегистрированы. Только bind, singleton, mergeConfigFrom.

boot() — всё остальное

Метод boot() вызывается, когда все провайдеры уже отработали register(). Здесь можно использовать любые сервисы:

public function boot(): void
{
    // Загрузить маршруты
    $this->loadRoutesFrom(__DIR__.'/../../routes/payment.php');

    // Загрузить вьюхи
    $this->loadViewsFrom(__DIR__.'/../../resources/views', 'payment');

    // Загрузить миграции
    $this->loadMigrationsFrom(__DIR__.'/../../database/migrations');

    // Опубликовать конфиг
    $this->publishes([
        __DIR__.'/../../config/payment.php' => config_path('payment.php'),
    ]);

    // Слушатели событий
    Event::listen(OrderPlaced::class, ChargePayment::class);

    // Добавить Blade-директиву
    Blade::directive('money', function ($amount) {
        return "<?php echo number_format($amount, 2) . ' ₴'; ?>";
    });

    // Добавить валидацию
    Validator::extend('card_number', function ($attribute, $value) {
        return preg_match('/^\d{16}$/', $value);
    });

    // Настроить модель
    Model::preventLazyLoading(!app()->isProduction());
}

Порядок загрузки

Laravel обрабатывает провайдеры в два прохода:

  1. Вызывает register() у всех провайдеров по порядку.
  2. Вызывает boot() у всех провайдеров по порядку.

Это гарантирует, что к моменту boot() все сервисы уже зарегистрированы в контейнере и доступны.

register() AppServiceProvider
register() AuthServiceProvider
register() PaymentServiceProvider
      ↓ все register() выполнены
boot() AppServiceProvider
boot() AuthServiceProvider
boot() PaymentServiceProvider
      ↓ приложение готово к работе

Практический пример: провайдер для платёжной системы

<?php

namespace App\Providers;

use App\Contracts\PaymentGateway;
use App\Services\StripeGateway;
use App\Services\LiqPayGateway;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Выбор реализации на основе конфига
        $this->app->singleton(PaymentGateway::class, function ($app) {
            return match(config('payment.driver')) {
                'stripe' => new StripeGateway(config('payment.stripe.key')),
                'liqpay' => new LiqPayGateway(
                    config('payment.liqpay.public_key'),
                    config('payment.liqpay.private_key'),
                ),
                default => throw new \RuntimeException('Unknown payment driver'),
            };
        });
    }

    public function boot(): void
    {
        // Публикация конфига
        $this->publishes([
            __DIR__.'/../../config/payment.php' => config_path('payment.php'),
        ], 'payment-config');

        // Маршруты для вебхуков
        $this->loadRoutesFrom(__DIR__.'/../../routes/payment-webhooks.php');
    }
}

Использование в контроллере:

class OrderController extends Controller
{
    // Laravel сам подставит нужную реализацию
    public function store(Request $request, PaymentGateway $gateway)
    {
        $gateway->charge($request->amount);
    }
}

Переключить платёжку — одна строка в .env:

PAYMENT_DRIVER=liqpay

AppServiceProvider — провайдер по умолчанию

Для мелких настроек не нужно создавать отдельный провайдер. Всё можно положить в AppServiceProvider:

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // простые привязки
        $this->app->bind(CurrencyConverter::class, function () {
            return new CurrencyConverter(config('services.exchange_api_key'));
        });
    }

    public function boot(): void
    {
        // глобальные настройки
        Model::preventLazyLoading(!app()->isProduction());
        Model::unguard();

        // пагинация в стиле Bootstrap
        Paginator::useBootstrapFive();

        // макросы для коллекций
        Collection::macro('toUpper', function () {
            return $this->map(fn ($value) => strtoupper($value));
        });
    }
}

Отдельный провайдер имеет смысл, когда логика большая, самостоятельная и может быть вынесена в пакет.

Общая картина запуска

Запрос → public/index.php
           ↓
       bootstrap/app.php          ← создаёт Application
           ↓                         настраивает роуты, middleware, ошибки
       bootstrap/providers.php    ← список провайдеров
           ↓
       register() всех провайдеров  ← привязки к контейнеру
           ↓
       boot() всех провайдеров      ← роуты, вьюхи, события, команды
           ↓
       Middleware pipeline           ← обработка запроса
           ↓
       Controller → Response         ← ответ пользователю

Итого

  • bootstrap/app.php — точка сборки приложения. Здесь настраивается роутинг, middleware и обработка ошибок. Один файл вместо трёх (Kernel, Handler, RouteServiceProvider).
  • bootstrap/providers.php — список провайдеров для загрузки. Массив классов, ничего лишнего.
  • Сервис-провайдеры — классы, которые подготавливают части приложения. register() привязывает сервисы к контейнеру, boot() настраивает всё остальное (роуты, вьюхи, события). Сначала все register(), потом все boot().