【问题标题】:Tightly coupled dependencies through callback argument types通过回调参数类型紧密耦合的依赖关系
【发布时间】:2020-11-19 18:08:51
【问题描述】:

我的示例使用 PHP,但这个概念应该普遍适用于 OOP。

我正在使用依赖注入模式来解耦我的类并允许轻松模拟和测试。对于这个具体示例,我的示例类 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;
}

现在我的问题是那些回调和参数类型提示(在其他语言中,这将是类型声明的问题)。

接口的一个可能实现是我的类Guzzle,它包装了GuzzleHttp 库,这是一个流行的PHP HTTP 客户端库。这个类看起来像这样:

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

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

    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 库使用的类型。例如,假设我在 ApiConsumer 类中发出 API 请求,如下所示:

    public function consumeSomeEndpoint(): void {
         $this->client->async(
             'GET', 
             'https://some.api.com/endpoint/', 
             [],
             function (Psr\Http\Message\ResponseInterface $response) {
                 var_dump($response);
             },
             function (GuzzleHttp\Exception\BadResponseException $reason) {
                 echo $reason->getResponse()->getBody();
                 throw new RuntimeException($reason->getMessage());
             },
         );
    }

ResponseInterface 很好,足够通用,可以在很多地方使用(整个Psr 包本质上只是提供了与 HTTP 相关的接口)。但是,BadResponseException 特定于 GuzzleHttp。如果我想创建HttpClientInterface 的替代实现,我必须实际导入 GuzzleHttp 库,这样我才能理解这些异常。我不能在这里引入接口,因为显然 lib 的 Exceptions 没有实现它。 BadResponseExceptionPsr\Http\Message\RequestInterface\RequestExceptionInterface 实现的接口不提供用于访问响应负载的getResponse() 方法,该负载包含来自 API 的有关失败原因的潜在信息。

有没有办法解决这个问题?

【问题讨论】:

  • 我不能在这里引入接口,因为显然 lib 的异常没有实现它 - 实际上在这种情况下,它们会实现。正是出于这个原因,PSR 提供​​了RequestExceptionInterface

标签: php oop dependency-injection guzzle


【解决方案1】:

是的,解决这个问题的方法是依赖于一个不紧密绑定到库的异常,并且其他替换可以抛出或实现。

幸运的是,Guzzle 就是这种情况。

GuzzleHttp\Exception\BadResponseException 扩展GuzzleHttp\Exception\RequestException,进而实现Psr\Http\Message\RequestInterface\RequestExceptionInterface

如所见,herehere

/**
 * 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 **}

要正确地键入提示回调,您可能必须指定Psr\Http\Client\ClientExceptionInterface,因为这是 PSR 合同中最通用的一个,并且符合要求的实现可能会抛出任何扩展异常:NetworkExceptionInterfaceRequestExceptionInterface,前面提到过。

【讨论】:

  • 不幸的是,该界面不提供getResponse(),我需要它才能访问响应负载中的完整错误消息。出于某种原因,Guzzle 会将错误消息截断到无用的地步。我想我必须将我的回调包装在 Guzzle 包装器中并重写异常:/
猜你喜欢
  • 2011-05-24
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 2014-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多