К основному контенту

Laravel контракты, сервис провайдеры, сервис-контейнеры, фасады, сервисы и все-все-все




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

Я сама не так давно осознала необходимость этих паттернов для разработки и поспособствовал тому проект на Symfony.
Можно много лет успешно кодить приложения на Laravel среднего уровня сложности и спокойно обходиться без сервис провайдеров, кастомных фасадов и понимания как это работает. Проверено на своем опыте :)
Но в один момент мне стало стыдно перед собой за подобную лень и безалаберность. И я решила сесть и разобраться. Этот прием реально работает! Во всем в программировании достаточно сесть и разобраться. И все оказывается доступно для понимания. 
Все эти термины – это лишь красивые названия в доску простых механизмов, и когда вы поймете насколько эти механизмы простые – вы себе не простите такого долгого неознакомления с ними.
Не повторяйте мою ошибку. Сядьте и разберитесь. И сейчас я попытаюсь вам в этом помочь. Возможно, мой стиль статей не отличается точностью подачи а больше имеет публицистичный характер, но тем, кому нужен учебник – велкам в доку, суше некуда ;)

Оговорюсь сразу – понять как работают все эти механизмы можно с любым уровнем знаний и опыта в программировании. Но вот понять на кой это использовать, вот действительно почувствовать, проникнуться необходимостью и воскликнуть «Как я раньше жил без этого?!» можно спустя только опыт разработки сложных приложений.

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

Контракты


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

Если на пальцах без замашки на гениальность – у вас есть сохранение картинок в приложении через облачное хранилище и непосредственно на диск. И так вышло, что вам нужно использовать оба режима сохранения одновременно. Чтобы не путаться - вы создаете интерфейс с перечнем методов сохранения и в дальнейшем наследуете его под разные реализации. А вдруг добавится сохранение еще на одно облако? Не беда, всегда можно создать еще один реализатор контракта.

Размещение контрактов произвольное, но я бы советовала их складывать в отдельную папку в App/Contracts.
Пример контракта:
SaveImage.php

interface SaveImage{

      public static function save();

      public static function delete();

}

Реализаторы: размещение произвольное, но я бы советовала их складывать в отдельную папку в App/Helpers:

Реализатор под сохранение на диск
SaveImageDisk.php

class SaveImageDisk implements SaveImage{

    public static function save (){
           //process saving on disk
    }

    public static function delete(){
           //process deleting from disk
    }
}

Реализатор под сохранение на облачный сервер:
SaveImageAws.php

class SaveImageAws implements SaveImage{

      public static function save (){
           //process saving on aws
      }

      public static function delete(){
           //process deleting from aws
      }
}

Не забывайте namespace и все use относительно вашего приложения!
При необходимости – просто используем тот или иной реализатор, например:
SaveImageDisk::save();
SaveImageAws::delete();

Service Providers aka Поставщики услуг


Окей, гугл, а если мне не надо параллельно использовать обе реализации сохранения картинок, а надо просто переключать легко и быстро режим с «сохранение на диск» на «сохранение на облако» и наоборот и без вот этого унылого по всему проекту ctrl+shift+F с автозаменой?

Ты по адресу J Страшные слова Service Provider сейчас станут для тебя манной небесной. А за использование подобных паттернов сразу +100 к ЧСВ :)
Сервис провайдер как раз и предоставляет эту самую возможность легкого переключения между реализациями. Не верите? Ну смотрите.

Давайте сразу создадим сервис провайдер и посмотрим что в нем:
php artisan make:provider SaveImageServiceProvider

В папке Providers найдем новый класс, нас интересует функция register, добавим в нее код:
SaveImageServiceProvider.php

public function register (){
     $this->app->bind('App\Contracts\SaveImage', function(){
            return new saveImageAws();
            //return new saveImageDisk();
     });
}

Мы говорим приложению, что когда происходит обращение к 'App\Contracts\SaveImage’ – пусть возвращает новый объект класса-реализатора saveImageAws.

То есть, в коде мы используем такую форму:
App\Contracts\SaveImage::save();

А метод save будет от класса saveImageAws. В провайдере легко изменить реализатор на saveImageDisk, просто раскомментировав его и закомментировав прежний.
Здесь происходит своего рода подмена понятий. Мы будто обращаемся сразу к интерфейсу и здесь есть что-то от полиморфизма, но нет, все намного банальнее, мы всего лишь обращаемся к ключу-строке в сервис контейнере, а не к интерфейсу в папке Contracts.

Завершим процесс регистрации нового провайдера внеся его в массив ‘providers’ в app.php, как раз в тот самый сервис контейнер о котором ниже.
Кстати, если вы хотите, чтобы создавался только один объект класса реализатора, вы хотите быть уверены в этом – используйте вместо bind – singleton.

Service Container


Это глобальный массив-контейнер всех доступных сервис – провайдеров. Таких же, как мы создали выше. Да-да, вот он, dependency-injection, легендарный. Есть множество способов внедрения зависимостей в фреймворке и это один из них.

Facades


А вот если ты, мой друг, придирчивый педант и в курсе, что писать static методы не по фэншую и ты хочешь чтобы твой код был легок к пониманию и интуитивно понятен - добро пожаловать в мир фасадов! 
Это вот прямо мое любимое. Тоже связано с подменой понятий (ну или с синтаксическим сахаром). Если правильно говорить, то фасады предоставляют статический интерфейс классам, зарегистрированным в сервис-контейнере.
Проще говоря, если у вас есть не static функции в классе - вы сможете обращаться к ним как к static в сокращенном синтаксисе без создания объекта класса с использованием фасадов. Как это происходит?  Класс Facade использует магический метод PHP __callStatic() для перенаправления вызовов методов с вашего фасада на полученный объект.


Переделаем наши классы контракта и реализаторов кошерно:

SaveImage.php

interface SaveImage{

    public function save();

    public function delete();
}

SaveImageDisk.php

class SaveImageDisk implements SaveImage{

    public function save (){
          //process saving on disk
    }

    public function delete(){
          //process deleting from disk
    }
}

SaveImageAws.php

class SaveImageAws implements SaveImage{

     public function save (){
           //process saving on aws
     }

     public function delete(){
           //process deleting from aws
     }
}

И приступим к созданию фасада в папке App/Facades:
SaveImage.php

class SaveImage extends Facade{
    protected static function getFacadeAccessor(){
        return ’App\Contracts\SaveImage’;
    }
}

Мы возвращаем строковый ключ для доступа к определенной ячейке Сервис контейнера,  то, что мы привязали в сервис провайдере, а именно -  'App\Contracts\SaveImage'.

Чтобы обращаться к нашему новому фасаду по сокращенному алиасу, скрыть детали реализации и явное наличие механизма фасада - в app.php в массиве ‘aliases’ добавляем:
‘SaveImage’ => ‘App\Facades\SaveImage::class’

И теперь мы используем данный механизм как:
SaveImage::save();

Этот код эквивалентен следующему:
$app = app();
$app->make('SaveImage')->save();

Функция save будет по прежнему взята из класса-реализатора saveImageAws, хоть функция не static - мы теперь имеем возможность обращаться к ней как к static.
Не забывайте, SaveImage – это только алиас, строка. Необходимо добавить в use SaveImage.
Если вы не добавите в app.php в массив ‘aliases’ запись с ключом SaveImage то вы можете использовать свой "перегруженный" класс через фасад напрямую:
App\Facades\SaveImage::save();

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

Комментарии

  1. я понимаю, что примеры выдуманные, но все же, почему у вас интерфейс декларирует статические методы?
    учитывая то, что этот материал в бОльшей степени для начинающих, зачем учить заведомо неверных примерах, которые на этапе интерпретации выдадут ошибки?
    но в целом за статью спасибо.

    ОтветитьУдалить
    Ответы
    1. Автор уже разбирается в фасадах и сервис провайдерах но ещё видимо не разобрался что такое интерфейсы, как говорится постигает науку с другого конца )))))

      Удалить
    2. "А вот если ты, мой друг, придирчивый педант и в курсе, что писать static методы не по фэншую и ты хочешь чтобы твой код был легок к пониманию и интуитивно понятен - добро пожаловать в мир фасадов! "

      Удалить
    3. Так вроде в конце статьи все было переписано без использования статических методов.

      Удалить
  2. Статья понравилась. Но в параграфе про фасады не понятно, почему в итоге реализуется именно saveImageAws, а не SaveImageDisk ?

    ОтветитьУдалить
    Ответы
    1. Потому что при вызове App\Contracts\SaveImage будет возвращаться обьект из сервис провайдера который зарегистрирован в контейнере. А в сервис-провайдере связь с saveImageAws. То есть через сервис-провайдер ты управляешь экземпляр какого класса возвращать по умолчанию при запросе контракта App\Contracts\SaveImage

      Удалить
  3. ахаха. у автора куча ошибок в решениях задач из учебника.

    ОтветитьУдалить
    Ответы
    1. автор писал эти решения задач 10 лет назад, а сейчас уже 5 лет как веслает.

      Удалить
  4. Негодуйте сколько хотите, но я наконец-то нашел статью, где простым языком объясняется архитектура Laravel. Спасибо автору за это!

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения из этого блога

Прата С. Решение задач 6 главы

На решение этих задач у меня ушло 2 дня. Становится все интереснее и интереснее! и черт возьми, я наконец-то поняла работу с файлами! прошу прощения за корявые условия. просто в нормальном формате электронной книги у меня нет, потому скрины задач просто пропущены через Файнридер.

Прата С. решение задач 5 главы

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

Прата С. Решение 3 и 4 главы

Повторюсь, взяла только интересные мне задания, т.е. с элементами того что поняла плохо/не встречала ранее. Сделала за сегодня. с удовольствием отмечаю что сдвиги  в положительную сторону  хотя-бы в стиле есть . в 4 главе ясно объяснено почему при вводе нескольких строк начинаются проблемы, и как этого избежать, подробно описана работа с входным потоком. Как работать со строками стиля Си и класса String, разница. для себя я сделала выводы что лучше всего, конечно-же класс string. намного меньше мороки. и метод ввода getline в обоих случаях. в задачах разница проиллюстрирована. и кстати, указатели больше не вызывают у меня ужаса и трепета. много нового узнала о видах структур. эта глава расставила все по местам в моей голове. правда, зачем в задачи на СР втиснута работа с массивом array - загадка. он ведь толком не объяснен, но понять его интуитивно можно, правда, зачем его использовать не зная тонкостей, как обычный массив с немного иным объявле...