Продвинутые темы

Контейнер вглубь, макросы, pipelines, broadcasting, архитектура приложения.

Middle Laravel 11/12/13: что важно знать о современной структуре приложения?

С Laravel 11 изменился «скелет» — знать обязательно, чтобы не путаться:

  • bootstrap/app.php — единая точка конфигурации: роуты, middleware, исключения, консоль. Файлы Http/Kernel.php и Console/Kernel.php упразднены:
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php')
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([...]);
        $middleware->validateCsrfTokens(except: ['webhooks/*']);
    })
    ->withExceptions(function (Exceptions $exceptions) { ... })
    ->create();
  • config/*.php опциональны — дефолты вшиты, публикуются php artisan config:publish;
  • нет app/Http/Middleware/* из коробки — свои добавляются через withMiddleware;
  • планировщик — routes/console.php (Schedule::), а не Console/Kernel;
  • Http/Controllers/Controller больше не содержит трейтов авторизации/валидации — используют $request->validate(), Gate::authorize() или authorize() с трейтом;
  • минимальная версия PHP 8.2+ (Laravel 11), 8.3 (Laravel 12), 8.4 (Laravel 13);
  • Laravel 11+ : Reverb (websockets), Prompts, php artisan about, per-second rate limiting; 12 — #[Scoped], ConsecutiveMigrationNames; стриминг-ответы StreamedJsonResponse и др.
Senior Contextual binding, tagged services, extend: контейнер за пределами bind/singleton.

Contextual binding — разные реализации одного интерфейса для разных потребителей:

$this->app->when(ReportGenerator::class)
    ->needs(Filesystem::class)
    ->give(fn () => Storage::disk('s3'));

$this->app->when(ImportCommand::class)
    ->needs('$chunkSize')                    // скаляры тоже
    ->give(1000);

Tagged — собрать группу реализаций:

$this->app->tag([CsvExporter::class, PdfExporter::class], 'exporters');
$this->app->bind(ExportManager::class, fn ($app) => new ExportManager($app->tagged('exporters')));

extend() — декорировать существующий биндинг:

$this->app->extend(Service::class, fn ($service, $app) => new LoggingDecorator($service));

Прочее: bindMethod() (вызов методов через app()->call), resolving-события ($app->resolving(...)), scoped() (синглтон на время запроса/jobs — важно для Octane и воркеров!), rebinding. Атрибут #[Scoped]/#[Singleton] на классе (Laravel 12+).

Senior Что такое макросы, миксины и где их уместно применять?

Макросы добавляют методы «магическим» классам фреймворка (Macroable-трейт: Collection, Str, Request, Response, Builder, Route...) без наследования:

// в AppServiceProvider::boot()
Str::macro('isSlug', fn (string $v) => preg_match('/^[a-z0-9-]+$/', $v) === 1);

Str::isSlug('hello-world');   // true

Миксин — пакетная регистрация группы макросов из класса:

Collection::mixin(new CollectionMacros);
// методы класса возвращают замыкания — они и становятся макросами

Свои «macroable» классы:

class Money { use Macroable; }
Money::macro('format', fn () => number_format($this->amount / 100, 2));

Когда уместно: инфраструктурные утилиты, пакеты (расширение чужих классов). Когда нет: бизнес-логика — её место в сервисах/моделях; макросы скрывают зависимости и ломают автодополнение IDE (лечится _ide_helper/докблоками @mixin).

Senior Pipeline: как пропустить объект через цепочку обработчиков?

Illuminate\Pipeline\Pipeline — паттерн «цепочка обработки» (на нём построены middleware):

$result = app(Pipeline::class)
    ->send($order)
    ->through([
        ValidateStock::class,
        ApplyDiscounts::class,
        CalculateTotals::class,
        ReserveItems::class,
    ])
    ->via('handle')                       // имя метода (по умолчанию handle)
    ->then(fn ($order) => $order);        // финальное действие

Каждый шаг:

class ApplyDiscounts
{
    public function handle(Order $order, Closure $next): Order
    {
        $order->total -= $order->discount;
        return $next($order);    // можно изменить объект до/после следующего шага
    }
}

Применение: последовательные трансформации (импорт, биллинг, фильтры каталога), когда if/elseif разрастается. Шаги легко добавлять/переставлять/тестировать по одному.

Классика собеседования: «middleware — это частный случай Pipeline, где объект — Request, а финал — контроллер».

Senior Как работает Broadcasting (WebSockets) в Laravel?

Broadcasting доставляет события клиентам в реальном времени: событие → драйвер (Pusher / Ably / Reverb / soketi / Redis+Socket.io) → WebSocket → браузер (Laravel Echo).

class OrderShipped implements ShouldBroadcast
{
    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel("orders.{$this->order->id}")];
    }
    // broadcastAs() — имя события на клиенте; broadcastWith() — payload
}

Типы каналов:

  • Channel — публичный, все подписчики;
  • PrivateChannel — авторизация через /broadcasting/auth:
    Broadcast::channel('orders.{orderId}', fn ($user, $orderId) => $user->id === Order::find($orderId)->user_id);
    
  • PresenceChannel — как private + «кто онлайн».

Клиент (Echo):

Echo.private(`orders.${id}`).listen('OrderShipped', (e) => ...);

Laravel Reverb — собственный WebSocket-сервер на PHP (Laravel 11+), замена Pusher/soketi для self-hosted. Broadcast-события ставят в очередь (ShouldBroadcastNow — синхронно).

Senior Как организовать архитектуру большого Laravel-приложения? Контроллеры, сервисы, actions.

Проблема роста: «толстые» контроллеры и модели. Типовые слои:

  1. Контроллер — только HTTP: валидация (FormRequest) → вызов слоя бизнес-логики → ответ (Resource).
  2. Actions / Services — бизнес-операции:
class CreateOrderAction
{
    public function execute(User $user, array $items): Order
    {
        return DB::transaction(function () use ($user, $items) {
            $order = Order::create([...]);
            event(new OrderCreated($order));
            return $order;
        });
    }
}
  1. Доменные объекты — DTO (spatie/laravel-data), enum'ы PHP 8.1+ для статусов.
  2. Repositories — спорно в Laravel (Eloquent уже абстракция); оправданы при смене источника данных или сложных выборках.
  3. Реакции на события, jobs — горизонтальное расширение без связанности.

Принципы: тонкие контроллеры, зависимости через конструктор и интерфейсы (контейнер), транзакции на уровне use-case, тесты на actions, а не только HTTP.

Термины на собеседовании: «single action controllers» (__invoke), CQRS-подход (разделение команд и запросов), модульность (nwidart/laravel-modules или доменная структура app/Domain/...).