Сервис-провайдеры в laravel для самых маленьких

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

Лично я потратил 2 дня за курением доков на русском, английском, несколько туториалов переведенных с совсем нерусского на менее нерусский, это когда чуваки пишут русскими буквами, но в русские несмогли. Попадались несколько на немецком и парочка на испанском, там только смотреть на код можно, благо код — интернациональен! Проблема всех официальных и около оффициальных мануалов — они копируют друг друга. И задача в тысяче источников рассматривается однобоко.  А ещё я посмотрел несколько видео обучалок, ненавижу видео-туторы, это полчаса времени про то, что можно в 5 абзацев вместить. Это я и попробую сделать.

Понятийный аппарат

Главная проблема — нифига не понятно кто это такие Сервис-провайдеры и как сними работать.

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

А вот и ещё одно прикольное понятие Сервис-контейнер. Попробую на пальцах — это «ящик», в котором хранятся все зарегистрированные сервисы, чтобы быстро к ним доступаться это раз, а самое главное:

Сервис-контейнер в Laravel — это мощное средство для управления зависимостями классов и внедрения зависимостей.

Так нам рассказывает официальная документация. Если не совсем понятно, то лучше бы тебе, милый дружок, почитать обзорные статьи на понятия контейнер внедрения зависимостей. Если очень грубо: иногда наш объект должен иметь доступ к чему-то из вне. Например, есть объект пользователь, который учится в школе. Можно все поля связанные со школой уместить внутрь пользователя. Но это, очевидно, будет избыточно, 2 разных пользователя могут учиться в одной школе, поэтому создаем отдельный объект школа. А чтобы пользователь мог получить инфу о школе, мы в конструктор передаем зависимость от некоего хранилища школы, интерфейс, который по запросу может вернуть объект школы. Т.е. теперь при создании пользователя он сможет дергать внешнее хранилище и получать требуемые данные. Таким образом объект замкнут сам в себе. Понимаешь?! Это как нарисовать сову! Ой, всё! Давай потом на эту тему поговорим.

Вернемся к нашему предмету.Сервис-контейнер хранит внутри себя некие сервисы. А что такое сервис? А вот это как раз тот самый функциональный блок, который нам нужен. Отправка смс, вывод статистики по чему-либо и т.д. Все зарегистрированные сервисы кэшируются в /var/www/bootstrap/cache/services.php, можно глянуть кто и что туда добавились

Нафига это надо? Можно ж по старинке всё делать, нужен класс, дернул, не нужен не дернул. Так-то оно так, но одно дело когда перфоратор у тебя на балконе лежит, ты пошел и взял, а другое дело, когда он в гараже, у отца, в другом городе, в Белоруссии, а ты в Гремании. Ну ты понял. Скорость и удобство.

Кодовое выражение

Допустим, мы поняли что такое сервис-провайдеры. А как с ними жить и как это выражается в печатных символах в любимой IDE’шке?

Есть короткий ответ, вот так:

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

Именно так написано в документации. И ты такой: «Аааа! Тут же вон оно… И чё?» А я был готов к этому вопросу, не зря ж уже 2ю неделю пишу этот опус.

Собственно, мы говорим: Мне нужно, чтобы в сервис контейнере лежал экземпляр класса Riak\Connection (за это отвечает строка — return new Connection(config(‘riak’))) с именем равным исходному имени класса (а за это отвечает Connection::class) и я хочу чтобы это был единственный инстанс ($this->app->singleton). И теперь, если в любом месте приложения я попытаюсь дернуть что-то с именем Connection, то мне вернется уже созданный объект из контейнера зависимостей.

Всё это прекрасно, но главная боль всё ещё в силе — ЗАЧЕМ?

Зачем?

Это хороший вопрос, если скорость доступа уже не будоражит, то вот тебе ещё один отличный пример.

Имеем нечто, нууу… например… чтобы такое подобрать чтобы не банально, но показательно… Ладно, допустим у нас имеется некая система оповещения пользователя, например,  об ураганах. Отлично зашли, главное не банально =) Ураганы бывают разной интенсивности и если это легкий ветерок, то можно и просто на почту отправить сообщение, мол, одевайся потеплее, зонт оставь дома ,он тебя не спасёт. А вот если ветерок такой, что твои губы колышет, то наверное лучше бы всем как минимум позвонить и роботом рассказать: «А ну-ка резче (!) собрали паспорта и бегом в подвалы!»

Как бы я сейчас это реализовал. Для начала опишем контракт, которому должны следовать все создаваемые системы оповещения. По сути, если снизойти до понятий php, контракт это всего лишь интерфейс, да, вот так просто. Но контракт — звучит гордо.

<?php

namespace App\Contracts;

/**
 * Interface ContractAlarm
 *
 * @package App\Contracts
 */
interface ContractAlarm
{
    public function send();
}

Конечно, в большинстве своем, это просто договорённость, ты можешь не наследоваться, но интерфейс обяжет тебя создавать все методы которые могут быть вызваны извне. В нашем случае нам очень нужен метод send().

Добавим пару реализаций, как огоговаривалось выше — EmailAlarm, PhoneAlarm, ну и какой-нибудь экзотический ArmyAlarm, это если бы мы жили в америке, то к тебе приехал бы офицер национальной гвардии и дал команду на эвакуацию.

Так.. И как я это хочу подвязать к сервис-провайдеру? Немного увлекся. Ах вот! Теперь мы создаем наш сервис провайдер, который… А лучше я сначала напишу, а оптом объясню

namespace App\Providers;

use App\Entity\Hurricane;
use App\Services\Alarm\EmailAlarm;
use App\Services\Alarm\PhoneAlarm;
use App\Services\Alarm\ArmyAlarm;
use Illuminate\Support\ServiceProvider;

class AlarmServiceProvider extends ServiceProvider
{
    public function register(Hurricane $hurricane): void
    {
        $this->app->singleton('ContractAlarm', function () use ($hurricane) {

            if( $hurricane->force <= 4 ) {
                return new EmailAlarm();
            }

            if( $hurricane->force > 4 && $hurricane->force < 7 ) {
                return new PhoneAlarm();
            }

            if( $hurricane->force <= 10 ) {
                return new ArmyAlarm();
            }

            throw new \InvalidArgumentException('Бегите, глупцы!');
        });
    }
}

Пояснения нужны? Ну лааадно уж. В зависимости от силы урагана будет создан тот или иной инстанс по оповещению, если всё слабенько, то по почте, если сильно — по телефону, при этом в коде мы будем везде вызывать ContractAlarm->send(). Ну круто же! Теперь нашему приложению совершенно пофигу что там и как реализуется, оно просто дергает некий абстрактный ContractAlarm, который имеем нужный метод send (кстати, имя можно было дать и более приличное, типа Alarmer, но это по желанию, говорят, что принято имя в контейнере и имя исходного класса лучше делать одинаковыми) Поэтому и удобно все реализации наследовать от контракта, чтобы всё было хорошо.

Надо ещё не забыть в config/app.php в секцию providers добавить наш новый сервис провайдер.

Выводы

Все эти штуки, дело сугубо добровольное. Никто тебя палками бить не будет если тебе впадлос юзать общепринятые решения. Хочешь, откажись от ООП вообще, сейчас это модно всякие извращения, можешь вообще всё внутри одной функции писать, чего ж нет? Типа очень жесткий Front Controller, который реально «Всё в одном месте» =) Но на счет «палками бить», ты подумай, а то вдруг всё же кто-то придет 😉

Всем рок!

1 thought on “Сервис-провайдеры в laravel для самых маленьких

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

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

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.