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

https://github.com/phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.
https://github.com/phpro/http-tools

Last synced: about 1 year ago
JSON representation

HTTP tools for developing more consistent HTTP implementations.

Awesome Lists containing this project

README

          

![Github Actions](https://github.com/phpro/http-tools/workflows/GrumPHP/badge.svg?branch=master)
[![Installs](https://img.shields.io/packagist/dt/phpro/http-tools.svg)](https://packagist.org/packages/phpro/http-tools/stats)
[![Packagist](https://img.shields.io/packagist/v/phpro/http-tools.svg)](https://packagist.org/packages/phpro/http-tools)

# HTTP-Tools

The goal of this package is to provide you some tools to set-up a data-centric, consistent HTTP integration.
The HTTP client implementation you want to use is just a small implementation detail and doesn't matter.
However, here are some default guidelines:

## Prerequisites

Choosing what HTTP package you want to us is all up to you.
We do require a PSR implementations in order to install this package:

* PSR-7: `psr/http-message-implementation` like `nyholm/psr7` or `guzzlehttp/psr7`
* PSR-17: `psr/http-factory-implementation` like `nyholm/psr7` or `guzzlehttp/psr7`
* PSR-18: `psr/http-client-implementation` like `symfony/http-client` or `guzzlehttp/guzzle`

## Installation

```bash
composer require phpro/http-tools
```

## Setting up an HTTP client :

You can choose whatever HTTP client you want.
However, this package provides some convenient factories that make the configuration a bit easier.

The factory accepts a list of implementation specific plugins / middlewares.
Besides that, you can configure implementation-specific options like a base_uri or default headers.

```php
$_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);
```

You can always create your own factory if you want to have more control or want to use another tool!

**Note:** This package does not download a specific HTTP implementation. You can choose whatever package you want, but you'll have to manually add it to composer.

### Configuring the client through plugins

[Plugin](http://docs.php-http.org/en/latest/plugins/index.html)-[Middleware](http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html) : Patato-Patato.

If you want to extend how an HTTP client works, we want you to use plugins!
You can use plugins for everything: logging, authentication, language specification, ...

Examples:

```php

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

*/
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'] ?? []);
}
}
```

This example is rather easy and might seem like overkill at first.
The true power will be visible once you create multiple named constructors and conditional porperty accessors inside the request models.
The response models, if crafted carefully, will improve the stability of your integration!

## Async request handlers

In order to send async requests, you can use this package in combination with fiber-based PSR-18 clients.
The architecture can remain as is.

An example client based on ReactPHP might be based on this:

```sh
composer require react/async veewee/psr18-react-browser
```

*(There currently is no official fiber based PSR-18 implementation of either AMP or ReactPHP. Therefore, [a small bridge can be used intermediately](https://github.com/veewee/psr18-react-browser))*

Since fibers deal with the async part, you can write your Request handlers is if they were synchronous:

```php

*/
private TransportInterface $transport
) {}

public function __invoke(FetchRequest $request): Something
{
return Something::tryParse(
($this->transport)($data)
);
}
}
```

In order to fetch multiple simultaneous requests, you can execute these in parallel:

```php
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),
]));
```

If your client is fiber compatible, this will fetch all requests in parallel.
If your client is not fiber compatible, this will result in the requests being performed in series.

## SDK

In some situations, writing request handlers might be overkill.
This package also provides some tools to compose a more generic API client instead.
However, our primary suggestion is to create specific request handlers instead!

[More information on creating SDKs](docs/sdk.md)

## Testing HTTP clients

This tool provided some traits for unit testing your API client with PHPUnit.

### UseHttpFactories

This trait can help you build requests and responses inside your tests without worrying what HTTP package you use:

* `createRequest`
* `createResponse`
* `createStream`
* `createEmptyHttpClientException`

### UseHttpToolsFactories

This trait can help you build HTTP tools specific objects in unit tests.
It could be hande to e.g. test transports.

* `createToolsRequest`

Example:

```php
$request = $this->createToolsRequest('GET', '/some-endpoint', [], ['hello' => 'world']);
```

### UseMockClient

*Includes `UseHttpFactories` trait*

Preferably, this one will be used to test your own middlewares and transports.
It is also possible to test a request-handler, but you'll have to manually provide the response for it.

example:

```php
client = $this->mockClient(function (Client $client): Client {
$client->setDefaultException(new \Exception('Dont call me!'));
return $client;
});
}
}
```

[More info ...](http://docs.php-http.org/en/latest/clients/mock-client.html)

### UseVcrClient

*Includes `UseHttpFactories` trait*

This one can be used to test your request-handlers with realtime data.
The first you use it in your test, it will do the actual HTTP request.
The response of this request will be recorded and stored inside your project.
The second time the test runs, it will use the recorded version.

example:

```php
client = AutoDiscoveredClientFactory::create([
...$this->useRecording(FIXTURES_DIR, new PathNamingStrategy())
]);
}
}
```

[More info ...](http://docs.php-http.org/en/latest/plugins/vcr.html)