phpro / http-tools
用于开发更一致的HTTP实现的HTTP工具。
Requires
- php: ~8.2.0 || ~8.3.0
- ext-json: *
- azjezz/psl: ^3.0
- cardinalby/content-disposition: ^1.1
- league/uri: ^7.3
- php-http/client-common: ^2.7
- php-http/discovery: ^1.19
- php-http/httplug: ^2.4
- php-http/logger-plugin: ^1.3
- php-http/message: ^1.16 || ^2.0
- psr/http-client-implementation: ^1.0
- psr/http-factory: ^1.0
- psr/http-factory-implementation: ^1.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-message-implementation: ^1.0
- psr/log: ^3
- webmozart/assert: ^1.11
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- nyholm/psr7: ^1.8
- php-http/message-factory: 1.1.0
- php-http/mock-client: ^1.6
- php-http/vcr-plugin: ^1.2
- phpro/grumphp-shim: ^2.1
- symfony/http-client: ^5.4.26 || ^6.0 || ^7.0
- symfony/mime: ^6.0 || ^7.0
- symfony/options-resolver: ^5.4 || ^6.0 || ^7.0
- symfony/property-access: ^5.4 || ^6.0 || ^7.0
- symfony/serializer: ^5.4 || ^6.0 || ^7.0
Suggests
- guzzlehttp/guzzle: If you want to use the built-in guzzlehttp/guzzle tools.
- php-http/mock-client: For testing HTTP clients through mocking Requests and responses.
- php-http/vcr-plugin: For testing HTTP clients through storing and replaying requests and responses.
- symfony/http-client: If you want to use the built-in symfony/http-client tools.
- symfony/mime: If you want to use symfony/mime to upload or download binary files.
- symfony/serializer: If you want to use symfony serializer to handle request serialization and response deserialization.
README
HTTP-Tools
该包的目的是为您提供一些工具来设置以数据为中心的、一致的HTTP集成。您想要使用的HTTP客户端实现只是一个小的实现细节,并不重要。然而,这里有一些默认指南
先决条件
选择您想要使用的HTTP包完全取决于您。我们要求使用PSR实现来安装此包
- PSR-7:
psr/http-message-implementation
如nyholm/psr7
或guzzlehttp/psr7
- PSR-17:
psr/http-factory-implementation
如nyholm/psr7
或guzzlehttp/psr7
- PSR-18:
psr/http-client-implementation
如symfony/http-client
或guzzlehttp/guzzle
安装
composer require phpro/http-tools
设置HTTP客户端
您可以选择任何您想要的HTTP客户端。然而,此包提供了一些方便的工厂,使配置更加简单。
工厂接受一个特定实现插件/中间件的列表。除此之外,您还可以配置特定实现的选项,如基础URI或默认头。
<?php use Phpro\HttpTools\Client\Configurator\PluginsConfigurator; use Phpro\HttpTools\Client\Factory\AutoDiscoveredClientFactory; use Phpro\HttpTools\Client\Factory\GuzzleClientFactory; use Phpro\HttpTools\Client\Factory\SymfonyClientFactory; $options = ['base_uri' => $_ENV['SOME_CLIENT_BASE_URI']]; $httpClient = AutoDiscoveredClientFactory::create($middlewares); $httpClient = GuzzleClientFactory::create($guzzlePlugins, $options); $httpClient = SymfonyClientFactory::create($middlewares, $options); // If you are using guzzle, you can both use guzzle and httplug plugins. // You can wrap additional httplug plugins like this: $httpClient = PluginsConfigurator::configure($httpClient, $middlewares);
如果您想有更多的控制或想使用其他工具,您始终可以创建自己的工厂!
注意:此包不下载特定的HTTP实现。您可以选择任何您想要的包,但您必须手动将其添加到composer。
通过插件配置客户端
如果您想扩展HTTP客户端的工作方式,我们希望您使用插件!您可以使用插件做任何事情:日志记录、身份验证、语言指定等...
示例
<?php $middlewares = [ new Phpro\HttpTools\Plugin\AcceptLanguagePlugin('nl-BE'), new App\SomeClient\Plugin\Authentication\ServicePrincipal($_ENV['API_SECRET']), ];
内置插件:
AcceptLanguagePlugin
:使得可以向请求添加Accept-Language。CallbackPlugin
:使得可以将简单的callable
提升为真正的Plugin
。
记住:已经有很多HTTPlug中间件可用。在编写自己的之前,尝试使用其中之一!
日志记录
此包包含php-http/logger-plugin
。此外,我们还添加了一些装饰器,可以帮助您从日志中删除敏感信息。您可以通过指定一个debug参数从完整日志切换到简单日志!
<?php use Phpro\HttpTools\Formatter\RemoveSensitiveHeadersFormatter; use Phpro\HttpTools\Formatter\RemoveSensitiveJsonKeysFormatter; $middlewares[] = new Http\Client\Common\Plugin\LoggerPlugin( $logger, new RemoveSensitiveHeadersFormatter( new RemoveSensitiveJsonKeysFormatter( BasicFormatterFactory::create($debug = true, $maxBodyLength = 1000), ['password', 'oldPassword', 'refreshToken'] ), ['X-Api-Key', 'X-Api-Secret'] ) );
使用HTTP客户端
我们不希望您直接使用PSR-18客户端!相反,我们建议您使用请求处理器原则。那么这种架构是什么样的呢?
- 模型:可以用来包装外出或传入数据(数组、对象、字符串等)的请求/响应值对象。
- 请求处理器:通过使用传输将请求数据模型转换为响应数据模型。您也可以在那里添加错误处理。
- 传输:将请求数据模型转换为PSR-7 HTTP请求,并通过实际的HTTP客户端请求响应。
- 编码:传输可以接受编码器/解码器,它们负责将值对象数据转换为例如JSON有效载荷,反之亦然。
- HTTP客户端:您想使用哪种PSR-18 HTTP客户端:guzzle、curl、symfony/http-client等
通过使用这种架构,我们提供了一个易于扩展的流程,其中包含用模型替换繁琐的数组结构。
您可能熟悉一个“客户端”类,它提供对多个API端点的访问。我们认为这种方法是一个多请求处理器类。您可以选择这种方法,但是,我们建议为每个API端点使用1个请求处理器。这样,您只需将当时需要注入/模拟的东西放入代码库中。
示例实现
<?php use Phpro\HttpTools\Transport\Presets\JsonPreset; use Phpro\HttpTools\Uri\TemplatedUriBuilder; $transport = App\SomeClient\Transport\MyCustomTransportWrapperForDealingWithIsErrorPropertyEg( JsonPreset::create( $httpClient, new TemplatedUriBuilder() ) );
示例请求处理器
<?php use Phpro\HttpTools\Transport\TransportInterface; class ListSomething { public function __construct( /** * TransportInterface<array, array> */ private TransportInterface $transport ) {} public function __invoke(ListRequest $request): ListResponse { // You could validate the result first + throw exceptions based on invalid content // Tip : never trust APIs! // Try to gracefully fall back if possible and keep an eye on how the implementation needs to handle errors! return ListResponse::fromRawArray( ($this->transport)($request) ); } }
<?php use Phpro\HttpTools\Request\RequestInterface; // By wrapping the request in a Value Object, you can use named constructors to pass in filters and POST data. // You can add multiple named constructors if you want the list to behave in different ways in some cases. /** * @implements RequestInterface<array> */ class ListRequest implements RequestInterface { public function method() : string { return 'GET'; } public function uri() : string { return '/list{?query}'; } public function uriParameters() : array { return [ 'query' => 'somequery', ]; } public function body() : array { return []; } } // By wrapping the response in a Value Object, you can sanitize and normalize data. // You could as well lazilly throw an exception in here if some value is missing. // However, that's might be more of a task for a request-handler. class ListResponse { public static function fromRawArray(array $data): self { return new self($data); } public function getItems(): array { // Never trust APIs! return (array) ($this->data['items'] ?? []); } }
这个示例相当简单,一开始可能看起来有些过度。一旦您在请求模型中创建了多个命名构造函数和条件属性访问器,真正的力量就会显现出来。如果精心构建响应模型,将提高您集成的稳定性!
异步请求处理器
为了发送异步请求,您可以结合使用此包和基于fibers的PSR-18客户端。架构可以保持不变。
一个基于ReactPHP的示例客户端可能基于此
composer require react/async veewee/psr18-react-browser
(目前还没有AMP或ReactPHP的官方基于fibers的PSR-18实现。因此,可以使用一个小型桥接器作为中间解决方案)
由于fibers处理异步部分,您可以像编写同步请求处理器一样编写请求处理器
<?php use Phpro\HttpTools\Transport\TransportInterface; class FetchSomething { public function __construct( /** * TransportInterface<array, array> */ private TransportInterface $transport ) {} public function __invoke(FetchRequest $request): Something { return Something::tryParse( ($this->transport)($data) ); } }
为了获取多个同时请求,您可以并行执行这些操作
use Phpro\HttpTools\Transport\Presets\JsonPreset; use Phpro\HttpTools\Uri\RawUriBuilder; use Phpro\HttpTools\Uri\TemplatedUriBuilder; use Veewee\Psr18ReactBrowser\Psr18ReactBrowserClient; use function React\Async\async; use function React\Async\await; use function React\Async\parallel; $client = Psr18ReactBrowserClient::default(); $transport = JsonPreset::create($client, new TemplatedUriBuilder()); $handler = new FetchSomething($transport); $run = fn($id) => async(fn () => $handler(new FetchRequest($id))); $things = await(parallel([ $run(1), $run(2), $run(3), ]));
如果您的客户端与fibers兼容,这将并行获取所有请求。如果您的客户端不与fibers兼容,这将导致请求依次执行。
SDK
在某些情况下,编写请求处理器可能有些过度。此包还提供了一些工具,可以组合更通用的API客户端。然而,我们的主要建议是创建特定的请求处理器!
测试HTTP客户端
此工具为使用PHPUnit对API客户端进行单元测试提供了一些特性。
UseHttpFactories
此特性可以帮助您在测试中构建请求和响应,而无需担心您使用的HTTP包
createRequest
createResponse
createStream
createEmptyHttpClientException
UseHttpToolsFactories
此特性可以帮助您在单元测试中构建特定的HTTP工具对象。例如,可以用来测试传输。
createToolsRequest
示例
$request = $this->createToolsRequest('GET', '/some-endpoint', [], ['hello' => 'world']);
UseMockClient
包含UseHttpFactories
特性
最好用它来测试您自己的中间件和传输。也有可能测试请求处理器,但您必须手动提供响应。
示例
<?php use Http\Mock\Client; use \Phpro\HttpTools\Test\UseMockClient; class SomeTest extends TestCase { use UseMockClient; protected function setUp(): void { // You can configure the mock client through a callback. // Or you can skip the callback and configure the result of this method. $this->client = $this->mockClient(function (Client $client): Client { $client->setDefaultException(new \Exception('Dont call me!')); return $client; }); } }
UseVcrClient
包含UseHttpFactories
特性
此客户端可以用于使用实时数据测试请求处理器。第一次在测试中使用它时,它将执行实际的HTTP请求。此请求的响应将被记录并存储在您的项目中。第二次测试运行时,它将使用记录的版本。
示例
<?php use Http\Client\Plugin\Vcr\NamingStrategy\PathNamingStrategy; use Phpro\HttpTools\Client\Factory\AutoDiscoveredClientFactory; use Phpro\HttpTools\Test\UseVcrClient; class SomeTest extends TestCase { use UseVcrClient; protected function setUp(): void { // Instead of the autodiscover client, you can use your own client factory. // That way, you can e.g. add the required authentication, ... $this->client = AutoDiscoveredClientFactory::create([ ...$this->useRecording(FIXTURES_DIR, new PathNamingStrategy()) ]); } }