Статьи

Альтернативный рабочий процесс разработки пакетов Laravel

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


Каждый фреймворк дает разработчикам возможность расширять систему с помощью пакетов / расширений. Как правило, мы можем подключить нашу логику в любой момент, когда мы хотим обеспечить определенную функциональность, и Laravel не является исключением! После статьи моего коллеги-автора Франческо Малатеста о его процессе разработки пакета Laravel я заметил, что у меня это немного по-другому, и я хотел поделиться им с вами!

Пакет Laravel

Демо Пакет

В качестве демонстрационного пакета мы собираемся создать пакет двухфакторной аутентификации Laravel для этой статьи . Пакет будет обрабатывать аутентификацию пользователя с помощью кода подтверждения, передаваемого через такие среды, как Nexmo , Twilio и т. Д. Окончательный пакет можно посмотреть здесь .

Настройка репозитория

Прежде чем мы начнем разработку, нам нужно создать новый репозиторий внутри GitHub, чтобы мы могли протолкнуть / вытащить его на этапе разработки.

Composer поддерживает ключ repositories внутри файла composer.json . Мы можем использовать это для указания наших пользовательских пакетов, которые еще не существуют в Packagist.

 { // .... "repositories": [ { "type": "vcs", "url": "https://github.com/Whyounes/laravel-two-factor-auth-demo" } ], // .... } 

Теперь мы можем потребовать наш пакет как обычно.

 { // .... "require": { "php": ">=5.6.4", "laravel/framework": "5.4.*", "laravel/tinker": "~1.0", "Whyounes/laravel-two-factor-auth-demo": "dev-master" }, // .... } 

Мы можем указать ветку или номер версии. Если мы хотим использовать ветку, перед ней должен стоять префикс dev- . Вы можете прочитать больше о настройке репозиториев на веб-сайте Packagist .

Пакет Скелет

Так как Composer идентифицирует пакеты с помощью файла composer.json , нам нужно создать один для нашего пакета и отправить его в хранилище.

 { "name": "whyounes/laravel-two-factor-auth", "type": "library", "description": "Laravel two factor authentication", "keywords": [ "auth", "passwordless" ], "homepage": "https://github.com/whyounes/laravel-two-factor-auth", "license": "MIT", "authors": [ { "name": "Rafie Younes", "email": "younes.rafie@gmail.com", "homepage": "http://younesrafie.com", "role": "Developer" } ], "require": { "php": "^5.5.9 || ^7.0", "illuminate/database": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/config": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/events": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/auth": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "twilio/sdk": "^5.4" }, "require-dev": { "phpunit/phpunit": "^4.8 || ^5.0", "orchestra/testbench": "~3.0", "mockery/mockery": "~0.9.4" }, "autoload": { "psr-4": { "Whyounes\\TFAuth\\": "src" }, "classmap": [ "tests" ] } } без { "name": "whyounes/laravel-two-factor-auth", "type": "library", "description": "Laravel two factor authentication", "keywords": [ "auth", "passwordless" ], "homepage": "https://github.com/whyounes/laravel-two-factor-auth", "license": "MIT", "authors": [ { "name": "Rafie Younes", "email": "younes.rafie@gmail.com", "homepage": "http://younesrafie.com", "role": "Developer" } ], "require": { "php": "^5.5.9 || ^7.0", "illuminate/database": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/config": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/events": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "illuminate/auth": "5.1.* || 5.2.* || 5.3.* || 5.4.*", "twilio/sdk": "^5.4" }, "require-dev": { "phpunit/phpunit": "^4.8 || ^5.0", "orchestra/testbench": "~3.0", "mockery/mockery": "~0.9.4" }, "autoload": { "psr-4": { "Whyounes\\TFAuth\\": "src" }, "classmap": [ "tests" ] } } 

После запуска команды composer update в корневой папке проекта Laravel мы видим ошибку, что в папке tests не найдено ни одного теста. Это не остановит процесс установки, поэтому мы можем продолжить!

Обновление Composer провалилось

Последняя часть конфигурации нашего пакета — связать загруженный пакет с репозиторием GitHub.

 // assuming you're inside your project folder cd vendor/whyounes/laravel-two-factor-auth git init origin git@github.com:Whyounes/laravel-two-factor-auth.git git commit -am "Init repo" git push origin master -f // force push for the first time 

Структура пакета

Эта часть немного противоречива, мы не можем все договориться об одной единственной структуре . Вот наша структура пакета:

  config/ auth.php services.php  migrations/ 2014_10_1_000000_add_tfa_columns_to_users_table.php 2016_12_1_000000_create_tokens_table.php  resources/  views/ verification_code.blade.php  src/  Contracts/ VerificationCodeSenderInterface.php  Controllers/ TFAController.php  Exceptions/ TFANotEnabledException.php UserNotFoundException.php  Models/ TFAuthTrait.php Token.php  Providers/ TwoFAProvider.php  Services/ Twilio.php  tests/ 

Для установки пакета мы используем провайдера Laravel:

 // config/app.php // ... 'providers' => [ // ... Whyounes\TFAuth\TwoFAProvider::class, }; 

Класс провайдера выглядит так:

 // src/Providers/TwoFAProvider.php namespace Whyounes\TFAuth; use Illuminate\Auth\Events\Authenticated; use Illuminate\Routing\Router; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; use Twilio\Rest\Client; use Whyounes\TFAuth\Contracts\VerificationCodeSenderInterface; use Whyounes\TFAuth\Services\Twilio; /** * Class TwoFAProvider * * @package Whyounes\TFAuth */ class TwoFAProvider extends ServiceProvider { public function boot() { $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); $this->mergeConfigFrom( __DIR__ . '/../../config/auth.php', 'auth' ); $this->mergeConfigFrom( __DIR__ . '/../../config/services.php', 'services' ); $this->loadViewsFrom(__DIR__.'/resources/views', 'tfa'); $this->publishes([ __DIR__.'/../../resources/views' => resource_path('views/vendor/tfa'), ]); } public function register() { $this->registerBindings(); $this->registerEvents(); $this->registerRoutes(); } public function registerBindings() { $this->app->singleton(Client::class, function () { return new Client(config('services.twilio.sid'), config('services.twilio.token')); }); $this->app->bind(VerificationCodeSenderInterface::class, Twilio::class); } public function registerEvents() { // Delete user tokens after login if (config('auth.delete_verification_code_after_auth', false) === true) { Event::listen( Authenticated::class, function (Authenticated $event) { $event->user->tokens() ->delete(); } ); } } public function registerRoutes() { /** @var $router Router */ $router = App::make("router"); $router->get("/tfa/services/twilio/say/{text}", ["as" => "tfa.services.twilio.say", "uses" => function ($text) { $response = "<Response><Say>" . $text . "</Say></Response>"; return $response; }]); } } 

Давайте рассмотрим каждый метод здесь:

  • Метод boot будет публиковать ресурсы нашего пакета (config, migrations, views).
  • Метод registerBindings связывает VerificationCodeSenderInterface с конкретной реализацией. Пакет использует службу Twilio по умолчанию, поэтому мы создаем класс src/Services/Twilio который реализует наш интерфейс и определяет необходимые методы.
 // src/Services/Twilio.php namespace Whyounes\TFAuth\Services; use Twilio\Rest\Client; use Whyounes\TFAuth\Contracts\VerificationCodeSenderInterface; class Twilio implements VerificationCodeSenderInterface { /** * Twilio client * * @var Client */ protected $client; /** * Phone number to send from * * @var string */ protected $number; public function __construct(Client $client) { $this->client = $client; $this->number = config("services.twilio.number"); } public function sendCodeViaSMS($code, $phone, $message = "Your verification code is %s") { try { $this->client->messages->create($phone, [ "from" => $this->number, "body" => sprintf($message, $code) ]); } catch (\Exception $ex) { return false; } return true; } public function sendCodeViaPhone($code, $phone, $message = "Your verification code is %s") { try { $this->client->account->calls->create( $phone, $this->number, ["url" => route('tfa.services.twilio.say', ["text" => sprintf($message, $code)])] ); } catch (\Exception $ex) { return false; } return true; } // getters and setter } - // src/Services/Twilio.php namespace Whyounes\TFAuth\Services; use Twilio\Rest\Client; use Whyounes\TFAuth\Contracts\VerificationCodeSenderInterface; class Twilio implements VerificationCodeSenderInterface { /** * Twilio client * * @var Client */ protected $client; /** * Phone number to send from * * @var string */ protected $number; public function __construct(Client $client) { $this->client = $client; $this->number = config("services.twilio.number"); } public function sendCodeViaSMS($code, $phone, $message = "Your verification code is %s") { try { $this->client->messages->create($phone, [ "from" => $this->number, "body" => sprintf($message, $code) ]); } catch (\Exception $ex) { return false; } return true; } public function sendCodeViaPhone($code, $phone, $message = "Your verification code is %s") { try { $this->client->account->calls->create( $phone, $this->number, ["url" => route('tfa.services.twilio.say', ["text" => sprintf($message, $code)])] ); } catch (\Exception $ex) { return false; } return true; } // getters and setter } - // src/Services/Twilio.php namespace Whyounes\TFAuth\Services; use Twilio\Rest\Client; use Whyounes\TFAuth\Contracts\VerificationCodeSenderInterface; class Twilio implements VerificationCodeSenderInterface { /** * Twilio client * * @var Client */ protected $client; /** * Phone number to send from * * @var string */ protected $number; public function __construct(Client $client) { $this->client = $client; $this->number = config("services.twilio.number"); } public function sendCodeViaSMS($code, $phone, $message = "Your verification code is %s") { try { $this->client->messages->create($phone, [ "from" => $this->number, "body" => sprintf($message, $code) ]); } catch (\Exception $ex) { return false; } return true; } public function sendCodeViaPhone($code, $phone, $message = "Your verification code is %s") { try { $this->client->account->calls->create( $phone, $this->number, ["url" => route('tfa.services.twilio.say', ["text" => sprintf($message, $code)])] ); } catch (\Exception $ex) { return false; } return true; } // getters and setter } 
  • Метод registerEvents удалит ранее использованный токен при аутентификации, если для значения конфигурации установлено значение true .
  • Метод registerRoutes добавляет маршрут для отправки кода подтверждения по телефону.

Если, например, мы хотим переключиться с Twilio на Nexmo, нам нужно только создать новый класс, который реализует VerificationCodeSenderInterface и привязать его к контейнеру.

Наконец, мы добавляем черту в класс модели User :

 // app/User.php class User extends Authenticatable { use \Whyounes\TFAuth\Models\TFAuthTrait; // ... } 

тесты

Тесты обязательны перед публикацией любого кода, не только для Laravel. НЕ используйте пакеты, которые не имеют тестов. Для Laravel вы можете использовать пакет orchestra/testbench , чтобы помочь вам, и документация предоставляет действительно хороший поток для тестирования компонентов Laravel, таких как Storage , Eloquent и т. Д.

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

Tagging

Мы не можем оставить только основную ветку для нашего пакета. Нам нужно пометить первую версию для начала. Это зависит от того, как далеко мы продвинулись в стадии разработки функции, и какова цель здесь!

Поскольку наш пакет не так сложен и не имеет много функций для сборки, мы можем пометить его как первую стабильную версию ( 1.0.0 ). Вы можете проверить веб-сайт документации Git для получения более подробной информации о тегах, и вот список команд для добавления нового тега в наш репозиторий:

 git tag git tag -a v1.0.0 -m "First stable version" git push origin --tags 

Tagging

Теперь мы должны увидеть тег на GitHub под окном выбора ветки:

Теги GitHub

Непрерывная интеграция

Инструменты непрерывной интеграции помогают нам интегрировать новые части в существующий код и отправлять уведомления о любых нарушениях. Это также дает нам значок (изображение со ссылкой) для статуса кода. Для этого пакета мы используем TravisCI в качестве инструмента CI.

Значок TravisCI

 // .travis.yml language: php php: - 5.6 - 7.0 - 7.1 sudo: true install: - travis_retry composer install --no-interaction --prefer-source 

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

Вывод

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

Если у вас есть какие-либо вопросы или комментарии, вы можете опубликовать их ниже! Каков ваш рабочий процесс разработки пакетов?