tomb1n0/generic-api-client 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

tomb1n0/generic-api-client

Composer 安装命令:

composer require tomb1n0/generic-api-client

包简介

A package to help speed up development when consuming APIs

README 文档

README

Latest Version on Packagist Total Downloads

When developing integrations for my PHP applications, i've often found myself carrying very similar but slightly different boiler-plate code around with me.

Often, each implementation will need:

  • Some way of handling auth.
    • Often is as simple as adding an extra header or property to the body of the request.
  • Pagination handling
    • Often boils down to checking for the presence of a header or some property in the body of the response, before fetching the next page with an added header/query parameter.
  • Response Mocking
    • Providing confidence that our integration is working as we expected, and error conditions are properly handled.

There's also the question of what HTTP client to use, with the introduction of PSR-7, PSR-17 and PSR-18 we are able to depend on HTTP clients and factories that implement these interfaces rather than relying on any one client.

My goal with this package is to provide a wrapper around these PSR interfaces that makes it simpler to write API integrations.

Installation

Please note that this package does not require a HTTP client out of the box - but rather it depends on the virtual packages psr/http-client-implementation and psr/http-factory-implementation. This allows the package to be client-agnostic.

If you're unsure on this, i would recommend requiring guzzlehttp/guzzle alongside this package as it provides implementations for the above virtual packages.

composer require tomb1n0/generic-api-client guzzlehttp/guzzle

If your project already require a HTTP-client that has implementations for the above standards, you can omit the guzzle dependency.

Usage

Please note that the examples below assume the use of Guzzle for the PSR-18 Client etc. Feel free to swap these out with your own.

Client Instantiation:

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.
);

Making a JSON request:

$response = $api->json('GET', 'https://dummyjson.com/products');

if ($response->successful()) {
    $products = $response->json('products');
}

Making a Form (x-www-form-urlencoded) request:

$response = $api->form('GET', 'https://dummyjson.com/products');

if ($response->successful()) {
    $products = $response->json('products');
}

Making a request using a PSR-7 request directly

$requestFactory = new GuzzleHttp\Psr7\HttpFactory();
$request = $requestFactory->createRequest('GET', 'https://example.com');

$response = $api->send($request);

if ($response->successful()) {
    // Do something with the response.
}

Configuration

Base Url:

$client = $existingClient->withBaseUrl('https://dummyjson.com');

$response = $client->json('GET', '/products'); // Will make a request to https://dummyjson.com/products.

Note that if you try to perform a request to a fully-formed URL that is different to the Base URL, the Base URL is ignored.

Pagination

You can create a pagination handler by creating a class that implements the PaginationHandlerContract interface provided by this package.

// 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
});

Middleware

Middleware can be created by creating a class that implements the MiddlewareContract interface.

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');

Note that it is possible for a middleware to mutate the request before it is sent, or the response after it is received.

Testing the API

Stubbing Responses

It is possible to stub responses for testing purposes:

// 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');
}

Preventing Stray Requests

By default the library will return a 200 OK for any non-matched responses when faked. If you prefer, you can prevent stray requests:

$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.
}

Asserting Requests

Maybe you want to assert the correct payload is sent to an API to create a user:

$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
});

Asserting Requests With Custom Request Matching

Maybe you want to stub a response using some other information in the request

Do the same as above but use the stubResponseWithCustomMatcher method providing a custom implementation of the matcher contract. For example you could use the included UrlMatcher to check the method type

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);

Running the Tests

composer test

Credits

License

The MIT License (MIT). Please see License File for more information.

tomb1n0/generic-api-client 适用场景与选型建议

tomb1n0/generic-api-client 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 20.28k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2023 年 01 月 14 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 tomb1n0/generic-api-client 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 tomb1n0/generic-api-client 我们能提供哪些服务?
定制开发 / 二次开发

基于 tomb1n0/generic-api-client 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 20.28k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 1
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-01-14