Как 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 обрабатывает провайдеры в два прохода:
- Вызывает
register()у всех провайдеров по порядку. - Вызывает
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().