Skip to content

Commit

Permalink
feat: implement Guzzle ClientInterface
Browse files Browse the repository at this point in the history
  • Loading branch information
JaZo committed Feb 28, 2024
1 parent e404e91 commit 6137e79
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 16 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![Total Downloads][ico-downloads]][link-downloads]
[![Made by SWIS][ico-swis]][link-swis]

Provides a bridge to use the Laravel HTTP Client as PSR-18 HTTP Client, for usage with libraries that require such a Client. This allows you to:
Provides a bridge to use the Laravel HTTP Client as PSR-18 (or Guzzle) HTTP Client, for usage with libraries that require such a Client. This allows you to:

* 🤖 Use request fakes and assertions in your tests
* 🔍 Debug requests in tools like Telescope or Pulse
Expand All @@ -24,16 +24,17 @@ composer require swisnl/laravel-psr-http-client-bridge

## Usage

Simply instantiate the `Swis\Laravel\Bridge\PsrHttpClient\Client` and use it as you would use any other PSR-18 or Guzzle HTTP client.

``` php
$client = new Swis\Laravel\Bridge\PsrHttpClient\Client();
$request = new Psr\Http\Message\RequestImplementation();
$response = $client->sendRequest($request);
$httpClient = new Swis\Laravel\Bridge\PsrHttpClient\Client();
$client = new My\Awesome\Api\Client($httpClient);
```

If you want to configure some request options, you can provide a callable that returns a `PendingRequest`.

``` php
$client = new Swis\Laravel\Bridge\PsrHttpClient\Client(
$httpClient = new Swis\Laravel\Bridge\PsrHttpClient\Client(
fn () => Http::withOptions(['proxy' => 'http://localhost:8125'])
);
```
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
],
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.4",
"illuminate/contracts": "^10.0",
"psr/http-client": "^1.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"laravel/pint": "^1.0",
"nunomaduro/collision": "^7.8",
"larastan/larastan": "^2.0.1",
Expand Down
52 changes: 49 additions & 3 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

namespace Swis\Laravel\Bridge\PsrHttpClient;

use GuzzleHttp\ClientInterface as GuzzleClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\ClientInterface as PsrClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class Client implements ClientInterface
class Client implements GuzzleClientInterface, PsrClientInterface
{
/**
* @var callable
Expand All @@ -26,9 +28,53 @@ public function __construct(?callable $pendingRequestFactory = null)

public function sendRequest(RequestInterface $request): ResponseInterface
{
return call_user_func($this->pendingRequestFactory, $request)
return $this->newPendingRequest()
->withHeaders($request->getHeaders())
->send($request->getMethod(), (string) $request->getUri(), ['body' => $request->getBody()])
->toPsrResponse();
}

public function send(RequestInterface $request, array $options = []): ResponseInterface
{
return $this->newPendingRequest()
->withHeaders($request->getHeaders())
->send($request->getMethod(), (string) $request->getUri(), array_merge(['body' => $request->getBody()], $options))
->toPsrResponse();
}

public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
{
/** @var \GuzzleHttp\Promise\PromiseInterface */
return $this->newPendingRequest()
->async()
->withHeaders($request->getHeaders())
->send($request->getMethod(), (string) $request->getUri(), array_merge(['body' => $request->getBody()], $options));
}

public function request(string $method, $uri, array $options = []): ResponseInterface
{
return $this->newPendingRequest()
->send($method, (string) $uri, $options)
->toPsrResponse();
}

public function requestAsync(string $method, $uri, array $options = []): PromiseInterface
{
/** @var \GuzzleHttp\Promise\PromiseInterface */
return $this->newPendingRequest()
->async()
->send($method, (string) $uri, $options);
}

public function getConfig(?string $option = null)
{
$options = $this->newPendingRequest()->getOptions();

return $option === null ? $options : ($options[$option] ?? null);
}

protected function newPendingRequest(): PendingRequest
{
return call_user_func($this->pendingRequestFactory);
}
}
86 changes: 79 additions & 7 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@
use Illuminate\Support\Facades\Http;
use Swis\Laravel\Bridge\PsrHttpClient\Client;

it('can send a request', function () {
it('uses the provided request factory', function () {
Http::fake(['*' => Http::response()]);

$client = new Client(fn () => Http::withHeaders(['X-Foo' => 'Bar']));
$client->sendRequest(new PsrRequest('GET', 'https://example.com'));

Http::assertSent(function (Request $request) {
return $request->method() === 'GET'
&& $request->url() === 'https://example.com'
&& $request->hasHeader('X-Foo', 'Bar');
});
});

it('implements sendRequest', function () {
Http::fake(['*' => Http::response('foo=baz', 404, ['X-Foo' => 'Baz'])]);

$client = new Client();
Expand All @@ -23,15 +36,74 @@
->and($response->getHeader('X-Foo'))->toBe(['Baz']);
});

it('uses the provided request factory', function () {
Http::fake(['*' => Http::response()]);
it('implements send', function () {
Http::fake(['*' => Http::response('foo=baz', 404, ['X-Foo' => 'Baz'])]);

$client = new Client(fn () => Http::withHeaders(['X-Foo' => 'Bar']));
$client->sendRequest(new PsrRequest('GET', 'https://example.com'));
$client = new Client();
$response = $client->send(new PsrRequest('POST', 'https://example.com', ['X-Foo' => 'Bar'], 'foo=bar'));

Http::assertSent(function (Request $request) {
return $request->method() === 'GET'
return $request->method() === 'POST'
&& $request->url() === 'https://example.com'
&& $request->hasHeader('X-Foo', 'Bar');
&& $request->hasHeader('X-Foo', 'Bar')
&& $request->body() === 'foo=bar';
});

expect((string) $response->getBody())->toBe('foo=baz')
->and($response->getStatusCode())->toBe(404)
->and($response->getHeader('X-Foo'))->toBe(['Baz']);
});

it('implements sendAsync', function () {
Http::fake(['*' => Http::response('foo=baz', 404, ['X-Foo' => 'Baz'])]);

$client = new Client();
$response = $client->sendAsync(new PsrRequest('POST', 'https://example.com', ['X-Foo' => 'Bar'], 'foo=bar'))->wait();

Http::assertSent(function (Request $request) {
return $request->method() === 'POST'
&& $request->url() === 'https://example.com'
&& $request->hasHeader('X-Foo', 'Bar')
&& $request->body() === 'foo=bar';
});

expect((string) $response->getBody())->toBe('foo=baz')
->and($response->getStatusCode())->toBe(404)
->and($response->getHeader('X-Foo'))->toBe(['Baz']);
});

it('implements request', function () {
Http::fake(['*' => Http::response('foo=baz', 404, ['X-Foo' => 'Baz'])]);

$client = new Client();
$response = $client->request('POST', 'https://example.com', ['headers' => ['X-Foo' => 'Bar'], 'body' => 'foo=bar']);

Http::assertSent(function (Request $request) {
return $request->method() === 'POST'
&& $request->url() === 'https://example.com'
&& $request->hasHeader('X-Foo', 'Bar')
&& $request->body() === 'foo=bar';
});

expect((string) $response->getBody())->toBe('foo=baz')
->and($response->getStatusCode())->toBe(404)
->and($response->getHeader('X-Foo'))->toBe(['Baz']);
});

it('implements requestAsync', function () {
Http::fake(['*' => Http::response('foo=baz', 404, ['X-Foo' => 'Baz'])]);

$client = new Client();
$response = $client->requestAsync('POST', 'https://example.com', ['headers' => ['X-Foo' => 'Bar'], 'body' => 'foo=bar'])->wait();

Http::assertSent(function (Request $request) {
return $request->method() === 'POST'
&& $request->url() === 'https://example.com'
&& $request->hasHeader('X-Foo', 'Bar')
&& $request->body() === 'foo=bar';
});

expect((string) $response->getBody())->toBe('foo=baz')
->and($response->getStatusCode())->toBe(404)
->and($response->getHeader('X-Foo'))->toBe(['Baz']);
});

0 comments on commit 6137e79

Please sign in to comment.