An open API service indexing awesome lists of open source software.

https://github.com/andy87/php-client-sdk

Base abstractions for building typed PHP API clients.
https://github.com/andy87/php-client-sdk

Last synced: about 1 month ago
JSON representation

Base abstractions for building typed PHP API clients.

Awesome Lists containing this project

README

          

# PHP Client SDK

Base abstractions for building typed PHP API clients.

[Russian documentation](docs/ru/README.md)

## Overview

`andy87/php-client-sdk` provides a small set of reusable building blocks for API client SDKs:

- prompt DTOs for request method, endpoint, path parameters, query parameters, body and validation;
- response DTOs for normalized response data, status code, headers and API errors;
- provider base class for executing typed API methods;
- pluggable authorization strategies;
- pluggable HTTP transport with a native PHP stream implementation.

The package does not generate API clients and does not depend on a specific HTTP client library.

## Requirements

- PHP 8.1 or higher.
- Composer.

## Installation

```bash
composer require andy87/php-client-sdk
```

## Core Concepts

The package separates an API call into three parts:

- `PromptInterface` describes an outgoing request.
- `ResponseInterface` describes a typed API response.
- `AbstractProvider` connects prompts, responses, authorization and HTTP transport.

`NativeHttpTransport` can be used without extra dependencies. If a project needs another transport, implement `HttpTransportInterface`.

## Prompt DTO

Extend `AbstractPrompt` to describe a request. The base class hydrates declared properties from input data, validates required fields, builds path/query/body arrays and normalizes nested DTO values through `toArray()` or `toValue()` when those methods exist.

Use `PublicPrompt` for public endpoints and `PrivatePrompt` for private endpoints with an authorization profile. `AbstractPrompt` remains the generic base class for custom prompt schemes.

```php
'id',
'includePosts' => 'include_posts',
];
protected const REQUIRED_FIELDS = ['id'];
protected const PATH_FIELDS = ['id'];
protected const QUERY_FIELDS = ['includePosts'];
protected const BODY_FIELDS = [];
protected const CONTENT_TYPE = null;

public int $id;
public ?bool $includePosts = null;
}
```

```php
'id',
'name' => 'name',
];
protected const REQUIRED_FIELDS = ['id', 'name'];

public int $id;
public string $name;
}
```

## Provider Usage

Extend `AbstractProvider` and expose public methods for concrete API operations. The protected `request()` method validates the prompt, adds authorization headers when required, sends the HTTP request and returns the requested response DTO.

```php
request(
new GetUserPrompt(['id' => $id]),
GetUserResponse::class,
);
}
}
```

Create the provider with a base URL, authorization strategy and transport:

```php
getUser(123);

if ($response->hasError()) {
$error = $response->getError();
echo $error?->message ?? 'API request failed.';
}

echo $response->getStatusCode();
```

## Client Options

`ClientOptions` is the main extension point. If it is not passed, the SDK uses safe defaults: JSON requests and responses, strict successful response validation, native no-retry policy and default request factory.

Configurable parts:

- `timeout`, `headers`, `events`;
- `strictValidation`;
- `validatePrompt`;
- `retryPolicy`;
- `queryEncoder`;
- `bodyEncoder`;
- `responseDecoder`;
- `errorFactory`;
- `requestFactory`.
- `authorizationResolver`;
- `refreshAuthorizationStatusCodes`.

Retry is disabled by default. Use `DefaultRetryPolicy` only when repeated requests are safe for the target API operation.

`validatePrompt` controls local prompt validation before a request is built. It is enabled by default. Set it to `false` only in mock or test environments where a client must return success fixtures for incomplete input:

```php
'crm',
],
events: [
ClientEvents::BEFORE_REQUEST => static function (BeforeRequestEvent $event): void {
$event->request->headers['X-Trace-Id'] = bin2hex(random_bytes(8));
},
],
);

$runtime->addHeaders([
'X-Account' => 'main',
]);
```

Supported events:

- `ClientEvents::AFTER_INIT` after a concrete client finishes initialization.
- `ClientEvents::BEFORE_REQUEST` before transport sends a mutable `HttpRequest`.
- `ClientEvents::AFTER_REQUEST` after raw HTTP response is converted to a typed response DTO.
- `ClientEvents::REQUEST_EXCEPTION` after transport, JSON decoding or response DTO construction fails.

Header names are merged case-insensitively. Authorization headers override default runtime headers, and `BEFORE_REQUEST` listeners can still mutate the final request.

## Authorization

Use `NullAuthorizationStrategy` for public APIs:

```php
new ApiKeyAuthorizationStrategy('X-Api-Key', 'secret'),
]),
);
```

For `PrivatePrompt` subclasses, prefer profile names such as `default`, `avito-client-credentials`, `api-key` or `sandbox-token`:

```php
$options = new ClientOptions(
authorizationResolver: new AuthorizationProfileStrategyResolver([
'orders-api' => new ApiKeyAuthorizationStrategy('X-Api-Key', 'secret'),
]),
);
```

## HTTP Transport

`NativeHttpTransport` sends requests through PHP stream wrappers. It supports:

- query parameters;
- JSON request bodies;
- `application/x-www-form-urlencoded` request bodies;
- `multipart/form-data` request bodies through `MultipartFile`;
- already encoded raw request bodies;
- response status code and headers;
- JSON response decoding through `HttpResponse::json()`.

Custom transports must implement `HttpTransportInterface`:

```php
addJson('GET', '/users/{id}', [
'id' => 123,
'name' => 'Mock User',
]);

$provider = new UsersProvider(
baseUrl: 'https://api.example.com',
authorizationStrategy: new NullAuthorizationStrategy(),
transport: new MockTransport($resolver),
options: new ClientOptions(validatePrompt: false),
);
```

Routes match by HTTP method and absolute URL, path or endpoint template stored in request metadata. OAuth token requests can be mocked by absolute token URL:

```php
$resolver->addJson('POST', 'https://auth.example.com/oauth/token', [
'access_token' => 'mock-token',
'expires_in' => 3600,
]);
```

`validatePrompt=false` disables only `Prompt::validate()`. Request building can still fail when a prompt cannot provide a method, endpoint or required path placeholder.

If route paths are unstable or generated, use `PromptClassMockResponseResolver` to bind fixtures to Prompt DTO classes:

```php
addJson(GetUserPrompt::class, [
'id' => 123,
'name' => 'Mock User',
]);

$provider = new UsersProvider(
baseUrl: 'https://api.example.com',
authorizationStrategy: new NullAuthorizationStrategy(),
transport: new MockTransport($resolver),
);
```

## Traceable Transport

`TraceableTransport` wraps any `HttpTransportInterface` and records requests, responses, exceptions and duration without changing transport behavior.

```php
getUser(123);
$lastRecord = $transport->getLastRecord();
```

Response DTOs can also store local diagnostic notes:

```php
$response->addDiagnostic(['source' => 'fixture', 'case' => 'empty-list']);
$diagnostics = $response->getDiagnostics();
```

## Error Handling

- Prompt validation throws `InvalidArgumentException` when a required field is missing or empty.
- Request factory validation can throw `ValidationException` when an endpoint contains an unfilled path placeholder.
- Authorization failures throw `AuthorizationException`.
- Transport failures throw `TransportException`.
- Successful non-JSON responses throw `ResponseDecodeException`.
- Response DTO construction failures throw `ResponseHydrationException`.
- HTTP responses with status code `400` or higher are converted to `ApiError` and available through `ResponseInterface::getError()`, including non-JSON error bodies.
- Successful responses with missing required fields throw `UnexpectedValueException` when `strictValidation` is enabled.

## License

MIT.