Статьи

Мой процесс сборки пакетов Laravel

Эта статья была рецензирована Клаудио Рибейро . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


Пакеты — действительно важная часть опыта Laravel (как и в любой другой среде). Что бы нам ни нужно было сделать, вероятно, уже есть пакет для этого; готов для composer require внести немного магии.

Подарочный пакет Laravel

Несколько недель назад у меня появилась идея для нового. Я работаю в AdEspresso , где у нас есть Symfony FeatureBundle , которая обрабатывает переключение функций в наших проектах. Это действительно хороший кусок кода, который мы используем для выпуска новых функций только для определенных групп пользователей. Итак, я спросил себя … почему бы не перенести его на Laravel? Так родилась моя идея пакета Laravel Feature .

Прежде чем начать, мне пришлось задать себе еще один вопрос: по какому пути я должен идти, чтобы создать пакет для Laravel 5? Я нашел множество советов в Интернете, много рекомендаций, но не реальную методологию о том, как я могу создать пакет для моей любимой платформы. В этой статье я попытаюсь объяснить, как я подготовил для этого свою среду разработки и какой выбор я сделал при сборке пакета. Не разработка каждой строки кода как таковой, а фактический рабочий процесс получения ничего от полного пакета.

Очевидно, не принимайте каждый шаг как должное: я полностью осознаю, что что-то может быть небрежным или просто неправильным. Не стесняйтесь оставлять мне отзывы, если хотите, оставив комментарий под статьей!

Функция пометки?

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

Типичным примером приложения для обозначения признаков является концепция «выброса канареек» (канареек использовали в шахтах для обнаружения токсичных газов, потому что они были выбиты им раньше, чем люди — они были «тестерами»). Представьте себе, что вы хотите развернуть новую функцию: однако мы хотим ограничить доступ к ней в первую неделю для лучшего мониторинга, чтобы лучше понять, что произошло, если что-то пойдет не так. У нас есть определенный набор пользователей (например, 5% от нашей общей базы пользователей), идентифицированных как тестировщики, которых мы «используем» для этого эксперимента. Внедрение функции маркировки означает больший контроль над жизненным циклом нашей функции программного обеспечения и позволяет избежать ошибок и ошибок.

Laravel не предлагает ничего из коробки для пометки функций. Компонент авторизации, вероятно, похож на него: однако он всегда связан с конкретным ресурсом. Определенно не наш случай: функция может задействовать более одного ресурса одновременно, а иногда и ни одного! Итак … давайте сделаем это сами!

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

Это сказало… К пакету машины!

Подготовка окружающей среды

Я всегда использую Homestead Improved в своих проектах, и вы должны делать то же самое. Через несколько минут у нас будет новая виртуальная машина для наших экспериментов! Если мы действительно, очень ленивы, мы могли бы попробовать LaraPrep , полезный скрипт, который готовит все. Сейчас он совместим только с Linux, но также должен работать на вашем Mac.

Эта статья будет охватывать версию фреймворка 5.4.

Пространства имен и папки

Первый вопрос, на который нужно ответить при разработке нового пакета: «где разместить мой код?»

Обычно рекомендуется использовать автозагрузку PSR-4 для создания другого пространства имен, отделенного от кода проекта, и «связать» его с выделенной папкой.

Значение по умолчанию для элемента в файле composer.json :

 "psr-4": { "App\\": "app/" } 

все, что нам нужно сделать, это добавить новый элемент, как

 "psr-4": { "App\\": "app/", "LaravelFeature\\": "LaravelFeature/src" } 

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

Играя с правым скелетом

Если вы посмотрите на код на GitHub , вы увидите различные файлы, такие как CHANGELOG.md , CHANGELOG.md и многие другие. Я не изобрел их: они являются обычной практикой и предложены пакетом PHPLeague Skeleton Package , фантастическим образцом для всех, кто хочет разработать новый пакет PHP.

Давайте посмотрим на его наиболее важные части:

  • папка src : содержит исходный код нашего пакета.
  • папка test : потому что мы собираемся писать тесты! И мы всегда пишем тесты, верно?
  • файл README.md : для README.md с пакетом.
  • файл LICENSE.md : указание деталей лицензии поможет разработчикам понять, что они могут делать с нашим кодом и как.
  • .scrutinizer.yml , .styleci.yml и .travisci.yml : Scrutinizer , StyleCI и TravisCI — великолепные (и бесплатные для проектов с открытым исходным кодом) сервисы. Они анализируют качество кода, исправляют проблемы со стилями и запускают тесты соответственно.

Прежде чем мы продолжим, коротко о тестах: не переоценивайте, но и не переоценивайте . Наступил 2017 год, не нужно было бы об этом предупреждать людей, но я все еще вижу много пакетов без единого теста, чтобы подтвердить их качество (каламбур).

В кодекс: домен

При написании пакета хорошим подходом является абстракция логики домена, которую мы хотим использовать для решения проблемы, а затем кодирование реализации. У Laravel есть хороший сервисный контейнер, который позволяет нам привязать интерфейс к конкретному классу и поддерживать свободный код в нашем коде: давайте его использовать.

Первая остановка: GitHub хранилище пакета. В папке src есть папка Domain : давайте откроем ее. Не стесняйтесь изучать код. Вы видите какой-нибудь специфичный для Laravel код? Нет, потому что логика нашего домена должна быть отделена от реальной реализации.

LaravelFeature\Domain\Model\Feature — хороший пример. Он описывает, что такое функция и каковы ее функции. Каждая функция имеет свое имя и свой статус (включен или отключен в системе).

 <?php namespace LaravelFeature\Domain\Model; class Feature { private $name; private $isEnabled; public static function fromNameAndStatus($name, $isEnabled) { $feature = new self($name, (bool) $isEnabled); return $feature; } private function __construct($name, $isEnabled) { $this->name = $name; $this->isEnabled = $isEnabled; } public function getName() { return $this->name; } public function isEnabled() { return $this->isEnabled; } public function setNewName($newName) { $this->name = $newName; } public function enable() { $this->isEnabled = true; } public function disable() { $this->isEnabled = false; } } 

Мы также можем выбрать новое имя и принять решение включить или отключить его: для этого setNewName методы setNewName , enable и disable . Наконец, фабричный метод fromNameAndStatus позволяет нам создавать наш объект наилучшим (и наиболее выразительным) способом без использования конструктора.

Давайте посмотрим на класс FeatureManager . Каждая операция, которую мы делаем над функциями, начинается здесь (мы скоро увидим, как) Он работает с объектами Feature и экземпляром класса, который реализует LaravelFeature\Domain\Repository\FeatureRepositoryInterface .

Давайте откроем это.

 <?php namespace LaravelFeature\Domain\Repository; use LaravelFeature\Domain\Model\Feature; use LaravelFeature\Featurable\FeaturableInterface; interface FeatureRepositoryInterface { public function save(Feature $feature); public function remove(Feature $feature); public function findByName($featureName); public function enableFor($featureName, FeaturableInterface $featurable); public function disableFor($featureName, FeaturableInterface $featurable); public function isEnabledFor($featureName, FeaturableInterface $featurable); } 

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

Это все для предметной логики.

В кодекс: реализация

Репозиторий

Теперь, когда мы закончили с доменной логикой, пришло время перейти к реализации. Давайте начнем с конкретного класса, который мы «свяжем» с интерфейсом, который мы только что видели.

EloquentFeatureRepository — это класс, который будет реализовывать FeatureRepositoryInterface . Laravel использует Eloquent по умолчанию, поэтому хорошая идея использовать Eloquent в качестве базы по умолчанию.

Мы не будем вдаваться в детали репозитория: он делает довольно простые вещи. Давайте посмотрим, как связать с поставщиком услуг вместо этого. Поставщики услуг — лучшее место для регистрации новых привязок в нашем контейнере услуг (и в нашем приложении). Если вы часто используете Laravel, вы, вероятно, зарегистрировали поставщика услуг в файле config/app.php для 99% установленных пакетов. Для моего пакета выбран FeatureServiceProvider .

Волшебство происходит в методе register() :

 ... $config = $this->app->make('config'); $this->app->bind(FeatureRepositoryInterface::class, function () use ($config) { return app()->make($config->get('features.repository')); }); 

FeatureRepositoryInterface привязан к классу EloquentFeatureRepository (это значение по умолчанию для элемента features.repository в файле конфигурации).

Ох, об этом …

Файл конфигурации

Еще одна полезная практика при создании пакетов Laravel — предоставление файла конфигурации, чтобы конечный разработчик мог опубликовать и использовать его для настройки пакета в соответствии с его бизнес-потребностями. Чтобы добавить файл конфигурации в пакет, все, что нам нужно сделать, это выбрать место для него. Использование описательного имени — лучший выбор: вот то, которое я добавил для пакета .

В поставщике услуг, который мы ранее сделали,

 $this->publishes([ __DIR__.'/../Config/features.php' => config_path('features.php'), ]); 

call гарантирует, что при публикации ресурсов вендора мы также получим блестящий файл features.php в папке config основного проекта.

Кроме того, еще один звонок

 $this->mergeConfigFrom(__DIR__.'/../Config/features.php', 'features'); 

идеально подходит, чтобы позволить разработчику выбрать, что перезаписать, а что оставить как есть. Меньше загрузочного кода для написания, больше довольных разработчиков!

Теперь вернемся к поставщику услуг. Мы также можем увидеть два вызова нескольких частных методов: registerBladeDirective и registerConsoleCommand . Давайте начнем с первого, представляя…

Директива о клинках

Директива Blade для проверки, включена ли функция или нет, может быть хорошей идеей. Его реализация очень проста: одна инструкция в методе registerBladeDirective .

 private function registerBladeDirective() { Blade::directive('feature', function ($featureName) { return "<?php if (app('LaravelFeature\\Domain\\FeatureManager')->isEnabled($featureName)): ?>"; }); Blade::directive('endfeature', function () { return '<?php endif; ?>'; }); } 

С помощью этих простых вызовов directive() мы теперь можем использовать @feature и @endfeature в наших шаблонах Blade. Если функция, переданная в качестве параметра, включена в системе, будет показан код в блоке. В противном случае он будет скрыт.

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

Консольная команда

Теперь заключительная часть: как насчет команды, которая сканирует все представления нашего проекта в поисках директив @feature и автоматически добавляет эти функции в систему с помощью FeatureManager ? Мы собираемся реализовать это, используя консольную команду: давайте посмотрим, как зарегистрировать новую в пакете.

Вот что находится в методе registerConsoleCommand .

 private function registerConsoleCommand() { if ($this->app->runningInConsole()) { $this->commands([ ScanViewsForFeaturesCommand::class ]); } } 

Здесь мы делаем просто: когда «это приложение работает в консоли», добавьте команду ScanViewsForFeaturesCommand в список команд, которые может выполнить разработчик.

Кроме этого, здесь нет ничего особенного. Довольно линейный процесс. Однако … чего-то еще не хватает, верно?

Фасад

О, да! Фасад все еще отсутствует! Предоставить фасад нашим разработчикам означает также дать им хороший инструмент для улучшения принятия пакета.

Давайте построим и разместим в подходящем месте .

 <?php namespace LaravelFeature\Facade; use LaravelFeature\Domain\FeatureManager; use Illuminate\Support\Facades\Facade; class Feature extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return FeatureManager::class; } } 

Мы привязываем фасад к классу FeatureManager . Это означает, что если разработчик добавляет его в config/app.php , он может вызвать

 Feature::enable(); 

вместо создания экземпляра класса FeatureManager и последующего вызова enable() для него. Совсем улучшение (не каждый раз, но мы не собираемся сегодня освещать драму фасада )!

Совет

Давайте закончим эту статью некоторыми общими советами:

  • используйте теги git для определения версий пакета. После нажатия на Github должна появиться возможность увидеть новую версию уже на Packagist.
  • не забудьте дважды проверить зависимости в файле composer.json . При работе с некоторыми компонентами Laravel вы должны обязательно включить правильную illuminate/ пакет в качестве зависимости. В моем случае, я использовал компоненты из illuminate/database и illuminate/support .
  • экосистема Laravel предлагает различные пакеты для помощи с тестами . Конечно, PHPUnit уже делает кучу вещей, но такие пакеты, как mockery/mockery и orchestra/testbench могут помочь.
  • напишите как можно больше документации. Если мы хотим, чтобы наш пакет распространялся по сети, первым делом нужно объяснить все его возможности. Написание примеров — это хорошо, объяснение теории и концепций (при необходимости) — это хорошо. Делать это на хорошем английском еще лучше!

Вывод

Это рабочий процесс, которому я следую при разработке пакетов Laravel — а вы? У вас когда-нибудь был другой опыт разработки пакетов, которым вы хотели бы поделиться? Пожалуйста, оставьте комментарий ниже — давайте сделаем хороший рабочий процесс создания пакета вместе!