#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 и переписать исключения :/