Про интеграцию сторонних библиотек в Laravel
Всем привет. Эту статью я хочу посвятить теме интеграции со сторонними библиотеками.
К написанию этой статьи меня подтолкнуло прочтение вот этой вот статьи, которая рассказывает про интеграцию с PayPal с помощью библиотеки paypal/rest-api-sdk-php.
По ходу данной статьи постараемся привести в порядок код контроллера и поймем, в чем одна из главных проблем, а также увеличим кол-во кода в три раза :)))
Проблема
Итак, будем считать, что все быстренько посмотрели код из данной статьи, а более опытные сразу поняли проблему, которая заключается в способе подключения и использовании библиотеки.
Посмотрим на данный кусок кода
Во первых подключение библиотеки происходит непосредственно в контроллере и если подключение потребуется где-то еще, то подключать придется заново.
// Решение от новичковclass PayPal {
public static function instance() {
$paypal_conf = \Config::get('paypal');
$_api_context = new ApiContext(new OAuthTokenCredential(
$paypal_conf['client_id'],
$paypal_conf['secret'])
);
$_api_context->setConfig($paypal_conf['settings']);
return $_api_context;
}
}// Так они обычно внедряют код в контроллеры
public function __construct()
{
$this->_api_context = PayPal::instance();
}
Если видите такой код или его вариации, знайте, вас ждут мучения :)
Во вторых код жестко завязан на использовании PayPal. В рамках данной статьи это оправдано, но чем старше становишься, тем больше понимаешь, что лучше, если проект знает минимум о том, с чем он работает и абстрагироваться от конкретной библиотеки. Это позволит в случае проблем, или расширении функционала не искать по всему проекту, где же использовался код библиотеки и внести изменения всего в одном месте.
В третьих данный код не поддается тестированию. Кстати, такой код легко выдает программистов, которые никогда не писали тесты.
Ладно, давайте уже внедрим зависимость, хватит из пустого в порожнее…
Внедрение зависимостей
Большинство из читателей хорошо знают что такое Service Container и для чего он нужен. Основное его предназначение — управление зависимостями и их внедрение в классах, которые создаются через него. Контроллер относится к таким классам, как и многое в Laravel. Простыми словами, хотите, чтобы фреймворк автоматические внедрял зависимости, создавайте объекты через Service Container.
В качестве зависимостей, в идеале, принято использовать интерфейсы (контракты), поэтому создадим первым делом все необходимое.
Контракты
Контракты помогут нам абстрагироваться от конкретной реализации и избежать завиимости от конкртеных классов библиотеки. В проекте я вижу следующий набор контактов:
—
PaymentGateway — отвечает за взаимодействие с платежным шлюзом. В нашем коде всего два действия, это создание платежа и проверка статуса.
// Заменит эту часть
$payment->create($this->_api_context);
Order и OrderItem — отвечает за формирование заказа, который оплачивает пользователь. Если хорошо подумать, то в любом процессе оплаты мы платим не в пустоту, а за конкретный товар по конкретному прайсу, так что это будет он.
// Заменит эту часть.
$item_1 = new Item();
$item_1->setName('Mobile Payment')
->setCurrency('USD')
->setQuantity(1)
->setPrice($amountToBePaid);$item_list = new ItemList();
$item_list->setItems(array($item_1));$amount = new Amount();
$amount->setCurrency('USD')->setTotal($amountToBePaid);
CreatedPayment — созданный платеж на стороне платежной системы. Данный ValueObject по сути должен содержать только payment_id и ссылку на редирект.
// Заменит эту часть.
foreach ($payment->getLinks() as $link) {
if ($link->getRel() == 'approval_url') {
$redirect_url = $link->getHref();
break;
}
}\Session::put('paypal_payment_id', $payment->getId());if (isset($redirect_url)) {
return Redirect::away($redirect_url);
}
Payment — по сути этот ValueObject будет использоваться в тех случаях, когда нам нужно будет получить информацию от платежной системы, например узнать статус платежа.
// Заменит эту часть.
$payment = Payment::get($payment_id, $this->_api_context);
$execution = new PaymentExecution();
$execution->setPayerId($request->PayerID);
$result = $payment->execute($execution, $this->_api_context);
PaymentStatus — ValueObject который возвращает статус платежа в унифицированном виде. Суть в том, что разные платежные системы имеют разное наименование статусов (например, PayPal — approved, YandexKassa — succeeded и т.д.) и нам нужно свести к единому виду.
// Заменит эту часть.
if ($result->getState() == 'approved') {
session()->flash('success', 'Платеж прошел успешно');
return Redirect::route('/');
}
Ну а теперь сами контракты
Имплементация
В коде выше я создал структуру будущего платежного шлюза, который ничего не знает про PayPal, но при этом он нам показывает основные методы, которые мы уже прямо сейчас можем использовать в нашем контроллере. Перепишем его с учетом текущих интерфейсов
Как мы видим, контроллер не использовал ни единой строчки кода из библиотеки PayPal, а оперирует исключительно интерфейсами нашего проекта, а это значит, что при изменении шлюза или смене библиотеки нам не нужно будет вносить изменения во всем проекте.
А теперь создадим наш первый платежный шлюз PayPalPaymentGateway
И теперь свяжем наш контракт PaymentGateway с PayPalPaymentGateway
С этого момента, каждый раз, когда контроллеры или у service container будет запрашиваться интерфейс PaymentGateway
, он нам вернет экземпляр объекта PayPalPaymentGateway
и если мы захотим подключить YandexKassaPaymentGateway
нам всего навсего придется реализовать для него соответствующий интерфейс и переключиться с PayPal на YandexKassa в AppServiceProvider
app(PaymentGateway::class); // PayPalPaymentGateway
Теперь наш код получится структурированным, готовый к подключению в любом месте нашего проекта. (Ага, а кода стало в три раза больше)
P.S. когда начинал писать статью, думал, что она получиться малюсенькой и кода будет меньше, но я рад, что его столько, ведь он поможет в понимании написания отвязанного и полностью тестируемого кода, который не привязан к конкретной библиотеке и если уделить должное внимание, то такой код можно легко переносить с проекта на проект.
Код не претендует на идеальный, подан мной для быстрого понимания сути и проблемы и ее решения и я уверен, что его вполне достаточно, чтобы вам стать ниндзя по написанию простых платежных шлюзов :)
Спасибо, что прочитали.
Предлагайте темы для следующих статей!
Если вам понравилась статья, то буду рад увидеть вас на patreon