From 053c419a6af1b7aac1e794b70b742c1cb8fec32c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 11 Feb 2024 20:32:51 +1000 Subject: [PATCH] Integrate Laravel HTTP client events --- composer.json | 6 +++ .../LazyJsonPagesServiceProvider.php | 52 +++++++++++++++++++ src/Services/Client.php | 34 ++++++++++-- tests/Integration/LaravelTest.php | 37 +++++++++++++ tests/Pest.php | 27 +++++++++- 5 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 src/Providers/LazyJsonPagesServiceProvider.php create mode 100644 tests/Integration/LaravelTest.php diff --git a/composer.json b/composer.json index f927fbc..f72340c 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ }, "require-dev": { "illuminate/http": ">=6.20", + "illuminate/support": ">=6.20", "mockery/mockery": "^1.3.4", "orchestra/testbench": ">=7.0", "pestphp/pest": "^2.0", @@ -55,6 +56,11 @@ "extra": { "branch-alias": { "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Cerbero\\LazyJsonPages\\Providers\\LazyJsonPagesServiceProvider" + ] } }, "config": { diff --git a/src/Providers/LazyJsonPagesServiceProvider.php b/src/Providers/LazyJsonPagesServiceProvider.php new file mode 100644 index 0000000..fbf8d7d --- /dev/null +++ b/src/Providers/LazyJsonPagesServiceProvider.php @@ -0,0 +1,52 @@ +sending(...), $this->sent(...))); + } + + /** + * Handle HTTP requests before they are sent. + */ + private function sending(RequestInterface $request): void + { + event(new RequestSending(new Request($request))); + } + + /** + * Handle HTTP requests after they are sent. + */ + private function sent(RequestInterface $request, array $options, PromiseInterface $promise): void + { + $clientRequest = new Request($request); + + $promise->then( + fn(ResponseInterface $response) => event(new ResponseReceived($clientRequest, new Response($response))), + fn() => event(new ConnectionFailed($clientRequest)), + ); + } +} diff --git a/src/Services/Client.php b/src/Services/Client.php index 793aed0..ec2e7f2 100644 --- a/src/Services/Client.php +++ b/src/Services/Client.php @@ -5,6 +5,7 @@ namespace Cerbero\LazyJsonPages\Services; use GuzzleHttp\Client as Guzzle; +use GuzzleHttp\HandlerStack; use GuzzleHttp\RequestOptions; /** @@ -27,9 +28,18 @@ final class Client /** * The custom options. + * + * @var array */ private static array $options = []; + /** + * The client middleware. + * + * @var array + */ + private static array $middleware = []; + /** * The Guzzle client instance. */ @@ -40,9 +50,18 @@ final class Client */ public static function instance(): Guzzle { - return self::$guzzle ??= new Guzzle( - array_replace_recursive(self::$defaultOptions, self::$options), - ); + if (self::$guzzle) { + return self::$guzzle; + } + + $options = array_replace_recursive(self::$defaultOptions, self::$options); + $options['handler'] ??= HandlerStack::create(); + + foreach (self::$middleware as $name => $middleware) { + $options['handler']->push($middleware, $name); + } + + return self::$guzzle = new Guzzle($options); } /** @@ -53,6 +72,14 @@ public static function configure(array $options): void self::$options = array_replace_recursive(self::$options, $options); } + /** + * Set the Guzzle client middleware. + */ + public static function middleware(string $name, callable $middleware): void + { + self::$middleware[$name] = $middleware; + } + /** * Clean up the static values. */ @@ -60,6 +87,7 @@ public static function reset(): void { self::$guzzle = null; self::$options = []; + self::$middleware = []; } /** diff --git a/tests/Integration/LaravelTest.php b/tests/Integration/LaravelTest.php new file mode 100644 index 0000000..7bef011 --- /dev/null +++ b/tests/Integration/LaravelTest.php @@ -0,0 +1,37 @@ +totalPages('meta.total_pages') + ->collect('data.*'); + + expect($lazyCollection)->toLoadItemsViaRequests([ + 'https://example.com/api/v1/users' => 'pagination/page1.json', + 'https://example.com/api/v1/users?page=2' => 'pagination/page2.json', + 'https://example.com/api/v1/users?page=3' => 'pagination/page3.json', + ]); + + Event::assertDispatched(RequestSending::class, 3); + Event::assertDispatched(ResponseReceived::class, 3); +}); + +it('fires Laravel HTTP client events on failure', function() { + Event::fake(); + + $lazyCollection = LazyJsonPages::from('https://example.com/api/v1/users') + ->totalPages('meta.total_pages') + ->collect('data.*'); + + expect($lazyCollection)->toFailRequest('https://example.com/api/v1/users'); + + Event::assertDispatched(RequestSending::class, 1); + Event::assertDispatched(ConnectionFailed::class, 1); +}); diff --git a/tests/Pest.php b/tests/Pest.php index f91e43a..ec36cc9 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,10 +1,14 @@ in('Feature'); +uses(OrchestraTestCase::class, WithWorkbench::class)->in('Integration/LaravelTest.php'); /* |-------------------------------------------------------------------------- @@ -57,6 +61,27 @@ expect($actualUris)->toBe($expectedUris); }); +expect()->extend('toFailRequest', function (string $uri) { + $transactions = []; + + $responses = [$exception = new RequestException('connection failed', new Request('GET', $uri))]; + + $stack = HandlerStack::create(new MockHandler($responses)); + + $stack->push(Middleware::history($transactions)); + + Client::configure(['handler' => $stack]); + + try { + iterator_to_array($this->value); + } catch (Throwable $e) { + expect($e)->toBe($exception); + } + + expect($transactions)->toHaveCount(1); + expect((string) $transactions[0]['request']->getUri())->toBe($uri); +}); + /* |-------------------------------------------------------------------------- | Functions