retailcrm / api-client-php

PHP client for RetailCRM API
http://www.retailcrm.ru
MIT License
60 stars 58 forks source link

Проблема с фильтрацией товаров в запросе $response = $client->store->products($request); #126

Closed vimbatu closed 2 years ago

vimbatu commented 2 years ago

use RetailCrm\Api\Enum\NumericBoolean, RetailCrm\Api\Factory\SimpleClientFactory, RetailCrm\Api\Interfaces\ApiExceptionInterface, RetailCrm\Api\Model\Filter\Store\ProductFilterType, RetailCrm\Api\Model\Request\Store\ProductsRequest;

$client = SimpleClientFactory::createClient('сайт', 'ключ');

$request = new ProductsRequest(); $request->filter = new ProductFilterType(); $request->filter->active = NumericBoolean::TRUE; $request->filter->externalId = '9460'; $request->page = 1; $request->limit = 100;

try { $response = $client->store->products($request); } catch (ApiExceptionInterface $exception) { echo sprintf( 'Error from RetailCRM API (status code: %d): %s', $exception->getStatusCode(), $exception->getMessage() ); if (count($exception->getErrorResponse()->errors) > 0) { echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); } return; }

echo 'Products: ' . "

".print_r($response, true)."
";

Не работает фильтрация элементов. При любых значениях фильтра все равно выводится полный список товаров. В чем может быть причина?

Neur0toxine commented 2 years ago

Добрый вечер. Проверил с помощью вашего сниппета без изменений - фильтры отработали корректно. Рекомендую проверить результат с такими же фильтрами напрямую используя Postman или любой другой подобный инструмент. URL запроса для вашего сниппета будет выглядеть примерно так:

https://{{ домен системы }}/api/v5/store/products?apiKey={{ API-ключ }}&filter%5Bactive%5D=1&filter%5BexternalId%5D=9460&page=1&limit=100

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

vimbatu commented 2 years ago

Сделал запрос curl'ом:

$url_crm = 'https://{{ домен системы }}/api/v5/store/products?apiKey={{ API-ключ }}&filter%5Bactive%5D=1&filter%5BexternalId%5D=9460&page=1&limit=100'; $curl_crm = curl_init(); curl_setopt($curl_crm, CURLOPT_URL, $url_crm);

$crm = curl_exec($curl_crm);

Без проблем ответ вернулся с результатами фильтрации.

Neur0toxine commented 2 years ago

Для дополнительного разбора потребуются данные запроса в том виде, в котором они передаются клиентом. Чтобы их получить инициализируйте API-клиент следующим образом:

use RetailCrm\Api\Builder\ClientBuilder;
use RetailCrm\Api\Builder\FormEncoderBuilder;
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
use Psr\Log\AbstractLogger;

$client = (new ClientBuilder())
    ->setApiUrl('здесь URL системы')
    ->setAuthenticatorHandler(new HeaderAuthenticatorHandler('здесь API-ключ'))
    ->setFormEncoder((new FormEncoderBuilder())->build())
    ->setDebugLogger(new class extends AbstractLogger {
        public function log($level, $message, array $context = [])
        {
            printf('[%s] %s %s' . PHP_EOL, $level, $message, json_encode($context));
        }
    })
    ->build();

Затем выполните запрос этим клиентом (выводить результат отдельно не нужно). В терминал будут выведены как данные запроса, так и данные ответа. Если фильтр не сработает и URL запроса корректный (содержит фильтр), то необходимо обратиться в техподдержку т.к. для повторения ситуации потребуются данные доступа к вашей системе.

vimbatu commented 2 years ago

Получил результаты. Есть некоторое отличие в запросах:

//запрос с нерабочим фильтром через клиент https://{{ домен системы }}/api/v5/store/products?filter%255Bactive%255D=1&filter%255BexternalId%255D=9460&limit=100

//запрос с рабочим фильтром через curl https://{{ домен системы }}/api/v5/store/products?filter%5Bactive%5D=1&filter%5BexternalId%5D=9460&page=1&limit=100

vimbatu commented 2 years ago

Я так понимаю, в урле запроса значения фильтра должны быть в [ ], а клиент по какой-то причине квадратные скобки меняет на юникод символ U+255B.

vimbatu commented 2 years ago

Может подскажете, в каком файле/классе формируется урл запроса, я сам гляну?

Neur0toxine commented 2 years ago

Формирование query string для запроса производится в компоненте FormEncoder. Сформированные данные в итоге используются в middleware RequestDataHandler. Однако, здесь клиент их уже никак не обрабатывает - они просто передаются в метод withQuery компонента, имплементирующего UriInterface.

Похоже, что используемая вами имплементация UriInterface работает некорректно. Согласно описанию в самом интерфейсе, UriInterface::withQuery должен определять наличие символов, подлежащих кодировке в query string, и если таковых нет - не выполнять кодирование. Однако, используемая вами имплементация безальтернативно кодирует символы.

Проверить эту гипотезу можно выполнив следующий код:

print_r((string) \Http\Discovery\Psr17FactoryDiscovery::findUriFactory()->createUri('https://example.com/?filter%5B0%5D'));

Если выходная строка не идентична входной, то ваш UriInterface работает некорректно. Если это так, то рекомендую использовать nyholm/psr7 вместо присутствующей у вас имплементации. Достаточно установить этот пакет - API-клиент должен начать находить его вместо существующей имплементации.

Проверить, что в клиенте начали использоваться фабрики из nyholm/psr7 можно выполнив следующий код:

print_r(get_class(\Http\Discovery\Psr17FactoryDiscovery::findUriFactory()));

Если выводится текст Nyholm\Psr7\Factory\Psr17Factory, то все должно заработать корректно. Если же до сих пор используется другая имплементация - можно заставить клиент использовать нужную инициализируя его через ClientBuilder (используя методы setStreamFactory, setRequestFactory и setUriFactory). В таком случае для удобства работы с клиентом в приложении рекомендую реализовать свою фабрику для создания клиента, которая будет использовать ClientBuilder (по аналогии с SimleClientFactory).

vimbatu commented 2 years ago

Проверил, UriInterface работает некорректно. Кстати, есть мысли, с чем это может быть связано? nyholm/psr7 установил, сам он не подцепился, выводится Phalcon\Http\Message\UriFactory Подключил на странице: use Nyholm\Psr7; use Nyholm\Psr7\Factory; Подскажете правильный синтаксис, как указать ему другую имплементацию?

$client = (new ClientBuilder()) ->setApiUrl(' урл ') ->setAuthenticatorHandler(new HeaderAuthenticatorHandler(' ключ ')) ->setFormEncoder((new FormEncoderBuilder())->build()) ->setStreamFactory( ??? ) //так не работает ->setRequestFactory( ??? ) //так не работает ->setUriFactory( ??? ) //так не работает ->setDebugLogger(new class extends AbstractLogger { public function log($level, $message, array $context = []) { printf('[%s] %s %s' . PHP_EOL, $level, $message, json_encode($context)); } }) ->build();

Спасибо.

Neur0toxine commented 2 years ago

Проверил, UriInterface работает некорректно. Кстати, есть мысли, с чем это может быть связано?

Судя по всему, вы используете Phalcon. Вероятно, стоит уточнить причины такой реализации у авторов фреймворка.

Вот рабочий пример инициализации с другими фабриками.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use RetailCrm\Api\Builder\ClientBuilder;
use RetailCrm\Api\Builder\FormEncoderBuilder;
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
use Tuupola\Http\Factory\RequestFactory;
use Tuupola\Http\Factory\StreamFactory;
use Tuupola\Http\Factory\UriFactory;
use Http\Client\Curl\Client;

$client = (new ClientBuilder())
    ->setApiUrl('https://test.retailcrm.pro')
    ->setAuthenticatorHandler(new HeaderAuthenticatorHandler('API-ключ'))
    ->setFormEncoder((new FormEncoderBuilder())->build())
    ->setStreamFactory(new StreamFactory())
    ->setRequestFactory(new RequestFactory())
    ->setUriFactory(new UriFactory())
    ->setHttpClient(new Client())
    ->build();
vimbatu commented 2 years ago

Спасибо большое, решение подошло.