biplane / yandex-direct

PHP library for Yandex.Direct API.
MIT License
44 stars 23 forks source link

Проблема с загрузкой отчетов в версии 5 #40

Closed AntsHere closed 2 years ago

AntsHere commented 2 years ago

Перешел с версии 4 на 5 (правда пока остался на Biplane\YandexDirect\User). Все работает, кроме загрузки отчетов. Все изменения по коду я внес (вместо Biplane\YandexDirect\Api\V5\Report теперь Biplane\YandexDirect\Api\V5\Reports). Но при попытке загрузить отчет, выдается следующая ошибка:

Fatal error:  Uncaught Http\Discovery\Exception\DiscoveryFailedException: Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors
 - No valid candidate found using strategy "Http\Discovery\Strategy\CommonClassesStrategy". We tested the following candidates: Http\Discovery\Strategy\CommonClassesStrategy::symfonyPsr18Instantiate, GuzzleHttp\Client, Http\Discovery\Strategy\CommonClassesStrategy::buzzInstantiate.
 - No valid candidate found using strategy "Http\Discovery\Strategy\CommonPsr17ClassesStrategy". We tested the following candidates: .

 in vendor\php-http\discovery\src\Exception\DiscoveryFailedException.php:41
Stack trace:
#0 vendor\php-http\discovery\src\ClassDiscovery.php(85): Http\Discovery\Exception\DiscoveryFailedException::create(Array)
#1 vendor\php-http\discovery\src\Psr18ClientDiscovery.php(25): Http\Discovery\ClassDis in vendor\php-http\discovery\src\Psr18ClientDiscovery.php on line 27

Сходил по ссылке http://docs.php-http.org/en/latest/discovery.html#common-errors , там предлагается выполнить "composer require php-http/curl-client guzzlehttp/psr7 php-http/message". Выполнил. Текст ошибки немного изменился, но суть вроде бы осталась старой:

Fatal error:  Uncaught Http\Discovery\Exception\DiscoveryFailedException: Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors
 - No valid candidate found using strategy "Http\Discovery\Strategy\CommonClassesStrategy". We tested the following candidates: .
 - No valid candidate found using strategy "Http\Discovery\Strategy\CommonPsr17ClassesStrategy". We tested the following candidates: Phalcon\Http\Message\ResponseFactory, Nyholm\Psr7\Factory\Psr17Factory, Zend\Diactoros\ResponseFactory, GuzzleHttp\Psr7\HttpFactory, Http\Factory\Diactoros\ResponseFactory, Http\Factory\Guzzle\ResponseFactory, Http\Factory\Slim\ResponseFactory, Laminas\Diactoros\ResponseFactory, Slim\Psr7\Factory\ResponseFactory.

 in vendor\php-http\discovery\src\Exception\DiscoveryFailedException.php:41
Stack trace:
#0 vendor\php-http\discovery\src\ClassDiscovery.php(85): Http\Discovery\Exception\D in vendor\php-http\discovery\src\ClassDiscovery.php on line 226

Как заставить работать отчеты?

yethee commented 2 years ago

Сейчас попробовал тестовый проект создать. Ошибку воспроизвести не удалось, после установки пакетов из примера документации к php-http/discovery.

    "require": {
        "biplane/yandex-direct": "^5.0",
        "php-http/curl-client": "^2.2",
        "guzzlehttp/psr7": "^2.1",
        "php-http/message": "^1.12"
    }
<?php

use Biplane\YandexDirect\ReportServiceFactory;

require 'vendor/autoload.php';

$factory = new ReportServiceFactory();

echo 'OK' . PHP_EOL;

Можно попробовать явно указать зависимости для ReportServiceFactory. Если ошибки не будет, то можно проверить актуальность vendor/autoload.php.

<?php

use Biplane\YandexDirect\ReportServiceFactory;
use GuzzleHttp\Psr7\HttpFactory;
use Http\Client\Curl\Client;

require 'vendor/autoload.php';

$httpFactory = new HttpFactory();
$httpClient = new Client($httpFactory, $httpFactory);

$factory = new ReportServiceFactory($httpClient, $httpFactory, $httpFactory);

echo 'OK' . PHP_EOL;
AntsHere commented 2 years ago

Ваш первый пример у меня выдает такую же ошибку, как и раньше.

Второй пимер выдает следующее: Fatal error: Uncaught Error: Class 'GuzzleHttp\Psr7\HttpFactory' not found in \www\test.php:9 Stack trace: #0 {main} thrown in www\test.php on line 9

yethee commented 2 years ago

А какая версия PHP? И какая версия guzzlehttp/psr7 установлена?

$ composer show guzzlehttp/psr7

Можно попробовать nyholm/psr7 (именно эту реализацию использую в своих проектах), как альтернативу guzzlehttp/psr7 (если нет возможности установить ^2.0).

AntsHere commented 2 years ago

Версия PHP - php-7.1.9-Win32-VC14-x86

Результат выполнения команды:

name     : guzzlehttp/psr7
descrip. : PSR-7 message implementation that also provides common utility methods
keywords : http, message, psr-7, request, response, stream, uri, url
versions : * 1.8.3
type     : library
license  : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText
homepage :
source   : [git] https://github.com/guzzle/psr7.git 1afdd860a2566ed3c2b0b4a3de6e23434a79ec85
dist     : [zip] https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85 1afdd860a2566ed3c2b0b4a3de6e23434a79ec85
path     : D:\Work\_test\vendor\guzzlehttp\psr7
names    : guzzlehttp/psr7, psr/http-message-implementation

support
issues : https://github.com/guzzle/psr7/issues
source : https://github.com/guzzle/psr7/tree/1.8.3

autoload
psr-4
GuzzleHttp\Psr7\ => src/
files

requires
php >=5.4.0
psr/http-message ~1.0
ralouphie/getallheaders ^2.0.5 || ^3.0.0

requires (dev)
ext-zlib *
phpunit/phpunit ~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10

suggests
laminas/laminas-httphandlerrunner Emit PSR-7 responses

provides
psr/http-message-implementation 1.0

Установка nyholm/psr7 помогла. Еще, в процессе поиска решения в интернете, наткнулся на http-interop/http-factory-guzzle - тоже помогает.

Но возникает следующая проблема:

Fatal error:  Uncaught Http\Client\Exception\RequestException: SSL certificate problem: unable to get local issuer certificate in www\vendor\php-http\curl-client\src\Client.php:166
Stack trace:
#0 www\vendor\biplane\yandex-direct\src\Api\V5\Reports.php(217): Http\Client\Curl\Client-&gt;sendRequest(Object(Nyholm\Psr7\Request))
#1 www\vendor\biplane\yandex-direct\src\Api\V5\Reports.php(74): Biplane\YandexDirect\Api\V5\Reports-&gt;doRequest(Object(Nyholm\Psr7\Request))
#2 www\main.php(941): Biplane\YandexDirect\Api\V5\Reports-&gt;get(Object(Biplane\YandexDirect\Api\V5\Reports\ReportRequest))
#3 {main}
  thrown in www\vendor\php-http\curl-client\src\Client.php on line 166

Помогает в файле vendor\php-http\curl-client\src\Client.php добавить в опции "CURLOPT_SSL_VERIFYPEER => false". Однако это не не правильно с точки зрения безопасности. Можно как-то по-правильному решить эту проблему? В прошлой версии Biplane 4 с SSL проблем не было...

yethee commented 2 years ago

Можно как-то по-правильному решить эту проблему?

Можно отдельно скачать свежий бандл с сертификатами и обновить php.ini

curl.cainfo = <path/to/cacert.pem>

В прошлой версии Biplane 4 с SSL проблем не было...

Видимо, причина в том, что в 4 версии использовался guzzlehttp/guzzle. Где есть логика определения актуального бандла с сертификатами, Utils::defaultCaBundle().

Можно php-http/curl-client заменить на guzzlehttp/guzzle. Должно решить проблему с сертификатами.

AntsHere commented 2 years ago

Можно php-http/curl-client заменить на guzzlehttp/guzzle. Должно решить проблему с сертификатами.

Не помогает. Возвращается прежняя ошибка "Uncaught Http\Discovery\Exception\DiscoveryFailedException: Could not find resource using any discovery strategy.".

curl.cainfo = <path/to/cacert.pem>

Да, это помогает. Но, конечно, интереснее было бы использовать guzzlehttp/guzzle.

yethee commented 2 years ago

Не помогает. Возвращается прежняя ошибка

Нужен guzzlehttp/guzzle версии 7.x, но эта версия совместима с PHP 7.2 и выше. Для PHP 7.1 устанавливается 6.x (предположение), а для этой версии нужен адаптер - php-http/guzzle6-adapter.

AntsHere commented 2 years ago

Да, с адаптером начинает работать, но сваливается в похожую ошибку SSL:

Fatal error:  Uncaught GuzzleHttp\Exception\RequestException: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) in www\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php:201
Stack trace:
#0 www\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php(155): GuzzleHttp\Handler\CurlFactory::createRejection(Object(GuzzleHttp\Handler\EasyHandle), Array)
#1 www\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php(105): GuzzleHttp\Handler\CurlFactory::finishError(Object(GuzzleHttp\Handler\CurlMultiHandler), Object(GuzzleHttp\Handler\EasyHandle), Object(GuzzleHttp\Handler\CurlFactory))
#2 www\vendor\guzzlehttp\guzzle\src\Handler\CurlMultiHandler.php(201): GuzzleHttp\Handler\CurlFactory::finish(Object(GuzzleHttp\Handler\CurlMultiHandler), Object(GuzzleHttp\Handler\EasyHandle), Object(GuzzleHttp\Handler\CurlFactory))
#3 in www\vendor\php-http\guzzle6-adapter\src\Promise.php on line 129

Если в php.ini вернуть опцию с cacert.pem, то начинает работать. То есть получается guzzlehttp/guzzle все равно не работает без cacert.pem в php.ini.

AntsHere commented 2 years ago

Решение с cacert.pem в целом помогло. Но вот тестирую сегодня работу новой версии Biplane (5) и постоянно сталкиваюсь с проблемами. То долго грузятся ответы. То вообще не загружаются (отваливаются по ограничению выполнения скрипта PHP). Ошибки бывают разными, к примеру:

Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing Schema: can't import schema from 'https://soap.direct.yandex.ru/v5/adextensiontypes.xsd' in \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php:70 Stack trace: #0 \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php(70): SoapClient->SoapClient('https://api.dir...', Array) #1 \vendor\biplane\yandex-direct\src\Api\ApiSoapClientV5.php(35): Biplane\YandexDirect\Soap\ApiSoapClient->__construct('https://api.dir...', Object(Biplane\YandexDirect\Config), Array) #2 \vendor\biplane\yandex-direct\src\Api\V5\Ads.php(200): Biplane\YandexDirect\Api\ApiSoapClientV5->__construct('https://api.dir...', Object(Biplane\YandexDirect\Config), Array) #3 [internal function]: Biplane\YandexDirect\Api\V5\Ads->__construct(Object(Biplane\YandexDirect\Config), Array) #4 \vendor\biplane\yandex-direct\src\Ap in \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php on line 70
Warning: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://api.direct.yandex.com/v5/keywordbids?wsdl' : failed to load external entity "https://api.direct.yandex.com/v5/keywordbids?wsdl" in \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php:70 Stack trace: #0 \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php(70): SoapClient->SoapClient('https://api.dir...', Array) #1 \vendor\biplane\yandex-direct\src\Api\ApiSoapClientV5.php(35): Biplane\YandexDirect\Soap\ApiSoapClient->__construct('https://api.dir...', Object(Biplane\YandexDirect\Config), Array) #2 \vendor\biplane\yandex-direct\src\Api\V5\KeywordBids.php(96): Biplane\YandexDirect\Api\ApiSoapClientV5->__construct('https://api.dir...', Object(Biplane\YandexDirect\Config), Array) #3 [internal function]: Biplane\YandexDirect\Api\V5\KeywordBids->__construct(Object(Biplane\YandexDirect\ in \vendor\biplane\yandex-direct\src\Soap\ApiSoapClient.php on line 70
Fatal error: Maximum execution time of 60 seconds exceeded in \vendor\biplane\yandex-direct\src\Api\ApiSoapClientV5.php on line 35

Вернулся на 4-ю версию. Все летает. Эксперимента ради добавил в файл vendor\php-http\curl-client\src\Client.php опцию "CURLOPT_SSL_VERIFYPEER => false" (уже писал про это выше) - сразу все стало работать быстро, как на 4-й версии.

Получается, решение с cacert.pem в php.ini в целом помогает, но все начинает как-то долго и с перебоями работать. Да, вспомнил, иногда вообще процесс Apache отваливается (тестирую в Windows 10).

yethee commented 2 years ago

То долго грузятся ответы. То вообще не загружаются (отваливаются по ограничению выполнения скрипта PHP).

В предыдущей версии по умолчанию было разрешено кеширование WSDL на диске.

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

Включить кеширование можно следующим образом:


use Biplane\YandexDirect\ConfigBuilder;
use Biplane\YandexDirect\Config\SoapOptions;

$soapOptions = SoapOptions::default()
    ->withWsdlCacheType(WSDL_CACHE_DISK);

$config = ConfigBuilder::create()
    ->setAccessToken('secret')
    ->setSoapOptions($soapOptions)
    ->getConfig();

Получается, решение с cacert.pem в php.ini в целом помогает, но все начинает как-то долго и с перебоями работать.

Если правильно понимаю, тут две независимые проблемы. Проблема с SSL касается сервиса Reports, а проблема с таймаутами относится к SOAP сервисам.

AntsHere commented 2 years ago

Включить кеширование можно следующим образом:

Большое спасибо, помогло! Я, правда, пользуюсь new User($options), но там в $options достаточно добавить 'soap_options' => SoapOptions::default()->withWsdlCacheType(WSDL_CACHE_DISK).

Если правильно понимаю, тут две независимые проблемы. Проблема с SSL касается сервиса Reports, а проблема с таймаутами относится к SOAP сервисам.

Да, согласен. Возможно, глобальное отключение проверки SSL как-то влияло на скорость соединения. Ну или так совпало...

Вообще, у меня есть предложение как-то все это задокументировать. Biplane 4 был удобен тем, что после выполнения composer require biplane/yandex-direct у пользователя был готовый у рабое иснтрумент. Да, надо было разобраться во всех методах подключения, разобраться с API Яндекса, но сама баблиотека была готова по одной строке в консоли.

Сейчас же, даже имея бекграунд с 4-й версией, 5-я стала некотором испытанием. Её после установки надо вручную дорабатывать напильником. Это не очень удобно. Либо надо как-то на этапе установки предлагать доустановить нужные пакеты (или ставить их сразу), проверять подключение SSL, предалгать включить кеш. Либо все это прописать в иснтрукции по установке.

P.S. Спасибо вам большое за вашу работу! Это действительно очень мощный и удобный инструмент для работы с Директом.

yethee commented 2 years ago

Спасибо за обратную связь!

Что касается конфигурации по умолчанию, этот момент можно пересмотреть. Согласен, включение кеширования WSDL по умолчанию будет оправданным компромиссом.

По поводу http-клиента для сервиса отчетов. Мотивация в абстракции от конкретной реализации была в том, чтобы пользователю дать выбор - была возможность не устанавливать лишние зависимости, дублирующие функционал. Например, на своих проектах все чаще использую symfony/http-client. И могу переиспользовать эту реализацию, вместо установки сбоку ещё и guzzlehttp/guzzle.

Проблема с сертификатами, это наверное вопрос больше к окружению/инфраструктуре (для себя этот момент решаю с помощью конфигурации PHP). Решение на уровне библиотеки/http-клиента не будет являться системным. Завтра на проекте может появиться еще одна зависимость, где будет использоваться curl и вопрос снова станет актуальным.

yethee commented 2 years ago

Начиная с версии 5.2.0 кеширование WSDL будет включено по умолчанию. Это должно решить проблему с долгим временем ожидания ответа при запросах к API.

Так же добавил в README абзац о том, что http-клиент нужно устанавливать самостоятельно.

Проблема с SSL должна решаться за рамками библиотеки, как мне кажется. Поэтому не знаю даже, как и что тут документировать.