Эта статья была рецензирована Марком Саги-Казаром и Дэвидом Бухманном . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
В предыдущей серии мы создали PHP-клиент для Diffbot . Клиент работает хорошо и широко используется — мы даже протестировали его на живом приложении, чтобы убедиться, что он на высоте — но это сильно зависит от Guzzle 5.
Есть две проблемы с этим:
- Жрет 6 и поддерживает PSR 7 . Хотя автор Guzzle утверждает, что Guzzle 5 будет поддерживаться в обозримом будущем, безопаснее скептически относиться к его долговечности. Кроме того, хотя PSR 7 может иметь свои причуды , хорошо следовать PSR хотя бы для совместимости с другими проектами.
- Кто-то, внедряющий наш клиент в свое приложение, может уже использовать предпочтительный 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. Затем этот ответ дополнительно обрабатывается фабрикой сущностей, которая проверяет ее действительность и строит из нее сущности , помещая их в итератор сущностей .
Таким образом, план состоит в том, чтобы:
- Замените
Diffbot::setHttpClient
на метод, принимающий реализацию HTTPlug - Измените методы
call
API abstract, Crawl и Search, чтобы они могли выдавать запрос GET с любой предоставленной им реализацией HTTP-клиента. - Измените 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
для полной гибкости
Большинство пользователей будут использовать это на автопилоте.
API Аннотация, Сканирование и Поиск
Далее, 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 предоставляет совершенно новый мир расширяемости за разумную цену за один дополнительный уровень абстракции.
Если вы хотите помочь, добавив больше реализаций адаптера или просто опробовав пакеты и предоставив обратную связь, команда приветствует все предложения. Свяжитесь с нами или оставьте свой отзыв в разделе комментариев ниже, и если вы нашли этот урок интересным, не забудьте нажать кнопку «Нравится»!