Идёт обновление сайта. Несколько дней возможны сбои в оформлении и переводах. Документация работает — если страница выглядит сломанной, обновите её позже.

Документация
L Laravel L intervention/image
Войти
Главная Laravel 10.x Сервисный контейнер

Сервисный контейнер

10.x 7 мар 2026 г.

#Введение

Сервисный контейнер Laravel — мощный инструмент для управления зависимостями классов и выполнения dependency injection. Dependency injection — это сложное выражение, которое по сути означает следующее: зависимости класса «внедряются» в класс через конструктор или, в некоторых случаях, через методы-сеттеры.

Рассмотрим простой пример:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Создать новый экземпляр контроллера.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}

    /**
     * Показать профиль для данного пользователя.
     */
    public function show(string $id): View
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

В этом примере UserController должен получать пользователей из источника данных. Поэтому мы внедряем сервис, который может извлекать пользователей. В данном случае наш UserRepository скорее всего использует Eloquent для получения информации о пользователях из базы данных. Однако, поскольку репозиторий внедряется, мы можем легко заменить его другой реализацией. Также мы можем легко «мокать» или создавать заглушку UserRepository при тестировании приложения.

Глубокое понимание сервисного контейнера Laravel необходимо для создания мощных крупных приложений, а также для вклада в сам Laravel core.

#Разрешение без конфигурации

Если класс не имеет зависимостей или зависит только от конкретных классов (не интерфейсов), контейнеру не нужно указывать, как разрешать этот класс. Например, вы можете поместить следующий код в файл routes/web.php:

<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    die($service::class);
});

В этом примере при обращении к маршруту / вашего приложения класс Service автоматически разрешится и будет внедрён в обработчик маршрута. Это меняет правила игры. Это значит, что вы можете разрабатывать приложение и использовать dependency injection без необходимости создавать громоздкие конфигурационные файлы.

К счастью, многие классы, которые вы будете писать при создании Laravel-приложения, автоматически получают свои зависимости через контейнер, включая контроллеры, слушатели событий, middleware и другие. Кроме того, вы можете указывать зависимости в методе handle очередных заданий. Попробовав мощь автоматической и безконфигурационной dependency injection, становится сложно разрабатывать без неё.

#Когда использовать контейнер

Благодаря разрешению без конфигурации вы часто будете указывать зависимости в маршрутах, контроллерах, слушателях событий и других местах, не взаимодействуя с контейнером напрямую. Например, вы можете указать тип Illuminate\Http\Request в определении маршрута, чтобы легко получить текущий запрос. Хотя мы никогда не взаимодействуем с контейнером напрямую при написании такого кода, он управляет внедрением этих зависимостей за кулисами:

use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    // ...
});

Во многих случаях благодаря автоматической dependency injection и фасадам вы можете создавать Laravel-приложения, никогда не привязывая и не разрешая что-либо вручную из контейнера. Так когда же стоит взаимодействовать с контейнером вручную? Рассмотрим два случая.

Во-первых, если вы пишете класс, реализующий интерфейс, и хотите указывать этот интерфейс в маршруте или конструкторе класса, вы должны сообщить контейнеру, как разрешать этот интерфейс. Во-вторых, если вы пишете пакет для Laravel, который планируете распространять среди других разработчиков, вам может понадобиться привязать сервисы вашего пакета в контейнер.

#Привязка

#Основы привязки

#Простые привязки

Почти все ваши привязки в сервисном контейнере будут регистрироваться в рамках service providers, поэтому большинство примеров будет показывать использование контейнера именно в этом контексте.

Внутри service provider у вас всегда есть доступ к контейнеру через свойство $this->app. Мы можем зарегистрировать привязку с помощью метода bind, передав имя класса или интерфейса, который хотим зарегистрировать, и замыкание, возвращающее экземпляр класса:

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

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

Как уже упоминалось, вы обычно взаимодействуете с контейнером внутри service providers; однако, если хотите работать с контейнером вне service provider, вы можете сделать это через фасад App:

use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});

Вы можете использовать метод bindIf для регистрации привязки в контейнере только если для данного типа ещё не зарегистрирована привязка:

$this->app->bindIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
Примечание

Нет необходимости привязывать классы в контейнер, если они не зависят от интерфейсов. Контейнеру не нужно указывать, как создавать такие объекты, так как он может автоматически разрешать их с помощью рефлексии.

#Привязка синглтона

Метод singleton привязывает класс или интерфейс в контейнер так, чтобы он разрешался только один раз. После первого разрешения синглтона при последующих вызовах контейнера будет возвращаться тот же экземпляр объекта:

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

Вы можете использовать метод singletonIf для регистрации синглтона в контейнере только если для данного типа ещё не зарегистрирована привязка:

$this->app->singletonIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

#Привязка синглтонов с областью действия

Метод scoped привязывает класс или интерфейс в контейнер так, чтобы он разрешался один раз в рамках жизненного цикла конкретного запроса или задания Laravel. Хотя этот метод похож на singleton, экземпляры, зарегистрированные через scoped, будут сбрасываться при начале нового «жизненного цикла» приложения Laravel, например, когда воркер Laravel Octane обрабатывает новый запрос или когда очередной воркер обрабатывает новое задание:

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->scoped(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

#Привязка экземпляров

Вы также можете привязать существующий объект в контейнер с помощью метода instance. Переданный экземпляр всегда будет возвращаться при последующих вызовах контейнера:

use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);

#Привязка интерфейсов к реализациям

Очень мощная возможность сервисного контейнера — привязывать интерфейс к конкретной реализации. Например, предположим, у нас есть интерфейс EventPusher и реализация RedisEventPusher. После того как мы написали реализацию RedisEventPusher этого интерфейса, мы можем зарегистрировать её в сервисном контейнере так:

use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);

Это говорит контейнеру, что при необходимости реализации EventPusher он должен внедрять RedisEventPusher. Теперь мы можем указывать интерфейс EventPusher в конструкторе класса, который разрешается контейнером. Помните, что контроллеры, слушатели событий, middleware и многие другие классы в Laravel всегда разрешаются через контейнер:

use App\Contracts\EventPusher;

/**
 * Создать новый экземпляр класса.
 */
public function __construct(
    protected EventPusher $pusher
) {}

#Контекстная привязка

Иногда у вас есть два класса, использующих один и тот же интерфейс, но вы хотите внедрять разные реализации в каждый из них. Например, два контроллера могут зависеть от разных реализаций контракта Illuminate\Contracts\Filesystem\Filesystem contract. Laravel предоставляет простой и удобный интерфейс для определения такого поведения:

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

#Привязка примитивов

Иногда класс получает внедрённые классы, но также нуждается во внедрении примитивного значения, например, целого числа. Вы можете легко использовать контекстную привязку, чтобы внедрить любое значение, необходимое вашему классу:

use App\Http\Controllers\UserController;

$this->app->when(UserController::class)
          ->needs('$variableName')
          ->give($value);

Иногда класс может зависеть от массива помеченных экземпляров. Используя метод giveTagged, вы можете легко внедрить все привязки контейнера с этим тегом:

$this->app->when(ReportAggregator::class)
    ->needs('$reports')
    ->giveTagged('reports');

Если вам нужно внедрить значение из одного из конфигурационных файлов вашего приложения, вы можете использовать метод giveConfig:

$this->app->when(ReportAggregator::class)
    ->needs('$timezone')
    ->giveConfig('app.timezone');

#Привязка типизированных вариадиков

Иногда класс получает массив типизированных объектов через вариадический аргумент конструктора:

<?php

use App\Models\Filter;
use App\Services\Logger;

class Firewall
{
    /**
     * Экземпляры фильтров.
     *
     * @var array
     */
    protected $filters;

    /**
     * Создать новый экземпляр класса.
     */
    public function __construct(
        protected Logger $logger,
        Filter ...$filters,
    ) {
        $this->filters = $filters;
    }
}

Используя контекстную привязку, вы можете разрешить эту зависимость, передав методу give замыкание, которое возвращает массив разрешённых экземпляров Filter:

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give(function (Application $app) {
                return [
                    $app->make(NullFilter::class),
                    $app->make(ProfanityFilter::class),
                    $app->make(TooLongFilter::class),
                ];
          });

Для удобства вы также можете просто передать массив имён классов, которые контейнер будет разрешать, когда Firewall нуждается в экземплярах Filter:

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give([
              NullFilter::class,
              ProfanityFilter::class,
              TooLongFilter::class,
          ]);

#Вариадические зависимости с тегами

Иногда класс может иметь вариадическую зависимость, типизированную определённым классом (Report ...$reports). Используя методы needs и giveTagged, вы можете легко внедрить все привязки контейнера с этим тегом для данной зависимости:

$this->app->when(ReportAggregator::class)
    ->needs(Report::class)
    ->giveTagged('reports');

#Тегирование

Иногда нужно разрешить все привязки определённой «категории». Например, вы создаёте анализатор отчётов, который получает массив различных реализаций интерфейса Report. После регистрации реализаций Report вы можете присвоить им тег с помощью метода tag:

$this->app->bind(CpuReport::class, function () {
    // ...
});

$this->app->bind(MemoryReport::class, function () {
    // ...
});

$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');

После того как сервисы были помечены тегом, вы можете легко разрешить их все через метод tagged контейнера:

$this->app->bind(ReportAnalyzer::class, function (Application $app) {
    return new ReportAnalyzer($app->tagged('reports'));
});

#Расширение привязок

Метод extend позволяет модифицировать разрешённые сервисы. Например, при разрешении сервиса вы можете выполнить дополнительный код для декорирования или настройки сервиса. Метод extend принимает два аргумента: класс сервиса, который вы расширяете, и замыкание, которое должно вернуть модифицированный сервис. Замыкание получает разрешаемый сервис и экземпляр контейнера:

$this->app->extend(Service::class, function (Service $service, Application $app) {
    return new DecoratedService($service);
});

#Разрешение

#Метод make

Вы можете использовать метод make для разрешения экземпляра класса из контейнера. Метод make принимает имя класса или интерфейса, который вы хотите разрешить:

use App\Services\Transistor;

$transistor = $this->app->make(Transistor::class);

Если некоторые зависимости вашего класса не могут быть разрешены через контейнер, вы можете передать их вручную в виде ассоциативного массива в метод makeWith. Например, мы можем вручную передать аргумент конструктора $id, необходимый сервису Transistor:

use App\Services\Transistor;

$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

Метод bound позволяет проверить, была ли явно зарегистрирована привязка для класса или интерфейса в контейнере:

if ($this->app->bound(Transistor::class)) {
    // ...
}

Если вы находитесь вне service provider в части кода, где нет доступа к переменной $app, вы можете использовать фасад App или хелпер app для разрешения экземпляра класса из контейнера:

use App\Services\Transistor;
use Illuminate\Support\Facades\App;

$transistor = App::make(Transistor::class);

$transistor = app(Transistor::class);

Если вы хотите, чтобы сам экземпляр контейнера Laravel был внедрён в класс, который разрешается контейнером, вы можете указать тип Illuminate\Container\Container в конструкторе вашего класса:

use Illuminate\Container\Container;

/**
 * Создать новый экземпляр класса.
 */
public function __construct(
    protected Container $container
) {}

#Автоматическая инъекция

Кроме того, и что важно, вы можете указывать зависимости в конструкторе класса, который разрешается контейнером, включая контроллеры, слушатели событий, middleware и другие. Также вы можете указывать зависимости в методе handle очередных заданий. На практике именно так большинство ваших объектов должно разрешаться контейнером.

Например, вы можете указать репозиторий, определённый в вашем приложении, в конструкторе контроллера. Репозиторий будет автоматически разрешён и внедрён в класс:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;
use App\Models\User;

class UserController extends Controller
{
    /**
     * Создать новый экземпляр контроллера.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}

    /**
     * Показать пользователя с данным ID.
     */
    public function show(string $id): User
    {
        $user = $this->users->findOrFail($id);

        return $user;
    }
}

#Вызов методов и инъекция

Иногда вы можете захотеть вызвать метод объекта, позволяя контейнеру автоматически внедрять зависимости этого метода. Например, для следующего класса:

<?php

namespace App;

use App\Repositories\UserRepository;

class UserReport
{
    /**
     * Сгенерировать новый отчёт пользователя.
     */
    public function generate(UserRepository $repository): array
    {
        return [
            // ...
        ];
    }
}

Вы можете вызвать метод generate через контейнер так:

use App\UserReport;
use Illuminate\Support\Facades\App;

$report = App::call([new UserReport, 'generate']);

Метод call принимает любой PHP callable. Метод call контейнера может даже использоваться для вызова замыкания с автоматическим внедрением его зависимостей:

use App\Repositories\UserRepository;
use Illuminate\Support\Facades\App;

$result = App::call(function (UserRepository $repository) {
    // ...
});

#События контейнера

Сервисный контейнер генерирует событие каждый раз, когда разрешает объект. Вы можете слушать это событие с помощью метода resolving:

use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;

$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
    // Вызывается при разрешении контейнером объектов типа "Transistor"...
});

$this->app->resolving(function (mixed $object, Application $app) {
    // Вызывается при разрешении контейнером объекта любого типа...
});

Как видите, объект, который разрешается, передаётся в обратный вызов, что позволяет вам установить дополнительные свойства объекта перед тем, как он будет передан потребителю.

#PSR-11

Сервисный контейнер Laravel реализует интерфейс PSR-11. Поэтому вы можете указывать тип интерфейса контейнера PSR-11, чтобы получить экземпляр контейнера Laravel:

use App\Services\Transistor;
use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get(Transistor::class);

    // ...
});

Если указанный идентификатор не может быть разрешён, будет выброшено исключение. Исключение будет экземпляром Psr\Container\NotFoundExceptionInterface, если идентификатор никогда не был привязан. Если идентификатор был привязан, но не удалось разрешить, будет выброшено исключение Psr\Container\ContainerExceptionInterface.