Про интеграцию сторонних библиотек в Laravel

Pavel Buchnev
4 min readJul 8, 2020

--

Всем привет. Эту статью я хочу посвятить теме интеграции со сторонними библиотеками.

К написанию этой статьи меня подтолкнуло прочтение вот этой вот статьи, которая рассказывает про интеграцию с 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

--

--

Pavel Buchnev
Pavel Buchnev

Written by Pavel Buchnev

Senior PHP Developer | Contributor to Spiral Framework 🚀 | Enthusiast of RoadRunner & long-running applications | Creator of Buggregator

Responses (3)