Статьи

Освободиться от Guzzle5 с помощью PHP-HTTP и HTTPlug

Эта статья была рецензирована Марком Саги-Казаром и Дэвидом Бухманном . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


В предыдущей серии мы создали PHP-клиент для Diffbot . Клиент работает хорошо и широко используется — мы даже протестировали его на живом приложении, чтобы убедиться, что он на высоте — но это сильно зависит от Guzzle 5.

Есть две проблемы с этим:

  1. Жрет 6 и поддерживает PSR 7 . Хотя автор Guzzle утверждает, что Guzzle 5 будет поддерживаться в обозримом будущем, безопаснее скептически относиться к его долговечности. Кроме того, хотя PSR 7 может иметь свои причуды , хорошо следовать PSR хотя бы для совместимости с другими проектами.
  2. Кто-то, внедряющий наш клиент в свое приложение, может уже использовать предпочтительный HTTP-клиент и хотел бы использовать его вместо Guzzle. Мы должны позволить легко внедрить любой HTTP-клиент в наш SDK.

По совпадению, есть новый проект, позволяющий нам сделать это: HTTPlug .

Сетевой адаптер питания

Примечание: вам не нужно знать внутреннюю логику Diffbot SDK, чтобы следовать ей. Процесс, описанный в этой статье, применим к любому пакету с конкретной реализацией HTTP-клиента и прост в использовании.

PHP-HTTP и HTTPlug

PHP-HTTP является организацией Github для связанных с HTTP инструментов в PHP. Он предоставляет HTTPlug , набор интерфейсов и исключений для определения минимального HTTP-контракта клиента поверх запроса и ответа PSR-7. Реализации этого контракта предоставляют виртуальный пакет php-http/client-implementation .

Это означает, что тот, кто использует Guzzle 6, может создать composer require php-http/guzzle6-adapter для подключения адаптера, пакета интерфейса HTTPlug и самого Guzzle 6 в качестве зависимости адаптера.

HTTPlug — это точка входа для повторно используемого пакета. Это клиентская абстракция, на которой основаны все клиенты (например, адаптер Guzzle6). Эти клиенты затем используют свои базовые пакеты / зависимости — в этом случае Guzzle 6.

Итак, снизу вверх:

  • HTTP-клиент существует (Guzzle 6)
  • адаптер Guzzle 6 построен с HTTPlug в качестве интерфейса для него, оборачивает Guzzle 6
  • приложение, которое должно иметь возможность совершать HTTP-вызовы, нуждается в клиенте, для которого требуется интерфейс HttpClient HTTPlug, а не Guzzle 6 напрямую
  • Затем приложение может использовать Guzzle 6 или любой другой адаптер, реализующий интерфейс HttpClient HTTPlug и упаковывающий другой сторонний HTTP-клиент.

План команды состоит в том, чтобы в конечном итоге получить максимальную поддержку для всех HTTP-клиентов на PHP-земле: Guzzle 6, Guzzle 5, Zend2, Zend1 и т. Д. Таким образом, пользователь каркаса или приложения не будет конфликтовать с установленными версиями клиента, и просто подключит соответствующий адаптер в микс.

Обратите внимание, что мы используем термины « адаптер» и « клиент» здесь почти взаимозаменяемо — оба адаптера, основанные на HTTPlug. Они являются обертками вокруг существующих клиентов, но используются непосредственно как сами клиенты.

В этом посте мы планируем заменить конкретную зависимость PHP-клиента Diffbot от Guzzle 5 версией HTTPlug.

Примечание. HTTPlug и связанные с ним пакеты являются альфа-программным обеспечением и могут быть изменены. Конвертировать что-либо для их использования — рискованное занятие.

Бутстрапирование

Как обычно, мы рекомендуем использовать Homestead Improved для начальной загрузки нашей среды. Когда мы будем готовы, мы можем клонировать и протестировать текущую стабильную версию SDK:

 git clone https://github.com/swader/diffbot-php-client cd diffbot-php-client git checkout tags/0.4.5 composer install phpunit 

Последняя команда предполагает, что PHPUnit глобально установлен в среде разработки.

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

Начиная

Во-первых, нам нужно создать новую ветку для разработки этого обновления.

 git checkout -b feature-httplug 

Затем мы добавляем две зависимости в наш файл composer.json :

  "require": { ... "php-http/client-implementation": "^1.0" }, "require-dev": { ... "php-http/guzzle6-adapter": "~0.2@dev" }, 

Это говорит клиенту, что теперь он зависит от виртуального пакетаэтого . Это означает, что для использования приложение, использующее наш клиент Diffbot (как этот ), должно выбрать реализацию этого пакета (один из перечисленных по ссылке на Packagist). Конечно, во время разработки пакета было бы невозможно протестировать и посмотреть, все ли работает без фактической реализации, поэтому мы указываем дополнительную зависимость require-dev . В приведенном выше конкретном случае мы используем "php-http/guzzle6-adapter": "~0.2@dev" . Мы выбрали именно эту версию просто потому, что она самая новая и стабильной версии нет.

Примечание: вас может удивить, почему мы использовали подход добавления значений в composer.json а не интерактивное объявление зависимостей в терминале, как мы обычно это делаем. Это потому, что выполнение composer require для виртуального пакета вызовет ошибку — пакет на самом деле не существует, это всего лишь его виртуальное имя, заполнитель, поэтому Composer запутается, не зная, что устанавливать. Есть проблема, предлагающая изменить это, но вряд ли это произойдет в ближайшее время.

Поскольку пакеты php-http все еще находятся в стадии разработки, мы должны добавить следующие два значения в наш файл composer.json :

 "prefer-stable": true, "minimum-stability": "dev" 

Это позволяет устанавливать пакеты dev (нестабильные), но предпочитает стабильные версии, если они существуют. Таким образом, вместо того, чтобы, скажем, получить PHPUnit 5.2.x, который очень нестабилен, он получит 5.0.8 (наиболее актуальный на момент написания), но он также будет успешным, если мы запросим его для пакетов, которые не имеют стабильные релизы (как у guzzle6-adapter ).

Нам также нужно убрать зависимость от Guzzle5, если мы собираемся установить Guzzle6. Конечные блоки require выглядят так:

  "require": { "php" : ">=5.4.0", "php-http/client-implementation": "^1.0" }, "require-dev": { "symfony/var-dumper": "~2", "phpunit/phpunit": "^5.0", "php-http/guzzle6-adapter": "~0.2@dev" }, 

План

В настоящее время SDK работает следующим образом: в основном классе Diffbot мы необязательно устанавливаем HTTPClient. В настоящее время это связано с реализацией Guzzle в версии 5. Если пользовательский экземпляр клиента не установлен, класс Diffbot автоматически использует клиента по умолчанию.

Этот клиент затем используется методом вызова Api Abstract для выдачи запроса на получение данного URL. Кроме того, в классе API Crawl и классе API поиска есть специальный метод call .

Результат вызова сохраняется как $response , который является ответом Guzzle5. Затем этот ответ дополнительно обрабатывается фабрикой сущностей, которая проверяет ее действительность и строит из нее сущности , помещая их в итератор сущностей .

Таким образом, план состоит в том, чтобы:

  1. Замените Diffbot::setHttpClient на метод, принимающий реализацию HTTPlug
  2. Измените методы call API abstract, Crawl и Search, чтобы они могли выдавать запрос GET с любой предоставленной им реализацией HTTP-клиента.
  3. Измените Entity Factory и Entity Iterator, чтобы они больше не зависели от версии Response Guzzle5, а скорее от PSR-7.

Проект PHP-HTTP имеет дополнительный пакет Utils , который содержит HttpMethodsClient . Этот класс объединяет фабрику сообщений и HTTP-клиента в одно целое, упрощая отправку запросов с часто используемыми глаголами, такими как GET, POST и т. Д., Что превращается в нечто похожее на то, что мы имели до сих пор: $client->get( ... ) Более того, он также возвращает getBody ResponseInterface PSR-7, что означает, что метод getBody будет нам доступен — это оставит нереализованным только метод toJson , что мы легко можем сделать сами.

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

С планом битвы мы можем начать рефакторинг.

Предпосылки

Давайте потребуем дополнительные пакеты:

 composer require "php-http/utils" "php-http/discovery" 

Класс Diffbot

Класс Diffbot имеет эту строку вверху:

 use GuzzleHttp\Client; 

Мы можем просто изменить его на:

 use Http\Client\Utils\HttpMethodsClient as Client; 

Метод setHttpClient должен setHttpClient в среде IDE, заявив, что в нем отсутствуют некоторые обязательные параметры, а именно клиент для использования и фабрика сообщений, с помощью которой создаются экземпляры Request.

Метод должен быть преобразован в:

 /** * Sets the client to be used for querying the API endpoints * * @param Client $client * @see http://php-http.readthedocs.org/en/latest/utils/#httpmethodsclient * @return $this */ public function setHttpClient ( Client $client = null ) { if ( $client === null ) { $client = new Client ( \ Http \ Discovery \ HttpClientDiscovery : : find ( ) , \ Http \ Discovery \ MessageFactoryDiscovery : : find ( ) ) ; } $this - > client = $client ; return $this ; } 

Кроме того, классы Discovery можно импортировать с use операторов use в верхней части класса.

Это изменение позволило конечному пользователю Diffbot SDK либо:

  • установить собственный клиент и позволить компонентам Discovery в тандеме с HttpMethodsClient позаботиться о них автоматически, или
  • сконфигурируйте их собственный экземпляр HttpMethodsClient, внедрив пользовательский экземпляр клиента PSR 7 и фабрики сообщений в новый его экземпляр, и setHttpClient метод setHttpClient для полной гибкости

Большинство пользователей будут использовать это на автопилоте.

Далее, call методов.

Экземпляр ответа помечен как ошибочный

Поскольку экземпляр HttpMethodsClient мы реализовали ранее, имеет метод get , никаких изменений в этом отношении не требуется. Однако экземпляр $response показывает несоответствие и на то есть веские причины. Исходный $response ожидаемый EntityFactory — это Guzzle5 Response.

Из-за того, что EntityFactory , нам на самом деле не нужно редактировать абстрактный API — он позаботится обо всем самостоятельно. Crawl call класса Crawl немного отличается:

 public function call ( ) { $response = $this - > diffbot - > getHttpClient ( ) - > get ( $this - > buildUrl ( ) ) ; $array = $response - > json ( ) ; if ( isset ( $array [ 'jobs' ] ) ) { $jobs = [ ] ; foreach ( $array [ 'jobs' ] as $job ) { $jobs [ ] = new JobCrawl ( $job ) ; } return new EntityIterator ( $jobs , $response ) ; } elseif ( ! isset ( $array [ 'jobs' ] ) && isset ( $array [ 'response' ] ) ) { return $array [ 'response' ] ; } else { throw new DiffbotException ( 'It appears something went wrong.' ) ; } } 

Здесь два предупреждения — вторая строка метода, использующего метод json $response , и экземпляр EntityIterator который ожидает ответ Guzzle5. Единственная строка, на которую мы можем повлиять, это первая, поэтому давайте изменим ее на:

 $array = json_decode($response->getBody(), true); 

Аналогичное изменение необходимо сделать в методе call класса Search , где строка:

 $arr = $ei->getResponse()->json(['big_int_strings' => true]); 

меняется на:

 $arr = json_decode((string)$ei->getResponse()->getBody(), true, 512, 1); 

Entity Factory

Класс EntityFactory имеет следующий импорт в верхней части:

 use GuzzleHttp\Message\ResponseInterface as Response; 

Мы можем изменить это на:

 use Psr\Http\Message\ResponseInterface as Response; 

То же самое необходимо сделать в интерфейсе EntityFactory который EntityFactory класс EntityFactory .

Другое изменение аналогично тому, что мы сделали выше, в классе Crawl . Мы меняемся:

 $arr = $response->json(['big_int_strings' => true]); 

в

 $arr = json_decode($response->getBody(), true, 512, 1); 

как в checkResponseFormat и в методах createAppropriateIterator .

Entity Iterator

Мы меняемся:

 use GuzzleHttp\Message\ResponseInterface as Response; 

в

 use Psr\Http\Message\ResponseInterface as Response; 

тесты

Mocking, основной способ тестирования HTTP-запросов и вызовов API, отличается в Guzzle 6 , поэтому наши тесты нуждаются в немного большем пересмотре.

Поскольку этот урок уже немного длинен, просмотрите соответствующую ветку функций, если вы заинтересованы в изучении различий в насмешках между Guzzle 5 и Guzzle 6 и, в частности, между двумя версиями Diffbot SDK.

Наконец, давайте запустим тесты:

 phpunit PHPUnit 5.0.8 by Sebastian Bergmann and contributors. Runtime: PHP 5.6.10-1+deb.sury.org~trusty+1 with Xdebug 2.3.2 Configuration: /home/vagrant/Code/diffbot-php-client/phpunit.xml.dist ............................................................... 63 / 347 ( 18%) ............................................................... 126 / 347 ( 36%) ............S.................................................. 189 / 347 ( 54%) ............................................................... 252 / 347 ( 72%) ............................................................... 315 / 347 ( 90%) ................................ 347 / 347 (100%) Time: 55.78 seconds, Memory: 34.25Mb 

Успех! Все прохождения (кроме ожидаемого пропущенного теста).

Diffbot SDK теперь не только совместим с PSR-7, но и восприимчив к другим реализациям HTTP-клиентов. Все, что ему нужно, это адаптер, поддерживающий интерфейс HTTPlug, и все должно работать «из коробки».

Вывод

HTTPlug — это новый полезный подход для абстрагирования реализаций HTTP-клиентов в создаваемых нами приложениях. Будь мы сами создаем HTTP-клиенты или используем их в других приложениях, PHP-HTTP предоставляет совершенно новый мир расширяемости за разумную цену за один дополнительный уровень абстракции.

Если вы хотите помочь, добавив больше реализаций адаптера или просто опробовав пакеты и предоставив обратную связь, команда приветствует все предложения. Свяжитесь с нами или оставьте свой отзыв в разделе комментариев ниже, и если вы нашли этот урок интересным, не забудьте нажать кнопку «Нравится»!