Сервис-провайдеры в 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, который реально «Всё в одном месте» =) Но на счет «палками бить», ты подумай, а то вдруг всё же кто-то придет ;)

Всем рок!

Понравилась статья? Поделиться с друзьями:
Комментариев: 7
  1. unuser

    Отличный слог, прекрасный юмор. Спасибо )

  2. Дмитрий

    А нужен ли сервис провайдер в случае, если реализация гарантированно одна?

    1. Ильдар (автор)

      Сервис провайдер не только про решение вопроса какую имплементацию интерфейса выбрать, это скорее приятный бонус, чем основная фича.

      На мой взгляд, основное — это положить блок часто используемого кода в быстродоступное место, в сервис контейнер. (ИМХО)

      Ну и потом можно использовать интересные штуки типа App::make(‘NeedService’); Или на время тестирования подменять реализацию.

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

  3. Олег

    Вопрос по статье: как вызвать провайдера с передачей параметров в register?

    1. Ильдар (автор)

      Есть короткий ответ: «Не знаю» =)

      А вообще, странный кейс. Если задача провайдера положить в IoC-контейнер некую реализацию функционального блока. То этот блок должен носить какой-то фундаментальный смысл. Например, «Система оповещения» или «сервис почтовой рассылки». И как следствие этот блок должен быть определен на момент выполнения основной бизнез-логики.

      Собственно, «параметры» можно определить в переменных окружения, либо в каких нибудь конфигурационных файлах. Это как раз те места, который ещё более низкоуровневы по отношению к основной логике

      1. Alex

        А $hurricane разве не является параметром в register? Или откуда он вообще берётся?

      2. Михаил

        про $huricane — выглядит то красиво — а откуда берется «не знаю». Статья неполная, поверхностная из разряда «я знаю много вумных слов»

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

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

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