Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use PSR-18 (and PSR-17) interfaces instead of PHP-HTTP #60

Merged
merged 1 commit into from
Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed

* Switched from [PHP-HTTP](http://php-http.org/) to [PSR-18](https://www.php-fig.org/psr/psr-18/), its successor.
* The `\Swis\JsonApi\Client\Client` now uses [php-http/discovery](https://github.com/php-http/discovery) itself instead of the service provider. This should make usage without Laravel easier.
* Removed the `$baseUri` parameter from `\Swis\JsonApi\Client\Client::__construct()`, use `\Swis\JsonApi\Client\Client::setBaseUri()` instead.

### Removed

* Removed `\Swis\JsonApi\Client\Providers\ServiceProvider::getHttpClient()` and `\Swis\JsonApi\Client\Providers\ServiceProvider::getMessageFactory()` as the client now discovers these classes itself. Custom HTTP clients must now be registered within your own service provider using a custom container binding.

### Fixed

* Self and related links can not be `null` [#59](https://github.com/swisnl/json-api-client/pull/59).
Expand Down
54 changes: 24 additions & 30 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ A PHP package for mapping remote [JSON:API](http://jsonapi.org/) resources to El
composer require swisnl/json-api-client
```

N.B. Make sure you have installed a HTTP Client before you install this package or install one at the same time e.g. `composer require swisnl/json-api-client php-http/guzzle6-adapter`.
N.B. Make sure you have installed a PSR-18 HTTP Client before you install this package or install one at the same time e.g. `composer require swisnl/json-api-client php-http/guzzle6-adapter`.

### HTTP Client

We are decoupled from any HTTP messaging client with the help of [PHP-HTTP](http://docs.php-http.org/en/latest/httplug/users.html).
This requires another package providing [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation).
We are decoupled from any HTTP messaging client with the help of [PSR-18 HTTP Client](https://www.php-fig.org/psr/psr-18/).
This requires an extra package providing [psr/http-client-implementation](https://packagist.org/providers/psr/http-client-implementation).
To use Guzzle 6, for example, simply require `php-http/guzzle6-adapter`:

``` bash
Expand Down Expand Up @@ -498,44 +498,38 @@ The service provider registers the `TypeMapper` as a singleton so your entire ap
### Bind Clients

The service provider registers the `Client` and `DocumentClient` to your application.
By default it uses [php-http/discovery](https://github.com/php-http/discovery) to find a HTTP client and message factory.
You can specify your own message factory by overwriting the `getMessageFactory()` method and your own HTTP client by overwriting the `getHttpClient()` method.
The first should return a message factory and the latter should return a HTTP client, both implementing HTTPlug.
These methods are the perfect place to add extra options to your HTTP client or register a mock HTTP client for your tests:
By default the `Client` uses [php-http/discovery](https://github.com/php-http/discovery) to find an available HTTP client, request factory and stream factory so you don't have to setup those yourself.
You can specify your own HTTP client, request factory or stream factory by customizing the container binding.
This is a perfect way to add extra options to your HTTP client or register a mock HTTP client for your tests:

``` php
protected function getHttpClient(): HttpClient
class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
if (app()->environment('testing')) {
return new \Swis\Http\Fixture\Client(
new \Swis\Http\Fixture\ResponseBuilder('/path/to/fixtures');
);
protected function register()
{
$this->app->bind(\Swis\JsonApi\Client\Client::class, function ($app) {
if ($app->environment('testing')) {
$httpClient = new \Swis\Http\Fixture\Client(
new \Swis\Http\Fixture\ResponseBuilder('/path/to/fixtures');
);
} else {
$httpClient = \Http\Adapter\Guzzle6\Client::createWithConfig(
[
'timeout' => 2,
]
);
}

return new \Swis\JsonApi\Client\Client($httpClient);
});
}

return \Http\Adapter\Guzzle6\Client::createWithConfig(
[
'timeout' => 2,
]
);
}
```

N.B. This example uses our [swisnl/php-http-fixture-client](https://github.com/swisnl/php-http-fixture-client) when in testing environment.
This package allows you to easily mock requests with static fixtures.
Definitely worth a try!

If you register your own service provider and use package auto discover, don't forget to exclude this package in your `package.json`:

``` json
"extra": {
"laravel": {
"dont-discover": [
"swisnl/json-api-client"
]
}
},
```


## Advanced usage

Expand Down
13 changes: 9 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
"ext-json": "*",
"illuminate/support": "5.5.*|5.6.*|5.7.*|5.8.*",
"jenssegers/model": "^1.1",
"php-http/client-implementation": "^1.0",
"php-http/discovery": "^1.0"
"nyholm/psr7": "^1.2",
bbrala marked this conversation as resolved.
Show resolved Hide resolved
"php-http/discovery": "^1.0",
"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"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"graham-campbell/testbench": "^5.1",
"phpunit/phpunit": "^6.1|^7.0|^8.0",
"php-http/guzzle6-adapter": "^1.1",
"php-http/mock-client": "^1.1"
"php-http/guzzle6-adapter": "^2.0",
"php-http/mock-client": "^1.2"
},
"suggest": {
"swisnl/php-http-fixture-client": "Allows for easily mocking API calls with fixtures in your tests"
Expand Down
165 changes: 89 additions & 76 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,59 @@

namespace Swis\JsonApi\Client;

use Http\Client\Exception\HttpException;
use Http\Client\HttpClient;
use Http\Message\MessageFactory;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;
use Psr\Http\Client\ClientInterface as HttpClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Swis\JsonApi\Client\Interfaces\ClientInterface;

class Client implements ClientInterface
{
/**
* @var string
*/
const METHOD_DELETE = 'DELETE';

/**
* @var string
*/
const METHOD_GET = 'GET';

/**
* @var string
* @var \Psr\Http\Client\ClientInterface
*/
const METHOD_PATCH = 'PATCH';
private $client;

/**
* @var string
* @var \Psr\Http\Message\RequestFactoryInterface
*/
const METHOD_POST = 'POST';
private $requestFactory;

/**
* @var \Http\Client\HttpClient
* @var \Psr\Http\Message\StreamFactoryInterface
*/
private $client;
private $streamFactory;

/**
* @var string
*/
private $baseUri;
private $baseUri = '';

/**
* @var MessageFactory
* @var array
*/
private $messageFactory;

protected $defaultHeaders = [
private $defaultHeaders = [
'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json',
];

/**
* @param \Http\Client\HttpClient $client
* @param string $baseUri
* @param MessageFactory $messageFactory
* @param \Psr\Http\Client\ClientInterface|null $client
* @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory
* @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory
*/
public function __construct(
HttpClient $client,
string $baseUri,
MessageFactory $messageFactory
HttpClientInterface $client = null,
RequestFactoryInterface $requestFactory = null,
StreamFactoryInterface $streamFactory = null
) {
$this->client = $client;
$this->baseUri = $baseUri;
$this->messageFactory = $messageFactory;
$this->client = $client ?: Psr18ClientDiscovery::find();
$this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
$this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();
}

/**
Expand All @@ -77,97 +68,124 @@ public function getBaseUri(): string
/**
* @param string $baseUri
*/
public function setBaseUri(string $baseUri)
public function setBaseUri(string $baseUri): void
{
$this->baseUri = $baseUri;
}

/**
* @return array
*/
public function getDefaultHeaders(): array
{
return $this->defaultHeaders;
}

/**
* @param array $defaultHeaders
*/
public function setDefaultHeaders(array $defaultHeaders): void
{
$this->defaultHeaders = $defaultHeaders;
}

/**
* @param string $endpoint
* @param array $headers
*
* @throws \Http\Client\Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function get(string $endpoint, array $headers = []): ResponseInterface
{
return $this->request(static::METHOD_GET, $endpoint, null, $headers);
return $this->request('GET', $endpoint, null, $headers);
}

/**
* @param string $endpoint
* @param resource|string|int|float|bool|\Psr\Http\Message\StreamInterface|callable|null $body
* @param array $headers
* @param string $endpoint
* @param string|resource|\Psr\Http\Message\StreamInterface|null $body
* @param array $headers
*
* @throws \Http\Client\Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function post(string $endpoint, $body, array $headers = []): ResponseInterface
{
return $this->request(static::METHOD_POST, $endpoint, $body, $headers);
return $this->request('POST', $endpoint, $body, $headers);
}

/**
* @param string $endpoint
* @param resource|string|int|float|bool|\Psr\Http\Message\StreamInterface|callable|null $body
* @param array $headers
* @param string $endpoint
* @param string|resource|\Psr\Http\Message\StreamInterface|null $body
* @param array $headers
*
* @throws \Http\Client\Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function patch(string $endpoint, $body, array $headers = []): ResponseInterface
{
return $this->request(static::METHOD_PATCH, $endpoint, $body, $headers);
return $this->request('PATCH', $endpoint, $body, $headers);
}

/**
* @param string $endpoint
* @param array $headers
*
* @throws \Http\Client\Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function delete(string $endpoint, array $headers = []): ResponseInterface
{
return $this->request(static::METHOD_DELETE, $endpoint, null, $headers);
return $this->request('DELETE', $endpoint, null, $headers);
}

/**
* @param string $method
* @param string $endpoint
* @param resource|string|int|float|bool|\Psr\Http\Message\StreamInterface|callable|null $body
* @param array $headers
* @param string $method
* @param string $endpoint
* @param string|resource|\Psr\Http\Message\StreamInterface|null $body
* @param array $headers
*
* @throws \Http\Client\Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function request(string $method, string $endpoint, $body = null, array $headers = []): ResponseInterface
{
$request = $this->buildRequest($method, $endpoint, $body, $headers);

try {
return $this->client->sendRequest($request);
} catch (HttpException $e) {
return $e->getResponse();
}
return $this->client->sendRequest($this->buildRequest($method, $endpoint, $body, $headers));
}

/**
* @param string $method
* @param string $endpoint
* @param resource|string|int|float|bool|\Psr\Http\Message\StreamInterface|callable|null $body
* @param array $headers
* @param string $method
* @param string $endpoint
* @param string|resource|\Psr\Http\Message\StreamInterface|null $body
* @param array $headers
*
* @return \Psr\Http\Message\RequestInterface
*/
protected function buildRequest(string $method, string $endpoint, $body = null, array $headers = []): RequestInterface
{
return $this->messageFactory->createRequest($method, $this->getEndpoint($endpoint), $this->mergeHeaders($headers), $body);
$request = $this->requestFactory->createRequest($method, $this->getEndpoint($endpoint));

if ($body !== null) {
if (is_resource($body)) {
$body = $this->streamFactory->createStreamFromResource($body);
}
if (!($body instanceof StreamInterface)) {
$body = $this->streamFactory->createStream($body);
}

$request = $request->withBody($body);
}

foreach ($this->mergeHeaders($headers) as $name => $value) {
$request = $request->withHeader($name, $value);
}

return $request;
}

/**
Expand All @@ -180,18 +198,13 @@ protected function getEndpoint(string $endpoint): string
return $this->baseUri.$endpoint;
}

protected function mergeHeaders(array $headers = []): array
/**
* @param array $headers
*
* @return array
*/
protected function mergeHeaders(array $headers): array
bbrala marked this conversation as resolved.
Show resolved Hide resolved
{
return array_merge($this->defaultHeaders, $headers);
}

public function getDefaultHeaders(): array
{
return $this->defaultHeaders;
}

public function setDefaultHeaders(array $defaultHeaders)
{
$this->defaultHeaders = $defaultHeaders;
}
}
Loading