Эта статья была рецензирована Франческо Малатеста . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Каждый фреймворк дает разработчикам возможность расширять систему с помощью пакетов / расширений. Как правило, мы можем подключить нашу логику в любой момент, когда мы хотим обеспечить определенную функциональность, и 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": "[email protected]", "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": "[email protected]", "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 не найдено ни одного теста. Это не остановит процесс установки, поэтому мы можем продолжить!
Последняя часть конфигурации нашего пакета — связать загруженный пакет с репозиторием GitHub.
// assuming you're inside your project folder cd vendor/whyounes/laravel-two-factor-auth git init origin [email protected]: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
Теперь мы должны увидеть тег на GitHub под окном выбора ветки:
Непрерывная интеграция
Инструменты непрерывной интеграции помогают нам интегрировать новые части в существующий код и отправлять уведомления о любых нарушениях. Это также дает нам значок (изображение со ссылкой) для статуса кода. Для этого пакета мы используем TravisCI в качестве инструмента CI.
// .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
и протестируйте реализацию вашего пакета. В конце вы можете продемонстрировать интеграционный тест, демонстрируя, что пакет работает должным образом. Я надеюсь, что это поможет вам создать несколько пакетов, которые будут возвращены сообществу ?
Если у вас есть какие-либо вопросы или комментарии, вы можете опубликовать их ниже! Каков ваш рабочий процесс разработки пакетов?