Если вам пока страшно от подобных словосочетаний, то не
беспокойтесь, это абсолютно нормально. Если вы уже не новичок в 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
В общем – экспериментируйте, учитесь, но используйте знания во благо и уместно.
Помните, что скорее всего после вас еще будет кто-то править этот код :)
я понимаю, что примеры выдуманные, но все же, почему у вас интерфейс декларирует статические методы?
ОтветитьУдалитьучитывая то, что этот материал в бОльшей степени для начинающих, зачем учить заведомо неверных примерах, которые на этапе интерпретации выдадут ошибки?
но в целом за статью спасибо.
Автор уже разбирается в фасадах и сервис провайдерах но ещё видимо не разобрался что такое интерфейсы, как говорится постигает науку с другого конца )))))
Удалить"А вот если ты, мой друг, придирчивый педант и в курсе, что писать static методы не по фэншую и ты хочешь чтобы твой код был легок к пониманию и интуитивно понятен - добро пожаловать в мир фасадов! "
УдалитьТак вроде в конце статьи все было переписано без использования статических методов.
УдалитьСтатья понравилась. Но в параграфе про фасады не понятно, почему в итоге реализуется именно saveImageAws, а не SaveImageDisk ?
ОтветитьУдалитьПотому что при вызове App\Contracts\SaveImage будет возвращаться обьект из сервис провайдера который зарегистрирован в контейнере. А в сервис-провайдере связь с saveImageAws. То есть через сервис-провайдер ты управляешь экземпляр какого класса возвращать по умолчанию при запросе контракта App\Contracts\SaveImage
Удалитьахаха. у автора куча ошибок в решениях задач из учебника.
ОтветитьУдалитьавтор писал эти решения задач 10 лет назад, а сейчас уже 5 лет как веслает.
УдалитьНегодуйте сколько хотите, но я наконец-то нашел статью, где простым языком объясняется архитектура Laravel. Спасибо автору за это!
ОтветитьУдалить+++
УдалитьСпасибо за материал :)
ОтветитьУдалитьСПАСИБО!!!
ОтветитьУдалить