tomb1n0 / generic-api-client
一个用于在消费API时加速开发的包
Requires
- php: ^8.1
- psr/http-client: ^1.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
Requires (Dev)
- guzzlehttp/guzzle: ^7.5
- mockery/mockery: ^1.5
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^7.0|^8.0|^9.0
Suggests
- guzzlehttp/guzzle: A psr/http-client implementation that can be used with this package for a PSR-18 client
- guzzlehttp/psr7: A psr/http-factory implementation that can be used with this package for PSR-17 factories
This package is auto-updated.
Last update: 2024-09-15 14:14:35 UTC
README
在开发我的PHP应用程序的集成时,我经常发现自己带着非常相似但略有不同的样板代码。
通常,每个实现都需要
- 某种处理身份验证的方法。
- 通常只是添加一个额外的头部或属性到请求体。
- 分页处理
- 通常归结为在获取下一页之前,检查响应头部或请求体中是否存在某些属性。
- 响应模拟
- 提供信心,确保我们的集成按预期工作,并且错误条件得到适当处理。
还有关于使用哪个HTTP客户端的问题,随着PSR-7、PSR-17和PSR-18的引入,我们能够依赖实现这些接口的HTTP客户端和工厂,而不是依赖于任何特定的客户端。
我的目标是通过这个包提供一个围绕这些PSR接口的包装器,使编写API集成更加简单。
安装
请注意,此包默认情况下不需要HTTP客户端 - 而是依赖于虚拟包psr/http-client-implementation和psr/http-factory-implementation。这允许包不受客户端限制。
如果您不确定,我建议将guzzlehttp/guzzle与该包一起使用,因为它为上述虚拟包提供了实现。
composer require tomb1n0/generic-api-client guzzlehttp/guzzle
如果您的项目已经需要具有上述标准实现的HTTP客户端,则可以省略guzzle依赖。
使用
请注意,以下示例假设使用Guzzle作为PSR-18客户端等。您可以用自己的替换这些。
客户端实例化
use GuzzleHttp\Psr7\HttpFactory; use GuzzleHttp\Client as GuzzleHttpClient; $api = new Client( new GuzzleHttpClient(), // PSR-18 Client that sends the PSR-7 Request new HttpFactory(), // A PSR-17 Request Factory used to create PSR-7 Requests new HttpFactory(), // A PSR-17 Response Factory used to create PSR-7 Responses new HttpFactory(), // A PSR-17 Stream Factory used to create the bodies of our PSR-7 requests new HttpFactory(), // a PSR-17 URI Factory used to create URIs. );
发送JSON请求
$response = $api->json('GET', 'https://dummyjson.com/products'); if ($response->successful()) { $products = $response->json('products'); }
发送表单(x-www-form-urlencoded)请求
$response = $api->form('GET', 'https://dummyjson.com/products'); if ($response->successful()) { $products = $response->json('products'); }
直接使用PSR-7请求发送请求
$requestFactory = new GuzzleHttp\Psr7\HttpFactory(); $request = $requestFactory->createRequest('GET', 'https://example.com'); $response = $api->send($request); if ($response->successful()) { // Do something with the response. }
配置
基本URL
$client = $existingClient->withBaseUrl('https://dummyjson.com'); $response = $client->json('GET', '/products'); // Will make a request to https://dummyjson.com/products.
请注意,如果您尝试向与基本URL不同的完整URL发送请求,则基本URL将被忽略。
分页
您可以通过创建一个实现此包提供的PaginationHandlerContract接口的类来创建一个分页处理程序。
// Create a class that implements the PaginationHandlerContract class PaginationHandler implements PaginationHandlerContract { public function hasNextPage(Response $response): bool { return $response->toPsr7Response()->hasHeader('next-page'); } public function getNextPage(Response $response): RequestInterface { $originalRequest = $response->toPsr7Request(); $psr7Response = $response->toPsr7Response(); return $originalRequest->withHeader('page', $psr7Response->getHeaderLine('next-page')); } } $handler = new PaginationHandler(); $client = $existingClient->withPaginationHandler($handler); $response = $client->json('GET', 'https://dummyjson.com/products'); // HasNextPage will defer to the Pagination Handler to determine if the Response has a next page if ($response->hasNextPage()) { $nextPage = $response->getNextPage(); } // For convenience, a helper is provided to fetch all pages in a loop: $response->forEachPage(function (Response $response) { // Do something with this pages response });
中间件
可以通过创建一个实现MiddlewareContract接口的类来创建中间件。
class AuthenticationMiddleware implements MiddlewareContract { public function __construct(protected string $accessToken) { } public function handle(RequestInterface $request, callable $next): ResponseInterface { // Mutate the request $request = $request->withHeader('Authorization', 'Bearer ' . $this->accessToken); // Call the next middleware in the chain, ultimately fetching the Response. $response = $next($request); // Can also mutate the Response here if desired. $response = $response->withHeader('X-My-Header', 'Foo'); // Return the Response return $response; } } // Multiple middleware can be provided $client = $existingClient->withMiddleware([ new AuthenticationMiddleware('my-access-token'); ]); // The request will be sent through our middleware in the order given. $response = $client->json('GET', 'https://dummyjson.com/products');
请注意,中间件可以在发送请求之前或接收响应之后修改请求。
测试API
模拟响应
为了测试目的,可以模拟响应。
// It is important to call fake first, as this returns a new client with a Fake PSR-18 client underneath. $client = $existingClient->fake()->stubResponse( 'https://dummyjson.com/products', [ 'products' => [['id' => 1], ['id' => 2]], ], 200, ['X-Custom-Header' => 'Foo'], ); $response = $client->json('GET', 'https://dummyjson.com/products'); if ($response->successful()) { $products = $response->json('products'); }
防止意外请求
默认情况下,库对于任何未匹配的模拟响应将返回200 OK。如果您愿意,可以防止意外请求。
$client = $existingClient->fake()->preventStrayRequests(); try { // Make a request which has not been stubbed $response = $client->json('GET', 'https://dummyjson.com/products'); } catch (NoMatchingStubbedResponseException $e) { // a NoMatchingStubbedResponseException exception will be thrown. }
断言请求
也许您想断言正确的有效负载已发送到API以创建用户。
$client = $existingClient->fake()->stubResponse('https://dummyjson.com/users', null, 200); // This would likely be in some Service object method your test is calling. $response = $client->json('POST', 'https://dummyjson.com/users', ['name' => 'Tom']); // Assert we sent a request with the correct payload $client->assertSent(function (RequestInterface $request) { $contents = $request->getBody()->getContents(); $expected = ['name' => 'Tom']; return $contents === $expected });
使用自定义请求匹配断言请求
可能你想使用请求中的其他信息来模拟一个响应
与上面相同,但使用stubResponseWithCustomMatcher方法,提供一个自定义的匹配器合约的实现。例如,你可以使用包含的UrlMatcher来检查方法类型
class UrlMatcher implements FakeResponseMatcherContract { public function __construct(private string $url, private ?string $method = 'GET') { } public function match(RequestInterface $request): bool { $requestUrl = (string) $request->getUri(); $requestMethod = $request->getMethod(); return $this->url === $requestUrl && $this->method === $requestMethod; } }
$client = $existingClient->fake(); $client->stubResponseWithCustomMatcher(new UrlMatcher('https://dummyjson.com/users', 'GET'), null, 200); $client->stubResponseWithCustomMatcher(new UrlMatcher('https://dummyjson.com/users', 'POST'), null, 500);
运行测试
composer test
鸣谢
许可协议
MIT许可协议(MIT)。有关更多信息,请参阅许可文件。