Загрузка...

Laravel. Сервис контейнер и Сервис провайдер

Сервис-контейнер и сервис-провайдеры в Laravel: подробный разбор
laravel cover

Laravel часто хвалят за «магический» Dependency Injection и удобную архитектуру. Но за этой магией стоит вполне конкретный механизм — сервис-контейнер и сервис-провайдеры.
Если понимать, как они работают, Laravel перестаёт быть «чёрным ящиком» и становится предсказуемым инструментом.

В этой статье разберём:

  • что такое сервис-контейнер и зачем он нужен;
  • как Laravel автоматически внедряет зависимости;
  • что такое сервис-провайдер и какую роль он играет;
  • где и как правильно регистрировать сервисы;
  • практические примеры и типовые ошибки.

Проблема, которую решает сервис-контейнер

Начнём с простой ситуации. У нас есть контроллер:

class OrderController
{
    public function store()
    {
        $service = new OrderService();
        $service->create();
    }
}

На первый взгляд всё нормально, но здесь есть несколько проблем:

  1. Контроллер жёстко зависит от конкретной реализации OrderService.
  2. Нельзя легко подменить реализацию (например, для тестов).
  3. Создание объекта происходит прямо внутри контроллера.
  4. При усложнении зависимостей код быстро превращается в «лапшу».

Чем больше приложение — тем больнее становится поддержка такого кода.


Dependency Injection как решение

Вместо создания зависимостей внутри класса, мы передаём их извне:

class OrderController
{
    public function __construct(
        private OrderService $orderService
    ) {}

    public function store()
    {
        $this->orderService->create();
    }
}

Теперь контроллер:

  • не знает, как создаётся OrderService;
  • не управляет его жизненным циклом;
  • просто говорит: «мне нужен объект такого типа».

Но возникает вопрос:
кто создаёт OrderService и передаёт его в контроллер?

Ответ: сервис-контейнер Laravel.


Что такое сервис-контейнер в Laravel

Сервис-контейнер — это объект, который:

  • хранит информацию о зависимостях;
  • знает, как создавать классы;
  • автоматически разрешает (resolve) зависимости;
  • управляет жизненным циклом объектов.

Проще говоря, это центральный реестр всех сервисов приложения.

Когда Laravel видит, что классу нужен другой класс, он:

  1. Смотрит тип аргумента.
  2. Проверяет, есть ли правило создания этого класса в контейнере.
  3. Если есть — использует его.
  4. Если нет — пытается создать объект автоматически через reflection.

Автоматическое разрешение зависимостей

Laravel умеет создавать классы без явной регистрации, если:

  • класс не абстрактный;
  • все его зависимости тоже могут быть разрешены.

Пример:

class Logger
{
    public function log(string $message) {}
}

class OrderService
{
    public function __construct(
        private Logger $logger
    ) {}
}

Никакой регистрации нет, но Laravel спокойно создаст OrderService, а затем и Logger.

Это называется auto-wiring.

⚠️ Но как только появляются:

  • интерфейсы,
  • абстрактные классы,
  • параметры конфигурации,

— автоматического создания становится недостаточно.


Интерфейсы и контейнер

Допустим, у нас есть интерфейс:

interface PaymentGateway
{
    public function pay(int $amount): void;
}

И реализация:

class StripeGateway implements PaymentGateway
{
    public function pay(int $amount): void {}
}

Если мы напишем так:

class PaymentService
{
    public function __construct(
        private PaymentGateway $gateway
    ) {}
}

Laravel не сможет догадаться, какую реализацию использовать.

В этот момент нам нужен сервис-провайдер.


Что такое сервис-провайдер

Сервис-провайдер — это класс, в котором мы:

  • регистрируем сервисы в контейнере;
  • описываем правила их создания;
  • связываем интерфейсы с реализациями;
  • выполняем начальную конфигурацию приложения.

Сервис-провайдеры загружаются на этапе инициализации Laravel, до обработки HTTP-запроса.

Все провайдеры перечислены в config/app.php.


Структура сервис-провайдера

Типичный провайдер выглядит так:

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // регистрация зависимостей
    }

    public function boot()
    {
        // инициализация после регистрации
    }
}

register()

Метод register предназначен только для регистрации сервисов:

  • bind
  • singleton
  • instance

Здесь нельзя использовать другие сервисы, так как они могут быть ещё не зарегистрированы.

boot()

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

  • использовать контейнер;
  • регистрировать макросы;
  • подписываться на события;
  • выполнять дополнительную настройку.

Регистрация зависимостей в контейнере

bind — создание нового объекта каждый раз

$this->app->bind(PaymentGateway::class, StripeGateway::class);

Каждый раз при запросе зависимости будет создаваться новый объект.


singleton — один объект на всё приложение

$this->app->singleton(CacheService::class, function () {
    return new CacheService();
});

Подходит для:

  • кеша;
  • клиентов внешних API;
  • сервисов без состояния запроса.

instance — готовый объект

$this->app->instance('config.version', '1.0.0');

Используется реже, но полезно для передачи уже созданных объектов.


Использование контейнера напрямую

Хотя чаще контейнер используется автоматически, иногда его вызывают вручную:

$service = app(OrderService::class);

Или через внедрение:

public function __construct(Container $container) {}

Это полезно для фабрик, динамических сервисов или CLI-утилит.


Как Laravel использует сервис-провайдеры внутри

Почти всё в Laravel — это сервис-провайдеры:

  • роутинг;
  • база данных;
  • очереди;
  • кеш;
  • события;
  • Blade;
  • Livewire;
  • Sanctum и Passport.

Когда вы устанавливаете пакет, он почти всегда добавляет свой сервис-провайдер, который регистрирует сервисы в контейнере.


Типичные ошибки

❌ Логика в register()

public function register()
{
    DB::listen(...); // плохо
}

В register — только регистрация, без логики.


❌ new вместо контейнера

new UserService();

Вы обходите контейнер и ломаете архитектуру.


❌ Глобальные хелперы вместо DI

auth()->user();

В бизнес-логике лучше передавать зависимости явно.


Почему это важно на практике

Понимание сервис-контейнера даёт:

  • чистую архитектуру;
  • легко тестируемый код;
  • гибкость при росте проекта;
  • контроль над жизненным циклом объектов;
  • отсутствие «магии».

Laravel не скрывает механику — он просто делает её удобной.


Итог

  • Сервис-контейнер — это механизм управления зависимостями.
  • Сервис-провайдер — место, где эти зависимости регистрируются.
  • Dependency Injection — основа архитектуры Laravel.
  • Чем лучше вы понимаете контейнер, тем меньше костылей в коде.

Если Laravel кажется «магическим» — значит, вы просто ещё не заглянули под капот.


Если хочешь, следующим шагом можем:

  • разобрать реальный кастомный сервис + провайдер;
  • показать архитектуру приложения через контейнер;
  • или написать статью «Как Laravel bootstrap’ится по шагам».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *