Перейти к содержанию

GuzzleHttp client

Отладка запроса

Guzzle умеет выводить запрос в STDOUT, для этого надо указать параметр ['debug' => true] при запросе. Подробнее в документации.

Request

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

$request = new Request('GET', 'http://my-site.loc');
$client = new Client();
$response = $client->send($request);

Отправить json можно так:

$client->post('api', ['json' => ['key' => 'value']])
Библиотека сама пропишет заголовок Content-Type: application/json и вызовет \GuzzleHttp\Utils::jsonEncode, которая является обёрткой над json_encode. Минус такого способа — нельзя передать в json_encode флаги.

Кроме json поддерживаются и другие типы. Смотреть тут: \GuzzleHttp\Client::applyOptions()

Middleware

Мидлвар — выполняемая функция, которая параметром принимает callable $handler — следующий обработчик. Мидлвар должен вернуть новую функцию, которая принимает параметрами Psr\Http\Message\RequestInterface и массив опций. Функция должна вернуть $handler($request, options).

Мидлвары добавляются к хендлерам. Если при создании клиента не указывать хендлеры, библиотека сама запускает определение нужного хендлера через \GuzzleHttp\Utils::chooseHandler(). Если нужен дефолтный хендлер — можно воспользоваться просто конструктором. Если надо указать конкретный хендлер — можно заиспользовать статический метод \GuzzleHttp\HandlerStack::create(). Так например можно создавать mockHandler для тестирования.

$myMiddleware = static function (callable $handler) {
    return static function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
        return $handler($request, $options);
    }
};
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push($myMiddleware);
$client = new \GuzzleHttp\Client(['handler' => $stack]);
$client->post('/api', ['json' => ['key' => 'value']]);

Установка заголовка Host

Необходимо установить Host отличный от того, что передан в base_uri. Заиспользуем Middleware, и с помощью $request->withHeader создадим новый $request с установленным заголовком.

$myMiddleware = static function (callable $handler) {
    return static function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
        $request = $request->withHeader('Host', 'my-site.loc');
        return $handler($request, $options);
    };
};
$stack = HandlerStack::create();
$stack->push($myMiddleware);
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://localhost:443/',
    RequestOptions::DEBUG => true,
    RequestOptions::VERIFY  => false,
    'handler' => $stack,
]);

Логгирование Request с помощью middleware

$logMiddleware = static function (callable $handler) {
    return static function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
        file_put_contents('/tmp/request.log', $request->getBody()->getContents(), FILE_APPEND);
        return $handler($request, $options);
    }
};
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push($logMiddleware);
$client = new \GuzzleHttp\Client(['handler' => $stack]);
$client->post('/api', ['json' => ['key' => 'value']]);

Логгирование с использованием библиотеки Monolog

$logger = new \Monolog\Logger('guzzleLogger', [
    // для примера логгер будет писать данные прямо в STDOUT
    new \Monolog\Handler\StreamHandler(STDOUT)
]);
$debugLog = static function(callable $handler) use ($logger) {
    return static function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler, $logger) {
        $logger->info($request->getBody()->getContents());
        return $handler($request, $options);
    };
};
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push($logMiddleware);
$client = new \GuzzleHttp\Client(['handler' => $stack]);
$client->post('/api', ['json' => ['key' => 'value']]);

Guzzle предоставляет несколько готовых Мидлваров (\GuzzleHttp\Middleware), в том числе и для логгинга — \GuzzleHttp\Middleware::log(). Удобно что первым параметром принимает Psr\Log\LoggerInterface, значит подружится с Monolog. А вот вторым параметром принимает свой интерфейс \GuzzleHttp\MessageFormatterInterface.

$logger = new \Monolog\Logger('guzzleLogger', [
    // для примера логгер будет писать данные прямо в STDOUT
    new \Monolog\Handler\StreamHandler(STDOUT)
]);
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(\GuzzleHttp\Middleware::log(
    $logger,
    new \GuzzleHttp\MessageFormatter('Тут можно передать шаблон, пример в исходниках')
));
$client = new \GuzzleHttp\Client(['handler' => $stack]);
$client->post('/api', ['json' => ['key' => 'value']]);

Тестирование

О тестировании написано в документации

Пример: Сервис MyService формирует и отправляет данные в формате json. Необходимо протестировать формат пакета в phpUnit без отправки запроса.

use GuzzleHttp\Client;

class MyService
{
    private Client $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    public function run()
    {
        $payload = []; // данные для отправки
        $response = $this->client->post('endpoint', $payload);
    }
}

Для этого заиспользуем Mock Handler + History Middleware. Использование Mock Handler предотвратит отправку данных, а с помощью History Middleware достанем body. В нашем случае body в формате json. Применив json_decode, получим набор отправляемых данных.

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

class MyServiceTest extends TestCase
{
    private MyService $service;
    public array $historyContainer;

    public function setUp(): void
    {
        $this->historyContainer = [];
        $mock = new MockHandler([
            // в данном случае структура ответа не важна
            new Response(200, [], ''),
        ]);
        $handlerStack = HandlerStack::create($mock);
        $history = Middleware::history($this->historyContainer);
        $handlerStack->push($history);
        $client = new Client(['handler' => $handlerStack]);
        $this->service = new MyService($client);
    }

    public function testRun()
    {
        $this->service->run();
        /** @var Request $request */
        $request = current($this->historyContainer)['request'];
        $body = $request->getBody();
        $body->rewind();
        $data = json_decode($body->getContents(), true);
        $this->assertArrayHasKey('foo', $data, 'Отсутствует необходимый параметр');
    }
}

Авторизация в Битрикс

Для того чтоб куки сохранялись между запросами, надо установить cookies в true в конструкторе (документация):

$client = new \GuzzleHttp\Client(['cookies' => true]);

Теперь можно авторизоваться в Битрикс и отправлять запросы как авторизованный пользователь:

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://my-site.loc',
    'cookies'  => true,
]);
// авторизация в Битрикс
$client->post('/', [
    'form_params' => [
        'AUTH_FORM'     => 'Y',
        'TYPE'          => 'AUTH',
        'USER_LOGIN'    => 'login',
        'USER_PASSWORD' => 'password'
    ]],
);
$response = $client->get('/crm/lead/list/');
$contents = $response->getBody()->getContents();