Тесно связанные зависимости через типы аргументов обратного вызова

#php #oop #dependency-injection #guzzle

#php #ооп #внедрение зависимостей #жрать

Вопрос:

В моем примере используется PHP, но концепция должна применяться к ООП в целом.

Я использую шаблон внедрения зависимостей, чтобы отделить мои классы и позволить легко издеваться и тестировать. Для этого конкретного примера мой класс example ApiConsumer выполняет HTTP-запросы к API с использованием HTTP-клиента, который вводится в конструктор через интерфейс:

 class ApiConsumer {
    private $client;

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

interface HttpClientInterface {
    public function async(string $method, string $uri, array $options, callable $success, callable $failure): void;
}
 

Теперь моя проблема связана с этими обратными вызовами и указанием типа параметра (на других языках это было бы проблемой объявления типа).

Одной из возможных реализаций интерфейса является my class Guzzle , который оборачивает библиотеку GuzzleHttp, популярную клиентскую библиотеку HTTP для PHP. Этот класс выглядит следующим образом:

 class Guzzle implements HttpClientInterface {
    private $client;
    private $promises;

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

    public function async(string $method, string $uri, array $options, callable $success, callable $failure): void {
        $this->promises[] = function () use ($method, $uri, $options, $success, $failure) {
            return $this->client->requestAsync($method, $uri, $options)->then($success, $failure);
        };
    }
}
 

Таким образом, он по существу помещает запрос в $promises массив до тех пор, пока в какой-то момент не будет вызван другой метод, который фактически запускает эти запросы, выполняя либо успешный, либо неудачный обратный вызов в зависимости от результата запроса.

Это приводит к проблеме, когда я пишу эти функции обратного вызова. Чтобы правильно ввести подсказку о том, что на самом деле будет передано этим обратным вызовам, мне нужно напрямую ссылаться на типы, используемые библиотекой GuzzleHttp. Например, скажем, я делаю запрос API внутри своего ApiConsumer класса следующим образом:

     public function consumeSomeEndpoint(): void {
         $this->client->async(
             'GET', 
             'https://some.api.com/endpoint/', 
             [],
             function (PsrHttpMessageResponseInterface $response) {
                 var_dump($response);
             },
             function (GuzzleHttpExceptionBadResponseException $reason) {
                 echo $reason->getResponse()->getBody();
                 throw new RuntimeException($reason->getMessage());
             },
         );
    }
 

Это ResponseInterface нормально, это достаточно общее, что его можно использовать во многих местах (весь Psr пакет, по сути, просто предоставляет интерфейсы, связанные с HTTP). Однако BadResponseException это специфично для GuzzleHttp. Если я хочу создать альтернативную реализацию an HttpClientInterface , мне нужно фактически импортировать библиотеку GuzzleHttp, чтобы я мог понять эти исключения. Я не могу представить здесь интерфейс, потому что, очевидно, исключения библиотеки не реализуют его. Интерфейс, реализованный с помощью BadResponseException , PsrHttpMessageRequestInterfaceRequestExceptionInterface , не предоставляет getResponse() метод, используемый для доступа к полезной нагрузке ответа, которая содержит потенциальную информацию из API о причине сбоя.

Есть ли какой-нибудь способ обойти это?

Комментарии:

1. Я не могу представить здесь интерфейс, потому что, очевидно, исключения библиотеки не реализуют его — на самом деле в этом случае они реализуют. PSR обеспечивает RequestExceptionInterface именно по этой причине.

Ответ №1:

Да, способ обойти это — зависеть от исключения, которое не тесно связано с библиотекой, и которое другие замены могут либо создавать, либо реализовывать.

К счастью, это относится к Guzzle.

GuzzleHttpExceptionBadResponseException расширяет GuzzleHttpExceptionRequestException , который, в свою очередь, реализует PsrHttpMessageRequestInterfaceRequestExceptionInterface ;

Как видно, здесь и здесь:

 /**
 * Exception when an HTTP error occurs (4xx or 5xx error)
 */
class BadResponseException extends RequestException
{ /** class omitted **}
 
 /**
 * HTTP Request exception
 */
class RequestException extends TransferException implements RequestExceptionInterface
{ /** class omitted **}
 

Чтобы правильно указать ecallback, вам, вероятно, придется указать PsrHttpClientClientExceptionInterface , поскольку это наиболее общий вариант в контракте PSR, и соответствующие реализации могут вызывать любое из расширенных исключений: NetworkExceptionInterface или RequestExceptionInterface , упомянутых ранее.

Комментарии:

1. К сожалению, интерфейс не предоставляет getResponse() , что мне нужно для доступа к полному сообщению об ошибке в полезной нагрузке ответа. По какой-то причине Guzzle обрезает сообщения об ошибках до бесполезности. Я думаю, мне придется обернуть мой обратный вызов внутри оболочки Guzzle и переписать исключения :/