• Время чтения ~1 мин
  • 20.01.2023

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

Using the container is all about being organized—having a consistent place to keep your container привязки and naming conventions that make sense and allow you to know what is going on. The container is only as good as the person sticking things into, after all.

Допустим, мы хотим хранить все наши привязки в одном месте, у поставщика услуг. Звучит разумно, верно? Но что происходит по мере роста нашего приложения? Мы начнем, может быть, с 5-6 привязок для простого приложения, добавим несколько новых функций и должны добавить привязки к контейнеру. Прежде чем мы это узнаем, поставщик услуг, которого мы используем, чрезвычайно велик, и требуется много когнитивных усилий, чтобы найти что-либо.

Как мы можем бороться с этим? Как мы можем гарантировать, что мы не просто втыкаем их в поставщика услуг, чтобы скрыть проблему? Позвольте мне рассказать вам, как я подхожу к этому.

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

final class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->register(
            provider: AuthDomainServiceProvider::class,
        );
        $this->app->register(
            provider: CommunicationDomainServiceProvider::class,
        );
        $this->app->register(
            provider: WorkDomainServiceProvider::class,
        );
    }
}

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

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

final class AuthDomainServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->register(
            provider: QueryServiceProvider::class,
        );
        $this->app->register(
            provider: CommandServiceProvider::class,
        );
        $this->app->register(
            provider: FactoryServiceProvider::class,
        );
    }
}

Таким образом, мой поставщик услуг аутентификации используется исключительно для регистрации аспектов домена, которым необходимо написать свои привязки.

Запрос — это операции чтения в приложении, общие запросы, которые мне нужно выполнить, или части запросов, которые мне нужно выполнить. Я написал учебник о том, как я это делаю здесь.

Command — это операции записи в приложении. Обычно они включаются в фоновые задания, чтобы приложение могло быстро реагировать.

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

Давайте посмотрим на CommandServiceProvider и как мы могли бы использовать это для эффективной регистрации команд в нашем приложении.

final class CommandServiceProvider extends ServiceProvider
{
    public array $bindings = [
        FindOrCreateUserContract::class => FindOrCreateUser::class,
        GenerateApiTokenContract::class => GenerateApiToken::class,
        SendPasswordResetContract::class => SendPasswordReset::class,
    ];
}

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

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

interface GenerateApiTokenContract
{
    public function handle(Authenticatable $user, DataObjectContract $payload): Model|NewAccessToken;
}

Затем переходим к реализации.

final class GenerateApiToken implements GenerateApiTokenContract
{
    public function handle(Authenticatable $user, DataObjectContract $payload): Model|NewAccessToken
    {
        return DB::transaction(
            fn ():  Model|NewAccessToken => $user->createToken(
                name: $payload->name,
            ),
        );
    }
}

Мы заключаем операцию записи в транзакцию базы данных, затем используем внедренную пользовательскую модель и вызываем для нее метод создания токена, передавая свойство name из нашей полезной нагрузки. Это сохраняет чистоту — так как тогда вы также можете использовать это для создания токенов API для любого пользователя в вашем приложении, а не только для текущего пользователя, вошедшего в систему.

Использование контейнера таким образом означает, что мои контроллеры всегда чисты и минимальны. Давайте рассмотрим пример контроллера API для входа пользователя.

final readonly class LoginController
{
    public function __construct(
        private GenerateApiTokenContract $command,
        private TokenNameGenerator $generator,
    ) {}
    public function __invoke(LoginRequest $request): Responsable
    {
        $request->authenticate();

        return new TokenResponse(
            data: TokenFactory::make(
                data: $this->command->handle(
                    user: auth()->user(),
                    payload: new TokenRequest(
                        name: $generator->generate(),
                    ),
                ),
            ),
        );
    }
}

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

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297