From e080e96ecd15aea9660c56f2e7b0378e7f513a7b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 15:26:18 +0100 Subject: [PATCH 01/18] require php 8.4 --- .github/workflows/ci.yml | 10 ++++------ CHANGELOG.md | 6 ++++++ composer.json | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fa3b98..e8ff166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,15 +4,13 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next with: scenarii: 20 coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next secrets: inherit psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' + uses: innmind/github-workflows/.github/workflows/cs.yml@next diff --git a/CHANGELOG.md b/CHANGELOG.md index c4449a0..79e9e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Changed + +- Requires PHP `8.4` + ## 8.1.0 - 2025-09-18 ### Added diff --git a/composer.json b/composer.json index 633bdf7..b9e8f93 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "issues": "http://github.com/Innmind/HttpTransport/issues" }, "require": { - "php": "~8.2", + "php": "~8.4", "ext-curl": "*", "innmind/http": "~8.0", "innmind/filesystem": "~8.1", From fd007fc8f2ec500c88db99b1bad495e6acd57e53 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 15:39:45 +0100 Subject: [PATCH 02/18] add internal Implementation interface --- src/CircuitBreaker.php | 6 +++--- src/Curl.php | 2 +- src/ExponentialBackoff.php | 6 +++--- src/FollowRedirections.php | 6 +++--- src/Implementation.php | 21 +++++++++++++++++++++ src/Logger.php | 6 +++--- tests/CircuitBreakerTest.php | 15 ++++++++------- tests/ExponentialBackoffTest.php | 23 ++++++++++++----------- tests/FollowRedirectionsTest.php | 13 +++++++------ 9 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 src/Implementation.php diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index 2ae5bae..b9413a2 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -26,13 +26,13 @@ /** * @psalm-import-type Errors from Transport */ -final class CircuitBreaker implements Transport +final class CircuitBreaker implements Implementation { /** * @param Map $openedCircuits */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private Clock $clock, private Period $delayBeforeRetry, private Map $openedCircuits, @@ -54,7 +54,7 @@ public function __invoke(Request $request): Either } public static function of( - Transport $fulfill, + Implementation $fulfill, Clock $clock, Period $delayBeforeRetry, ): self { diff --git a/src/Curl.php b/src/Curl.php index c4eda29..3d8e1ec 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -22,7 +22,7 @@ /** * @psalm-import-type Errors from Transport */ -final class Curl implements Transport +final class Curl implements Implementation { /** * @param \Closure(): void $heartbeat diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index c1a4f20..b407ee2 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -15,13 +15,13 @@ /** * @psalm-import-type Errors from Transport */ -final class ExponentialBackoff implements Transport +final class ExponentialBackoff implements Implementation { /** * @param Sequence $retries */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private Halt $halt, private Sequence $retries, ) { @@ -33,7 +33,7 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->retries); } - public static function of(Transport $fulfill, Halt $halt): self + public static function of(Implementation $fulfill, Halt $halt): self { /** @psalm-suppress ArgumentTypeCoercion Periods are necessarily positive */ return new self( diff --git a/src/FollowRedirections.php b/src/FollowRedirections.php index f83ca17..450880b 100644 --- a/src/FollowRedirections.php +++ b/src/FollowRedirections.php @@ -18,13 +18,13 @@ /** * @psalm-import-type Errors from Transport */ -final class FollowRedirections implements Transport +final class FollowRedirections implements Implementation { /** * @param int<1, max> $hops */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private int $hops, ) { } @@ -35,7 +35,7 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->hops); } - public static function of(Transport $fulfill): self + public static function of(Implementation $fulfill): self { return new self($fulfill, 5); } diff --git a/src/Implementation.php b/src/Implementation.php new file mode 100644 index 0000000..f432255 --- /dev/null +++ b/src/Implementation.php @@ -0,0 +1,21 @@ + + */ + #[\NoDiscard] + #[\Override] + public function __invoke(Request $request): Either; +} diff --git a/src/Logger.php b/src/Logger.php index 063a798..54bd3d8 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -19,10 +19,10 @@ /** * @psalm-import-type Errors from Transport */ -final class Logger implements Transport +final class Logger implements Implementation { private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private LoggerInterface $logger, ) { } @@ -37,7 +37,7 @@ public function __invoke(Request $request): Either ->leftMap(fn($error) => $this->logError($error, $reference)); } - public static function psr(Transport $fulfill, LoggerInterface $logger): self + public static function psr(Implementation $fulfill, LoggerInterface $logger): self { return new self($fulfill, $logger); } diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 51e38f8..75ad553 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -7,6 +7,7 @@ CircuitBreaker, Curl, Transport, + Implementation, Success, ServerError, ClientError, @@ -56,7 +57,7 @@ public function testDoesntOpenCircuitOnSuccessfulResponse() $expected = Either::right(new Success($request, $response)); $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -89,7 +90,7 @@ public function testDoesntOpenCircuitOnRedirectionResponse() $expected = Either::left(new Redirection($request, $response)); $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -122,7 +123,7 @@ public function testDoesntOpenCircuitOnClientErrorResponse() $expected = Either::left(new ClientError($request, $response)); $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -155,7 +156,7 @@ public function testOpenCircuitOnServerError() $expected = Either::left(new ServerError($request, $response)); $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -189,7 +190,7 @@ public function testOpenCircuitOnConnectionFailure() $expected = Either::left(new ConnectionFailed($request, '')); $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -237,7 +238,7 @@ public function testOpenCircuitOnlyForTheDomainThatFailed() $expected2 = Either::right(new Success($request2, $response2)); $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Transport { + new class([$expected1, $expected2]) implements Implementation { public function __construct( private array $expected, ) { @@ -275,7 +276,7 @@ public function testRecloseTheCircuitAfterTheSpecifiedDelay() $expected2 = Either::right(new Success($request, $response2)); $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Transport { + new class([$expected1, $expected2]) implements Implementation { public function __construct( private array $expected, ) { diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index 363ab98..c897f19 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -7,6 +7,7 @@ ExponentialBackoff, Curl, Transport, + Implementation, ServerError, Success, ClientError, @@ -63,7 +64,7 @@ public function testDoesntRetryWhenInformationResponseOnFirstCall() $expected = Either::left(new Information($request, $response)); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -99,7 +100,7 @@ public function testDoesntRetryWhenSuccessfulResponseOnFirstCall() $expected = Either::right(new Success($request, $response)); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -135,7 +136,7 @@ public function testDoesntRetryWhenRedirectionResponseOnFirstCall() $expected = Either::left(new Redirection($request, $response)); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -171,7 +172,7 @@ public function testDoesntRetryWhenClientErrorResponseOnFirstCall() $expected = Either::left(new ClientError($request, $response)); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -203,7 +204,7 @@ public function testDoesntRetryWhenMalformedResponseOnFirstCall() $expected = Either::left(new MalformedResponse($request)); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -235,7 +236,7 @@ public function testDoesntRetryWhenFailureOnFirstCall() $expected = Either::left(new Failure($request, 'whatever')); $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { + new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -271,7 +272,7 @@ public function testRetryWhileThereIsStillATooManyRequestsError() $expected = Either::left(new ClientError($request, $response)); $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, public int $calls = 0, @@ -329,7 +330,7 @@ public function testRetryWhileThereIsStillAServerError() $expected = Either::left(new ServerError($request, $response)); $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, public int $calls = 0, @@ -383,7 +384,7 @@ public function testRetryWhileThereIsStillAConnectionFailure() $expected = Either::left(new ConnectionFailed($request, '')); $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, public int $calls = 0, @@ -446,7 +447,7 @@ public function testStopRetryingWhenNoLongerReceivingAServerError() $expected = Either::right(new Success($request, $response2)); $fulfill = ExponentialBackoff::of( - $inner = new class([$error, $expected]) implements Transport { + $inner = new class([$error, $expected]) implements Implementation { public function __construct( private $expected, public int $calls = 0, @@ -498,7 +499,7 @@ public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() $expected = Either::left(new ServerError($request, $response)); $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, public int $calls = 0, diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index d010cef..b30d35e 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -7,6 +7,7 @@ FollowRedirections, Curl, Transport, + Implementation, Information, Success, Redirection, @@ -102,7 +103,7 @@ public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof )), )) ->prove(function($result) use ($request) { - $inner = new class($result) implements Transport { + $inner = new class($result) implements Implementation { public function __construct( private $result, ) { @@ -155,7 +156,7 @@ public function testRedirectMaximum5Times(): BlackBox\Proof ), ), )); - $inner = new class($this, $firstUrl, $expected) implements Transport { + $inner = new class($this, $firstUrl, $expected) implements Implementation { public function __construct( private $test, private $firstUrl, @@ -219,7 +220,7 @@ public function testDoesntRedirectWhenNoLocationHeader(): BlackBox\Proof $protocol, ), )); - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, ) { @@ -269,7 +270,7 @@ public function testRedirectSeeOther(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $protocol, $expected) implements Transport { + $inner = new class($this, $start, $newUrl, $protocol, $expected) implements Implementation { public function __construct( private $test, private $start, @@ -361,7 +362,7 @@ public function testRedirect(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $statusCode, $protocol, $expected) implements Transport { + $inner = new class($this, $start, $newUrl, $statusCode, $protocol, $expected) implements Implementation { public function __construct( private $test, private $start, @@ -457,7 +458,7 @@ public function testDoesntRedirectUnsafeMethods(): BlackBox\Proof ), ), )); - $inner = new class($expected) implements Transport { + $inner = new class($expected) implements Implementation { public function __construct( private $expected, ) { From 5bdbea6e30f8e4b372e4e418e304beef1bf52a6b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:06:54 +0100 Subject: [PATCH 03/18] add Transport::map() --- CHANGELOG.md | 5 ++ src/CircuitBreaker.php | 11 ++++ src/Config.php | 102 +++++++++++++++++++++++++++++++ src/Curl.php | 20 ++++++ src/ExponentialBackoff.php | 9 +++ src/FollowRedirections.php | 6 ++ src/Implementation.php | 4 ++ src/Logger.php | 9 +++ src/Transport.php | 3 + tests/CircuitBreakerTest.php | 36 +++++++++++ tests/ExponentialBackoffTest.php | 56 +++++++++++++++++ tests/FollowRedirectionsTest.php | 31 ++++++++++ 12 files changed, 292 insertions(+) create mode 100644 src/Config.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e9e79..80b3146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- `Innmind\HttpTransport\Config` +- `Innmind\HttpTransport\Transport::map()` + ### Changed - Requires PHP `8.4` diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index b9413a2..37eb5ac 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -66,6 +66,17 @@ public static function of( ); } + #[\Override] + public function map(Config $config): self + { + return new self( + $this->fulfill->map($config), + $this->clock, + $this->delayBeforeRetry, + Map::of(), + ); + } + private function open( Request $request, ServerError|ConnectionFailed $error, diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..cbaf124 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,102 @@ +> $maxConcurrency + * @param Maybe $proxy + */ + private function __construct( + private Maybe $maxConcurrency, + private bool $verifySSL, + private Maybe $proxy, + ) { + } + + /** + * @psalm-pure + */ + public static function new(): self + { + /** @var Maybe> */ + $maxConcurrency = Maybe::nothing(); + /** @var Maybe */ + $proxy = Maybe::nothing(); + + return new self( + $maxConcurrency, + true, + $proxy, + ); + } + + /** + * @param int<1, max> $max + */ + #[\NoDiscard] + public function limitConcurrencyTo(int $max): self + { + return new self( + Maybe::just($max), + $this->verifySSL, + $this->proxy, + ); + } + + #[\NoDiscard] + public function disableSSLVerification(): self + { + return new self( + $this->maxConcurrency, + false, + $this->proxy, + ); + } + + #[\NoDiscard] + public function throughProxy(Url $proxy): self + { + return new self( + $this->maxConcurrency, + $this->verifySSL, + Maybe::just($proxy), + ); + } + + /** + * @internal + * + * @return Maybe> + */ + public function maxConcurrency(): Maybe + { + return $this->maxConcurrency; + } + + /** + * @internal + */ + public function verifySSL(): bool + { + return $this->verifySSL; + } + + /** + * @internal + * + * @return Maybe + */ + public function proxy(): Maybe + { + return $this->proxy; + } +} diff --git a/src/Curl.php b/src/Curl.php index 3d8e1ec..bf7c026 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -74,6 +74,26 @@ public static function of( ); } + #[\Override] + public function map(Config $config): self + { + return new self( + $this->headerFactory, + $this->io, + Concurrency::new($config->maxConcurrency()->match( + static fn($max) => $max, + static fn() => null, + )), + $this->timeout, + $this->heartbeat, + !$config->verifySSL(), + $config->proxy()->match( + static fn($proxy) => $proxy, + static fn() => null, + ), + ); + } + /** * @psalm-mutation-free * diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index b407ee2..160c079 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -49,6 +49,15 @@ public static function of(Implementation $fulfill, Halt $halt): self ); } + #[\Override] + public function map(Config $config): self + { + return self::of( + $this->fulfill->map($config), + $this->halt, + ); + } + /** * @param Sequence $retries * diff --git a/src/FollowRedirections.php b/src/FollowRedirections.php index 450880b..3b8a5e9 100644 --- a/src/FollowRedirections.php +++ b/src/FollowRedirections.php @@ -40,6 +40,12 @@ public static function of(Implementation $fulfill): self return new self($fulfill, 5); } + #[\Override] + public function map(Config $config): self + { + return self::of($this->fulfill->map($config)); + } + /** * @param int<0, max> $hops * diff --git a/src/Implementation.php b/src/Implementation.php index f432255..922bd82 100644 --- a/src/Implementation.php +++ b/src/Implementation.php @@ -18,4 +18,8 @@ interface Implementation extends Transport #[\NoDiscard] #[\Override] public function __invoke(Request $request): Either; + + #[\NoDiscard] + #[\Override] + public function map(Config $config): self; } diff --git a/src/Logger.php b/src/Logger.php index 54bd3d8..5c559ab 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -42,6 +42,15 @@ public static function psr(Implementation $fulfill, LoggerInterface $logger): se return new self($fulfill, $logger); } + #[\Override] + public function map(Config $config): self + { + return new self( + $this->fulfill->map($config), + $this->logger, + ); + } + private function logRequest(Request $request): string { $this->logger->debug( diff --git a/src/Transport.php b/src/Transport.php index 5e482fa..8531df2 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -16,4 +16,7 @@ interface Transport */ #[\NoDiscard] public function __invoke(Request $request): Either; + + #[\NoDiscard] + public function map(Config $config): self; } diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 75ad553..11a6ad0 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -13,6 +13,7 @@ ClientError, Redirection, ConnectionFailed, + Config, }; use Innmind\Http\{ Request, @@ -67,6 +68,11 @@ public function __invoke(Request $_): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -100,6 +106,11 @@ public function __invoke(Request $_): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -133,6 +144,11 @@ public function __invoke(Request $_): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -166,6 +182,11 @@ public function __invoke(Request $_): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -200,6 +221,11 @@ public function __invoke(Request $_): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -248,6 +274,11 @@ public function __invoke(Request $_): Either { return \array_shift($this->expected); } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::hour(1), @@ -286,6 +317,11 @@ public function __invoke(Request $_): Either { return \array_shift($this->expected); } + + public function map(Config $config): self + { + return $this; + } }, Clock::live(), Period::millisecond(1), diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index c897f19..11fa23c 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -16,6 +16,7 @@ Information, MalformedResponse, Failure, + Config, }; use Innmind\Http\{ Request, @@ -74,6 +75,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -110,6 +116,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -146,6 +157,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -182,6 +198,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -214,6 +235,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -246,6 +272,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class implements Halt { public function __invoke(Period $period): Attempt @@ -285,6 +316,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class($this) implements Halt { public function __construct( @@ -343,6 +379,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class($this) implements Halt { public function __construct( @@ -397,6 +438,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class($this) implements Halt { public function __construct( @@ -460,6 +506,11 @@ public function __invoke(Request $request): Either return \array_shift($this->expected); } + + public function map(Config $config): self + { + return $this; + } }, new class($this) implements Halt { public function __construct( @@ -512,6 +563,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }, new class($this) implements Halt { public function __construct( diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index b30d35e..c29da18 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -16,6 +16,7 @@ MalformedResponse, ConnectionFailed, Failure, + Config, }; use Innmind\TimeContinuum\Clock; use Innmind\Http\{ @@ -113,6 +114,11 @@ public function __invoke(Request $request): Either { return $this->result; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); @@ -178,6 +184,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); @@ -230,6 +241,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); @@ -315,6 +331,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); @@ -408,6 +429,11 @@ public function __invoke(Request $request): Either return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); @@ -468,6 +494,11 @@ public function __invoke(Request $request): Either { return $this->expected; } + + public function map(Config $config): self + { + return $this; + } }; $fulfill = FollowRedirections::of($inner); From 32ae9fd9036bb9fd37e2e9141212cef5ecb2d909 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:13:27 +0100 Subject: [PATCH 04/18] remove Curl::maxConcurrency() --- CHANGELOG.md | 4 ++++ src/Curl.php | 19 ------------------- tests/CurlTest.php | 7 ++++--- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b3146..d1c0035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - Requires PHP `8.4` +### Removed + +- `Innmind\HttpTransport\Curl::maxConcurrency()` use `::map()` instead + ## 8.1.0 - 2025-09-18 ### Added diff --git a/src/Curl.php b/src/Curl.php index bf7c026..dc2aab3 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -94,25 +94,6 @@ public function map(Config $config): self ); } - /** - * @psalm-mutation-free - * - * @param positive-int $max - */ - #[\NoDiscard] - public function maxConcurrency(int $max): self - { - return new self( - $this->headerFactory, - $this->io, - Concurrency::new($max), - $this->timeout, - $this->heartbeat, - $this->disableSSLVerification, - $this->proxy, - ); - } - /** * @psalm-mutation-free * diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 8f9bc42..5af094e 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -13,6 +13,7 @@ ServerError, Failure, Header\Timeout, + Config, }; use Innmind\Http\{ Request, @@ -365,7 +366,7 @@ public function testConcurrency() public function testMaxConcurrency() { - $curl = $this->curl->maxConcurrency(1); + $curl = $this->curl->map(Config::new()->limitConcurrencyTo(1)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -427,7 +428,7 @@ static function() use (&$heartbeat) { public function testOutOfOrderUnwrapWithMaxConcurrency() { - $curl = $this->curl->maxConcurrency(2); + $curl = $this->curl->map(Config::new()->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -452,7 +453,7 @@ public function testOutOfOrderUnwrapWithMaxConcurrency() public function testSubsequentRequestsAreCalledCorrectlyInsideFlatMaps() { - $curl = $this->curl->maxConcurrency(2); + $curl = $this->curl->map(Config::new()->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, From 997feabcfe0711fde6f382d6e18f4616533483d1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:14:51 +0100 Subject: [PATCH 05/18] remove Curl::disableSSLVerification() --- CHANGELOG.md | 1 + src/Config.php | 4 ++++ src/Curl.php | 20 -------------------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c0035..799f693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Removed - `Innmind\HttpTransport\Curl::maxConcurrency()` use `::map()` instead +- `Innmind\HttpTransport\Curl::disableSSLVerification()` use `::map()` instead ## 8.1.0 - 2025-09-18 diff --git a/src/Config.php b/src/Config.php index cbaf124..2b3b4c0 100644 --- a/src/Config.php +++ b/src/Config.php @@ -52,6 +52,10 @@ public function limitConcurrencyTo(int $max): self ); } + /** + * You should use this method only when trying to call a server you own that + * uses a self signed certificate that will fail the verification. + */ #[\NoDiscard] public function disableSSLVerification(): self { diff --git a/src/Curl.php b/src/Curl.php index dc2aab3..ae93ff5 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -117,26 +117,6 @@ public function heartbeat(Period $timeout, ?callable $heartbeat = null): self ); } - /** - * You should use this method only when trying to call a server you own that - * uses a self signed certificate that will fail the verification. - * - * @psalm-mutation-free - */ - #[\NoDiscard] - public function disableSSLVerification(): self - { - return new self( - $this->headerFactory, - $this->io, - $this->concurrency, - $this->timeout, - $this->heartbeat, - true, - $this->proxy, - ); - } - /** * @psalm-mutation-free */ From 80ff91c9fd57a98c7791070b0b901a651b80d560 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:19:19 +0100 Subject: [PATCH 06/18] remove Curl::proxy() --- CHANGELOG.md | 1 + src/Curl.php | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 799f693..7f9b2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - `Innmind\HttpTransport\Curl::maxConcurrency()` use `::map()` instead - `Innmind\HttpTransport\Curl::disableSSLVerification()` use `::map()` instead +- `Innmind\HttpTransport\Curl::proxy()` use `::map()` instead ## 8.1.0 - 2025-09-18 diff --git a/src/Curl.php b/src/Curl.php index ae93ff5..f3fd47d 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -116,21 +116,4 @@ public function heartbeat(Period $timeout, ?callable $heartbeat = null): self $this->proxy, ); } - - /** - * @psalm-mutation-free - */ - #[\NoDiscard] - public function proxy(Url $proxy): self - { - return new self( - $this->headerFactory, - $this->io, - $this->concurrency, - $this->timeout, - $this->heartbeat, - $this->disableSSLVerification, - $proxy, - ); - } } From 6f75bd4b4b92fb91e1575e7116091603bc5c8066 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:29:55 +0100 Subject: [PATCH 07/18] replace Curl->heartbeat() by Curl::async() --- CHANGELOG.md | 1 + src/Curl.php | 46 +++++++++++++++++++++++----------------------- tests/CurlTest.php | 5 ++++- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9b2ab..9273ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - `Innmind\HttpTransport\Curl::maxConcurrency()` use `::map()` instead - `Innmind\HttpTransport\Curl::disableSSLVerification()` use `::map()` instead - `Innmind\HttpTransport\Curl::proxy()` use `::map()` instead +- `Innmind\HttpTransport\Curl::heartbeat()` (as it was intended for internal use) ## 8.1.0 - 2025-09-18 diff --git a/src/Curl.php b/src/Curl.php index f3fd47d..b8ac2a1 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -74,6 +74,29 @@ public static function of( ); } + /** + * @internal + * + * @param Period $timeout Only seconds are allowed + * @param callable(): void $heartbeat + */ + public static function async( + Clock $clock, + IO $io, + Period $timeout, + callable $heartbeat, + ): self { + return new self( + Factory::new($clock), + $io, + Concurrency::new(), + $timeout, + \Closure::fromCallable($heartbeat), + false, + null, + ); + } + #[\Override] public function map(Config $config): self { @@ -93,27 +116,4 @@ public function map(Config $config): self ), ); } - - /** - * @psalm-mutation-free - * - * @param Period $timeout Only seconds are allowed - * @param callable(): void $heartbeat - */ - #[\NoDiscard] - public function heartbeat(Period $timeout, ?callable $heartbeat = null): self - { - return new self( - $this->headerFactory, - $this->io, - $this->concurrency, - $timeout, - match ($heartbeat) { - null => static fn() => null, - default => \Closure::fromCallable($heartbeat), - }, - $this->disableSSLVerification, - $this->proxy, - ); - } } diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 5af094e..4ed53d2 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -29,6 +29,7 @@ File\Content, Name, }; +use Innmind\IO\IO; use Innmind\TimeContinuum\{ Clock, Period, @@ -407,7 +408,9 @@ public function testMaxConcurrency() public function testHeartbeat() { $heartbeat = 0; - $curl = $this->curl->heartbeat( + $curl = Curl::async( + Clock::live(), + IO::fromAmbientAuthority(), Period::second(1), static function() use (&$heartbeat) { ++$heartbeat; From a898684a9fe009e1a556596e5c22ced65a7911dd Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 16:41:48 +0100 Subject: [PATCH 08/18] fix Transport::map() API to allow to transparently reuse previous config --- src/CircuitBreaker.php | 7 +++++-- src/Curl.php | 12 +++++++++++- src/ExponentialBackoff.php | 12 ++++++++++-- src/FollowRedirections.php | 12 ++++++++++-- src/Implementation.php | 7 ++++++- src/Logger.php | 7 +++++-- src/Transport.php | 7 ++++++- tests/CircuitBreakerTest.php | 15 +++++++-------- tests/CurlTest.php | 7 +++---- tests/ExponentialBackoffTest.php | 23 +++++++++++------------ tests/FollowRedirectionsTest.php | 13 ++++++------- 11 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index 37eb5ac..efdf266 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -66,11 +66,14 @@ public static function of( ); } + /** + * @psalm-mutation-free + */ #[\Override] - public function map(Config $config): self + public function map(callable $map): self { return new self( - $this->fulfill->map($config), + $this->fulfill->map($map), $this->clock, $this->delayBeforeRetry, Map::of(), diff --git a/src/Curl.php b/src/Curl.php index b8ac2a1..a1538a3 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -28,6 +28,7 @@ final class Curl implements Implementation * @param \Closure(): void $heartbeat */ private function __construct( + private Config $config, private Factory $headerFactory, private IO $io, private Concurrency $concurrency, @@ -64,6 +65,7 @@ public static function of( $io ??= IO::fromAmbientAuthority(); return new self( + Config::new(), Factory::new($clock), $io, Concurrency::new(), @@ -87,6 +89,7 @@ public static function async( callable $heartbeat, ): self { return new self( + Config::new(), Factory::new($clock), $io, Concurrency::new(), @@ -97,10 +100,17 @@ public static function async( ); } + /** + * @psalm-mutation-free + */ #[\Override] - public function map(Config $config): self + public function map(callable $map): self { + /** @psalm-suppress ImpureFunctionCall */ + $config = $map($this->config); + return new self( + $config, $this->headerFactory, $this->io, Concurrency::new($config->maxConcurrency()->match( diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index 160c079..ee53fd4 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -18,6 +18,8 @@ final class ExponentialBackoff implements Implementation { /** + * @psalm-mutation-free + * * @param Sequence $retries */ private function __construct( @@ -33,6 +35,9 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->retries); } + /** + * @psalm-pure + */ public static function of(Implementation $fulfill, Halt $halt): self { /** @psalm-suppress ArgumentTypeCoercion Periods are necessarily positive */ @@ -49,11 +54,14 @@ public static function of(Implementation $fulfill, Halt $halt): self ); } + /** + * @psalm-mutation-free + */ #[\Override] - public function map(Config $config): self + public function map(callable $map): self { return self::of( - $this->fulfill->map($config), + $this->fulfill->map($map), $this->halt, ); } diff --git a/src/FollowRedirections.php b/src/FollowRedirections.php index 3b8a5e9..63fcf85 100644 --- a/src/FollowRedirections.php +++ b/src/FollowRedirections.php @@ -21,6 +21,8 @@ final class FollowRedirections implements Implementation { /** + * @psalm-mutation-free + * * @param int<1, max> $hops */ private function __construct( @@ -35,15 +37,21 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->hops); } + /** + * @psalm-pure + */ public static function of(Implementation $fulfill): self { return new self($fulfill, 5); } + /** + * @psalm-mutation-free + */ #[\Override] - public function map(Config $config): self + public function map(callable $map): self { - return self::of($this->fulfill->map($config)); + return self::of($this->fulfill->map($map)); } /** diff --git a/src/Implementation.php b/src/Implementation.php index 922bd82..a15ef1f 100644 --- a/src/Implementation.php +++ b/src/Implementation.php @@ -19,7 +19,12 @@ interface Implementation extends Transport #[\Override] public function __invoke(Request $request): Either; + /** + * @psalm-mutation-free + * + * @param callable(Config): Config $map + */ #[\NoDiscard] #[\Override] - public function map(Config $config): self; + public function map(callable $map): self; } diff --git a/src/Logger.php b/src/Logger.php index 5c559ab..d0a85e7 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -42,11 +42,14 @@ public static function psr(Implementation $fulfill, LoggerInterface $logger): se return new self($fulfill, $logger); } + /** + * @psalm-mutation-free + */ #[\Override] - public function map(Config $config): self + public function map(callable $map): self { return new self( - $this->fulfill->map($config), + $this->fulfill->map($map), $this->logger, ); } diff --git a/src/Transport.php b/src/Transport.php index 8531df2..25a5a2d 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -17,6 +17,11 @@ interface Transport #[\NoDiscard] public function __invoke(Request $request): Either; + /** + * @psalm-mutation-free + * + * @param callable(Config): Config $map + */ #[\NoDiscard] - public function map(Config $config): self; + public function map(callable $map): self; } diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 11a6ad0..50e8ada 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -13,7 +13,6 @@ ClientError, Redirection, ConnectionFailed, - Config, }; use Innmind\Http\{ Request, @@ -69,7 +68,7 @@ public function __invoke(Request $_): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -107,7 +106,7 @@ public function __invoke(Request $_): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -145,7 +144,7 @@ public function __invoke(Request $_): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -183,7 +182,7 @@ public function __invoke(Request $_): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -222,7 +221,7 @@ public function __invoke(Request $_): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -275,7 +274,7 @@ public function __invoke(Request $_): Either return \array_shift($this->expected); } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -318,7 +317,7 @@ public function __invoke(Request $_): Either return \array_shift($this->expected); } - public function map(Config $config): self + public function map(callable $map): self { return $this; } diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 4ed53d2..678517c 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -13,7 +13,6 @@ ServerError, Failure, Header\Timeout, - Config, }; use Innmind\Http\{ Request, @@ -367,7 +366,7 @@ public function testConcurrency() public function testMaxConcurrency() { - $curl = $this->curl->map(Config::new()->limitConcurrencyTo(1)); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(1)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -431,7 +430,7 @@ static function() use (&$heartbeat) { public function testOutOfOrderUnwrapWithMaxConcurrency() { - $curl = $this->curl->map(Config::new()->limitConcurrencyTo(2)); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -456,7 +455,7 @@ public function testOutOfOrderUnwrapWithMaxConcurrency() public function testSubsequentRequestsAreCalledCorrectlyInsideFlatMaps() { - $curl = $this->curl->map(Config::new()->limitConcurrencyTo(2)); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index 11fa23c..ef63909 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -16,7 +16,6 @@ Information, MalformedResponse, Failure, - Config, }; use Innmind\Http\{ Request, @@ -76,7 +75,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -117,7 +116,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -158,7 +157,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -199,7 +198,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -236,7 +235,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -273,7 +272,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -317,7 +316,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -380,7 +379,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -439,7 +438,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -507,7 +506,7 @@ public function __invoke(Request $request): Either return \array_shift($this->expected); } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -564,7 +563,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index c29da18..d0648ab 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -16,7 +16,6 @@ MalformedResponse, ConnectionFailed, Failure, - Config, }; use Innmind\TimeContinuum\Clock; use Innmind\Http\{ @@ -115,7 +114,7 @@ public function __invoke(Request $request): Either return $this->result; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -185,7 +184,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -242,7 +241,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -332,7 +331,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -430,7 +429,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } @@ -495,7 +494,7 @@ public function __invoke(Request $request): Either return $this->expected; } - public function map(Config $config): self + public function map(callable $map): self { return $this; } From 07cd690f2733b32ac6a9e5c44c8af88adf9600cb Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 17:06:57 +0100 Subject: [PATCH 09/18] make Transport a final class --- CHANGELOG.md | 1 + src/CircuitBreaker.php | 3 +- src/Curl.php | 3 +- src/ExponentialBackoff.php | 3 +- src/FollowRedirections.php | 3 +- src/Implementation.php | 4 +- src/Logger.php | 3 +- src/Transport.php | 87 ++++++++++++++++++++++++++++++-- tests/CircuitBreakerTest.php | 14 ----- tests/CurlTest.php | 9 ---- tests/ExponentialBackoffTest.php | 18 +------ tests/FollowRedirectionsTest.php | 11 ---- tests/LoggerTest.php | 9 ---- 13 files changed, 97 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9273ca8..6e15f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Changed - Requires PHP `8.4` +- `Innmind\HttpTransport\Transport` is now a final class, all previous implementations are now flagged as internal ### Removed diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index efdf266..e93dddb 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -24,7 +24,8 @@ }; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ final class CircuitBreaker implements Implementation { diff --git a/src/Curl.php b/src/Curl.php index a1538a3..c891018 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -20,7 +20,8 @@ use Innmind\Immutable\Either; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ final class Curl implements Implementation { diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index ee53fd4..d631450 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -13,7 +13,8 @@ }; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ final class ExponentialBackoff implements Implementation { diff --git a/src/FollowRedirections.php b/src/FollowRedirections.php index 63fcf85..75860b9 100644 --- a/src/FollowRedirections.php +++ b/src/FollowRedirections.php @@ -16,7 +16,8 @@ use Innmind\Immutable\Either; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ final class FollowRedirections implements Implementation { diff --git a/src/Implementation.php b/src/Implementation.php index a15ef1f..04d5980 100644 --- a/src/Implementation.php +++ b/src/Implementation.php @@ -10,13 +10,12 @@ * @internal * @psalm-type Errors = Failure|ConnectionFailed|MalformedResponse|Information|Redirection|ClientError|ServerError */ -interface Implementation extends Transport +interface Implementation { /** * @return Either */ #[\NoDiscard] - #[\Override] public function __invoke(Request $request): Either; /** @@ -25,6 +24,5 @@ public function __invoke(Request $request): Either; * @param callable(Config): Config $map */ #[\NoDiscard] - #[\Override] public function map(callable $map): self; } diff --git a/src/Logger.php b/src/Logger.php index d0a85e7..7f06ce8 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -17,7 +17,8 @@ use Ramsey\Uuid\Uuid; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ final class Logger implements Implementation { diff --git a/src/Transport.php b/src/Transport.php index 25a5a2d..aca34bb 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -3,19 +3,97 @@ namespace Innmind\HttpTransport; +use Innmind\TimeWarp\Halt; use Innmind\Http\Request; +use Innmind\IO\IO; +use Innmind\TimeContinuum\{ + Clock, + Period, +}; use Innmind\Immutable\Either; +use Psr\Log\LoggerInterface; /** * @psalm-type Errors = Failure|ConnectionFailed|MalformedResponse|Information|Redirection|ClientError|ServerError */ -interface Transport +final class Transport { + private function __construct( + private Implementation $implementation, + ) { + } + /** * @return Either */ #[\NoDiscard] - public function __invoke(Request $request): Either; + public function __invoke(Request $request): Either + { + return ($this->implementation)($request); + } + + public static function curl( + Clock $clock, + ?IO $io = null, + ): self { + return new self(Curl::of($clock, $io)); + } + + public static function circuitBreaker( + self $transport, + Clock $clock, + Period $delayBeforeRetry, + ): self { + return new self(CircuitBreaker::of( + $transport->implementation, + $clock, + $delayBeforeRetry, + )); + } + + public static function exponentialBackoff( + self $transport, + Halt $halt, + ): self { + return new self(ExponentialBackoff::of( + $transport->implementation, + $halt, + )); + } + + public static function followRedirections(self $transport): self + { + return new self(FollowRedirections::of($transport->implementation)); + } + + public static function logger( + self $transport, + LoggerInterface $logger, + ): self { + return new self(Logger::psr( + $transport->implementation, + $logger, + )); + } + + /** + * @internal + * + * @param callable(): void $heartbeat + */ + public static function async( + Clock $clock, + IO $io, + Period $timeout, + callable $heartbeat, + ): self { + return new self(Curl::async( + $clock, + $io, + $timeout, + $heartbeat, + )); + } /** * @psalm-mutation-free @@ -23,5 +101,8 @@ public function __invoke(Request $request): Either; * @param callable(Config): Config $map */ #[\NoDiscard] - public function map(callable $map): self; + public function map(callable $map): self + { + return new self($this->implementation->map($map)); + } } diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 50e8ada..8cb62d7 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -5,8 +5,6 @@ use Innmind\HttpTransport\{ CircuitBreaker, - Curl, - Transport, Implementation, Success, ServerError, @@ -31,18 +29,6 @@ class CircuitBreakerTest extends TestCase { - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - CircuitBreaker::of( - Curl::of(Clock::live()), - Clock::live(), - Period::millisecond(1), - ), - ); - } - public function testDoesntOpenCircuitOnSuccessfulResponse() { $request = Request::of( diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 678517c..42bb945 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -5,7 +5,6 @@ use Innmind\HttpTransport\{ Curl, - Transport, Success, Redirection, ClientError, @@ -59,14 +58,6 @@ public function setUp(): void $this->curl = Curl::of(Clock::live()); } - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - $this->curl, - ); - } - public function testOkResponse() { $success = ($this->curl)(Request::of( diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index ef63909..e8c267c 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -5,8 +5,6 @@ use Innmind\HttpTransport\{ ExponentialBackoff, - Curl, - Transport, Implementation, ServerError, Success, @@ -25,10 +23,7 @@ Response\StatusCode, }; use Innmind\TimeWarp\Halt; -use Innmind\TimeContinuum\{ - Clock, - Period, -}; +use Innmind\TimeContinuum\Period; use Innmind\Url\Url; use Innmind\Immutable\{ Either, @@ -39,17 +34,6 @@ class ExponentialBackoffTest extends TestCase { - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - ExponentialBackoff::of( - Curl::of(Clock::live()), - Halt\Usleep::new(), - ), - ); - } - public function testDoesntRetryWhenInformationResponseOnFirstCall() { $request = Request::of( diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index d0648ab..d551e39 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -5,8 +5,6 @@ use Innmind\HttpTransport\{ FollowRedirections, - Curl, - Transport, Implementation, Information, Success, @@ -17,7 +15,6 @@ ConnectionFailed, Failure, }; -use Innmind\TimeContinuum\Clock; use Innmind\Http\{ Request, Response, @@ -44,14 +41,6 @@ class FollowRedirectionsTest extends TestCase { use BlackBox; - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - FollowRedirections::of(Curl::of(Clock::live())), - ); - } - public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof { $request = Request::of( diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 0a22ceb..52e3120 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -6,7 +6,6 @@ use Innmind\HttpTransport\{ Logger, Curl, - Transport, Success, }; use Innmind\TimeContinuum\Clock; @@ -34,14 +33,6 @@ public function setUp(): void ); } - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - $this->fulfill, - ); - } - public function testFulfill() { $request = Request::of( From 3b4dbefe387f8c542c950332b4174aa32abeb24c Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 17:37:33 +0100 Subject: [PATCH 10/18] add Transport::via() --- src/Transport.php | 10 ++ src/Via.php | 46 ++++++ tests/CircuitBreakerTest.php | 142 +++------------- tests/CurlTest.php | 6 +- tests/ExponentialBackoffTest.php | 253 ++++++----------------------- tests/FollowRedirectionsTest.php | 267 ++++++++++--------------------- tests/LoggerTest.php | 7 +- 7 files changed, 217 insertions(+), 514 deletions(-) create mode 100644 src/Via.php diff --git a/src/Transport.php b/src/Transport.php index aca34bb..2f830d2 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -95,6 +95,16 @@ public static function async( )); } + /** + * @internal + * + * @param callable(Request): Either $via + */ + public static function via(callable $via): self + { + return new self(Via::of($via)); + } + /** * @psalm-mutation-free * diff --git a/src/Via.php b/src/Via.php new file mode 100644 index 0000000..f4f9a10 --- /dev/null +++ b/src/Via.php @@ -0,0 +1,46 @@ + $fulfill + */ + private function __construct( + private \Closure $fulfill, + ) { + } + + #[\Override] + public function __invoke(Request $request): Either + { + return ($this->fulfill)($request); + } + + /** + * @param callable(Request): Either $fulfill + */ + public static function of(callable $fulfill): self + { + // todo support exposing a Config to the callable ? + return new self(\Closure::fromCallable($fulfill)); + } + + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return $this; + } +} diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 8cb62d7..67388d8 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -4,8 +4,7 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - CircuitBreaker, - Implementation, + Transport, Success, ServerError, ClientError, @@ -42,23 +41,8 @@ public function testDoesntOpenCircuitOnSuccessfulResponse() ); $expected = Either::right(new Success($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -80,23 +64,8 @@ public function testDoesntOpenCircuitOnRedirectionResponse() ); $expected = Either::left(new Redirection($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -118,23 +87,8 @@ public function testDoesntOpenCircuitOnClientErrorResponse() ); $expected = Either::left(new ClientError($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -156,23 +110,8 @@ public function testOpenCircuitOnServerError() ); $expected = Either::left(new ServerError($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -195,23 +134,8 @@ public function testOpenCircuitOnConnectionFailure() ); $expected = Either::left(new ConnectionFailed($request, '')); - $fulfill = CircuitBreaker::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -247,24 +171,12 @@ public function testOpenCircuitOnlyForTheDomainThatFailed() ); $expected1 = Either::left(new ServerError($request1, $response1)); $expected2 = Either::right(new Success($request2, $response2)); + $expected = [$expected1, $expected2]; - $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Implementation { - public function __construct( - private array $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return \array_shift($this->expected); - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static function() use (&$expected) { + return \array_shift($expected); + }), Clock::live(), Period::hour(1), ); @@ -290,24 +202,12 @@ public function testRecloseTheCircuitAfterTheSpecifiedDelay() ); $expected1 = Either::left(new ServerError($request, $response1)); $expected2 = Either::right(new Success($request, $response2)); + $expected = [$expected1, $expected2]; - $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Implementation { - public function __construct( - private array $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return \array_shift($this->expected); - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static function() use (&$expected) { + return \array_shift($expected); + }), Clock::live(), Period::millisecond(1), ); diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 42bb945..7c3bc39 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -4,7 +4,7 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - Curl, + Transport, Success, Redirection, ClientError, @@ -55,7 +55,7 @@ class CurlTest extends TestCase public function setUp(): void { - $this->curl = Curl::of(Clock::live()); + $this->curl = Transport::curl(Clock::live()); } public function testOkResponse() @@ -398,7 +398,7 @@ public function testMaxConcurrency() public function testHeartbeat() { $heartbeat = 0; - $curl = Curl::async( + $curl = Transport::async( Clock::live(), IO::fromAmbientAuthority(), Period::second(1), diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index e8c267c..adad8d5 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -4,8 +4,7 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - ExponentialBackoff, - Implementation, + Transport, ServerError, Success, ClientError, @@ -47,23 +46,8 @@ public function testDoesntRetryWhenInformationResponseOnFirstCall() ); $expected = Either::left(new Information($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -88,23 +72,8 @@ public function testDoesntRetryWhenSuccessfulResponseOnFirstCall() ); $expected = Either::right(new Success($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -129,23 +98,8 @@ public function testDoesntRetryWhenRedirectionResponseOnFirstCall() ); $expected = Either::left(new Redirection($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -170,23 +124,8 @@ public function testDoesntRetryWhenClientErrorResponseOnFirstCall() ); $expected = Either::left(new ClientError($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -207,23 +146,8 @@ public function testDoesntRetryWhenMalformedResponseOnFirstCall() ); $expected = Either::left(new MalformedResponse($request)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -244,23 +168,8 @@ public function testDoesntRetryWhenFailureOnFirstCall() ); $expected = Either::left(new Failure($request, 'whatever')); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), new class implements Halt { public function __invoke(Period $period): Attempt { @@ -284,27 +193,14 @@ public function testRetryWhileThereIsStillATooManyRequestsError() $request->protocolVersion(), ); $expected = Either::left(new ClientError($request, $response)); + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + return $expected; + }), new class($this) implements Halt { public function __construct( private $test, @@ -332,7 +228,7 @@ public function __invoke(Period $period): Attempt $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testRetryWhileThereIsStillAServerError() @@ -347,27 +243,14 @@ public function testRetryWhileThereIsStillAServerError() $request->protocolVersion(), ); $expected = Either::left(new ServerError($request, $response)); + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + return $expected; + }), new class($this) implements Halt { public function __construct( private $test, @@ -395,7 +278,7 @@ public function __invoke(Period $period): Attempt $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testRetryWhileThereIsStillAConnectionFailure() @@ -406,27 +289,14 @@ public function testRetryWhileThereIsStillAConnectionFailure() ProtocolVersion::v11, ); $expected = Either::left(new ConnectionFailed($request, '')); + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }, + return $expected; + }), new class($this) implements Halt { public function __construct( private $test, @@ -454,7 +324,7 @@ public function __invoke(Period $period): Attempt $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testStopRetryingWhenNoLongerReceivingAServerError() @@ -474,27 +344,15 @@ public function testStopRetryingWhenNoLongerReceivingAServerError() ); $error = Either::left(new ServerError($request, $response1)); $expected = Either::right(new Success($request, $response2)); + $all = [$error, $expected]; + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class([$error, $expected]) implements Implementation { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return \array_shift($this->expected); - } + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, &$all) { + ++$calls; - public function map(callable $map): self - { - return $this; - } - }, + return \array_shift($all); + }), new class($this) implements Halt { public function __construct( private $test, @@ -516,7 +374,7 @@ public function __invoke(Period $period): Attempt ); $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); } public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() @@ -531,27 +389,14 @@ public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() $request->protocolVersion(), ); $expected = Either::left(new ServerError($request, $response)); + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; - public function map(callable $map): self - { - return $this; - } - }, + return $expected; + }), new class($this) implements Halt { public function __construct( private $test, @@ -577,6 +422,6 @@ public function __invoke(Period $period): Attempt ); $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(6, $inner->calls); + $this->assertSame(6, $calls); } } diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index d551e39..53c0585 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -4,8 +4,7 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - FollowRedirections, - Implementation, + Transport, Information, Success, Redirection, @@ -92,23 +91,8 @@ public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof )), )) ->prove(function($result) use ($request) { - $inner = new class($result) implements Implementation { - public function __construct( - private $result, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->result; - } - - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $result); + $fulfill = Transport::followRedirections($inner); $this->assertEquals($result, $fulfill($request)); }); @@ -150,40 +134,24 @@ public function testRedirectMaximum5Times(): BlackBox\Proof ), ), )); - $inner = new class($this, $firstUrl, $expected) implements Implementation { - public function __construct( - private $test, - private $firstUrl, - private $expected, - public int $calls = 0, - ) { + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $firstUrl, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($firstUrl, $request->url()); + } else { + $this->assertNotSame($firstUrl, $request->url()); } - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->firstUrl) { - $this->test->assertSame($this->firstUrl, $request->url()); - $this->firstUrl = null; - } else { - $this->test->assertNotSame($this->firstUrl, $request->url()); - } - - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(6, $inner->calls); + $this->assertSame(6, $calls); }); } @@ -219,23 +187,8 @@ public function testDoesntRedirectWhenNoLocationHeader(): BlackBox\Proof $protocol, ), )); - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $expected); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); @@ -274,63 +227,46 @@ public function testRedirectSeeOther(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $protocol, $expected) implements Implementation { - public function __construct( - private $test, - private $start, - private $newUrl, - private $protocol, - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->calls === 1) { - $this->test->assertSame($this->start, $request); - - return Either::left(new Redirection( - $this->start, - Response::of( - StatusCode::seeOther, - $this->protocol, - Headers::of( - Location::of($this->newUrl), - ), + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $start, $newUrl, $protocol, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($start, $request); + + return Either::left(new Redirection( + $start, + Response::of( + StatusCode::seeOther, + $protocol, + Headers::of( + Location::of($newUrl), ), - )); - } - - $this->test->assertSame(Method::get, $request->method()); - $this->test->assertFalse($request->url()->authority()->equals(Authority::none())); - $this->test->assertTrue($request->url()->path()->absolute()); - // not a direct comparison as new url might be a relative path - $this->test->assertStringEndsWith( - $this->newUrl->path()->toString(), - $request->url()->path()->toString(), - ); - $this->test->assertSame($this->newUrl->query(), $request->url()->query()); - $this->test->assertSame($this->newUrl->fragment(), $request->url()->fragment()); - $this->test->assertSame($this->start->headers(), $request->headers()); - $this->test->assertSame('', $request->body()->toString()); - - return $this->expected; + ), + )); } - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + $this->assertSame(Method::get, $request->method()); + $this->assertFalse($request->url()->authority()->equals(Authority::none())); + $this->assertTrue($request->url()->path()->absolute()); + // not a direct comparison as new url might be a relative path + $this->assertStringEndsWith( + $newUrl->path()->toString(), + $request->url()->path()->toString(), + ); + $this->assertSame($newUrl->query(), $request->url()->query()); + $this->assertSame($newUrl->fragment(), $request->url()->fragment()); + $this->assertSame($start->headers(), $request->headers()); + $this->assertSame('', $request->body()->toString()); + + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); }); } @@ -371,64 +307,46 @@ public function testRedirect(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $statusCode, $protocol, $expected) implements Implementation { - public function __construct( - private $test, - private $start, - private $newUrl, - private $statusCode, - private $protocol, - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->calls === 1) { - $this->test->assertSame($this->start, $request); - - return Either::left(new Redirection( - $this->start, - Response::of( - $this->statusCode, - $this->protocol, - Headers::of( - Location::of($this->newUrl), - ), + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $start, $newUrl, $statusCode, $protocol, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($start, $request); + + return Either::left(new Redirection( + $start, + Response::of( + $statusCode, + $protocol, + Headers::of( + Location::of($newUrl), ), - )); - } - - $this->test->assertSame($this->start->method(), $request->method()); - $this->test->assertFalse($request->url()->authority()->equals(Authority::none())); - $this->test->assertTrue($request->url()->path()->absolute()); - // not a direct comparison as new url might be a relative path - $this->test->assertStringEndsWith( - $this->newUrl->path()->toString(), - $request->url()->path()->toString(), - ); - $this->test->assertSame($this->newUrl->query(), $request->url()->query()); - $this->test->assertSame($this->newUrl->fragment(), $request->url()->fragment()); - $this->test->assertSame($this->start->headers(), $request->headers()); - $this->test->assertSame($this->start->body(), $request->body()); - - return $this->expected; + ), + )); } - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + $this->assertSame($start->method(), $request->method()); + $this->assertFalse($request->url()->authority()->equals(Authority::none())); + $this->assertTrue($request->url()->path()->absolute()); + // not a direct comparison as new url might be a relative path + $this->assertStringEndsWith( + $newUrl->path()->toString(), + $request->url()->path()->toString(), + ); + $this->assertSame($newUrl->query(), $request->url()->query()); + $this->assertSame($newUrl->fragment(), $request->url()->fragment()); + $this->assertSame($start->headers(), $request->headers()); + $this->assertSame($start->body(), $request->body()); + + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); }); } @@ -472,23 +390,8 @@ public function testDoesntRedirectUnsafeMethods(): BlackBox\Proof ), ), )); - $inner = new class($expected) implements Implementation { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - - public function map(callable $map): self - { - return $this; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $expected); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 52e3120..c177e42 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -4,8 +4,7 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - Logger, - Curl, + Transport, Success, }; use Innmind\TimeContinuum\Clock; @@ -27,8 +26,8 @@ class LoggerTest extends TestCase public function setUp(): void { - $this->fulfill = Logger::psr( - Curl::of(Clock::live()), + $this->fulfill = Transport::logger( + Transport::curl(Clock::live()), new NullLogger, ); } From 877046ce88be4ae8a11aef0cbe2d38fbd19b61cf Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 17:51:06 +0100 Subject: [PATCH 11/18] update dependencies --- composer.json | 17 ++- tests/CurlTest.php | 4 +- tests/ExponentialBackoffTest.php | 214 +++++++++++-------------------- 3 files changed, 84 insertions(+), 151 deletions(-) diff --git a/composer.json b/composer.json index b9e8f93..c54e72f 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,18 @@ "require": { "php": "~8.4", "ext-curl": "*", - "innmind/http": "~8.0", - "innmind/filesystem": "~8.1", + "innmind/http": "dev-next", + "innmind/filesystem": "dev-next", "psr/log": "~3.0", "ramsey/uuid": "^4.7", - "innmind/time-warp": "~4.0", - "innmind/time-continuum": "^4.1.1", - "innmind/immutable": "~5.14", - "innmind/url": "~4.0", - "innmind/io": "~3.2" + "innmind/time-warp": "dev-next", + "innmind/time-continuum": "dev-next", + "innmind/immutable": "dev-next", + "innmind/url": "dev-next", + "innmind/io": "dev-next", + "innmind/media-type": "dev-next", + "innmind/validation": "dev-next", + "innmind/ip": "dev-next" }, "autoload": { "psr-4": { diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 7c3bc39..fb99468 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -23,7 +23,7 @@ Header\Location, }; use Innmind\Filesystem\{ - Adapter\Filesystem, + Adapter, File\Content, Name, }; @@ -277,7 +277,7 @@ public function testPostLargeContent() $this ->assert() ->memory(function() { - $data = Filesystem::mount(Path::of(__DIR__.'/../data/')); + $data = Adapter::mount(Path::of(__DIR__.'/../data/'))->unwrap(); $memory = \memory_get_peak_usage(); $success = ($this->curl)(Request::of( diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index adad8d5..3755645 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -48,12 +48,7 @@ public function testDoesntRetryWhenInformationResponseOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -74,12 +69,7 @@ public function testDoesntRetryWhenSuccessfulResponseOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -100,12 +90,7 @@ public function testDoesntRetryWhenRedirectionResponseOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -126,12 +111,7 @@ public function testDoesntRetryWhenClientErrorResponseOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -148,12 +128,7 @@ public function testDoesntRetryWhenMalformedResponseOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -170,12 +145,7 @@ public function testDoesntRetryWhenFailureOnFirstCall() $fulfill = Transport::exponentialBackoff( Transport::via(static fn() => $expected), - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -201,28 +171,20 @@ public function testRetryWhileThereIsStillATooManyRequestsError() return $expected; }), - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); @@ -251,28 +213,20 @@ public function testRetryWhileThereIsStillAServerError() return $expected; }), - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); @@ -297,28 +251,20 @@ public function testRetryWhileThereIsStillAConnectionFailure() return $expected; }), - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); @@ -353,24 +299,16 @@ public function testStopRetryingWhenNoLongerReceivingAServerError() return \array_shift($all); }), - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1 => $this->test->assertEquals(Period::millisecond(100), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1 => $this->assertEquals(Period::millisecond(100), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); @@ -397,28 +335,20 @@ public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() return $expected; }), - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1 => $this->test->assertEquals(Period::millisecond(100), $period), - 2 => $this->test->assertEquals(Period::millisecond(271), $period), - 3 => $this->test->assertEquals(Period::millisecond(738), $period), - 4 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1=> $this->assertEquals(Period::millisecond(100), $period), + 2=> $this->assertEquals(Period::millisecond(271), $period), + 3=> $this->assertEquals(Period::millisecond(738), $period), + 4=> $this->assertEquals(Period::millisecond(2008), $period), + 5=> $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); From c3f45e0a019ae748b92b2748ac1e7dae4fff7e3b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 17:53:41 +0100 Subject: [PATCH 12/18] remove use of deprecated code --- composer.json | 2 +- tests/CurlTest.php | 2 +- tests/FollowRedirectionsTest.php | 36 ++++++++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index c54e72f..79e5dc4 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,6 @@ "require-dev": { "innmind/static-analysis": "^1.2.1", "innmind/coding-standard": "~2.0", - "innmind/black-box": "~6.2" + "innmind/black-box": "~6.5" } } diff --git a/tests/CurlTest.php b/tests/CurlTest.php index fb99468..c9c68d7 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -230,7 +230,7 @@ public function testHead() public function testPost(): BlackBox\Proof { return $this - ->forAll(Set\Unicode::strings()) + ->forAll(Set::strings()->unicode()) ->prove(function($body) { $success = ($this->curl)(Request::of( Url::of('https://httpbin.org/post'), diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index 53c0585..6bb8afa 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -49,7 +49,7 @@ public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof ); return $this - ->forAll(Set\Elements::of( + ->forAll(Set::of( Either::right(new Success( $request, Response::of( @@ -104,15 +104,15 @@ public function testRedirectMaximum5Times(): BlackBox\Proof ->forAll( FUrl::any(), FUrl::any(), - Set\Elements::of(Method::get, Method::head), // unsafe methods are not redirected - Set\Elements::of( + Set::of(Method::get, Method::head), // unsafe methods are not redirected + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::seeOther, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, @@ -160,15 +160,15 @@ public function testDoesntRedirectWhenNoLocationHeader(): BlackBox\Proof return $this ->forAll( FUrl::any(), - Set\Elements::of(...Method::cases()), - Set\Elements::of( + Set::of(...Method::cases()), + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::seeOther, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, @@ -204,13 +204,13 @@ public function testRedirectSeeOther(): BlackBox\Proof ->filter(static fn($url) => !$url->authority()->equals(Authority::none())) ->filter(static fn($url) => $url->path()->absolute()), FUrl::any(), - Set\Elements::of(...Method::cases()), - Set\Elements::of( + Set::of(...Method::cases()), + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $protocol, $body) { $start = Request::of( @@ -278,19 +278,19 @@ public function testRedirect(): BlackBox\Proof ->filter(static fn($url) => !$url->authority()->equals(Authority::none())) ->filter(static fn($url) => $url->path()->absolute()), FUrl::any(), - Set\Elements::of(Method::get, Method::head), // unsafe methods are not redirected - Set\Elements::of( + Set::of(Method::get, Method::head), // unsafe methods are not redirected + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $statusCode, $protocol, $body) { $start = Request::of( @@ -356,21 +356,21 @@ public function testDoesntRedirectUnsafeMethods(): BlackBox\Proof ->forAll( FUrl::any(), FUrl::any(), - Set\Elements::of(...Method::cases())->filter( + Set::of(...Method::cases())->filter( static fn($method) => $method !== Method::get && $method !== Method::head, ), - Set\Elements::of( + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $statusCode, $protocol, $body) { $start = Request::of( From ecdc5f3c7f5cb6c22f41d64fa90cb336798b20a7 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 11 Dec 2025 14:12:23 +0100 Subject: [PATCH 13/18] remove warnings --- tests/ClientErrorTest.php | 4 ++-- tests/RedirectionTest.php | 4 ++-- tests/ServerErrorTest.php | 4 ++-- tests/SuccessTest.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/ClientErrorTest.php b/tests/ClientErrorTest.php index 14a4f7a..45edd53 100644 --- a/tests/ClientErrorTest.php +++ b/tests/ClientErrorTest.php @@ -19,7 +19,7 @@ class ClientErrorTest extends TestCase { public function testAcceptClientErrorfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::clientError) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptClientErrorfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::clientError) ->foreach(function($code) { $request = Request::of( diff --git a/tests/RedirectionTest.php b/tests/RedirectionTest.php index 326594b..f74db54 100644 --- a/tests/RedirectionTest.php +++ b/tests/RedirectionTest.php @@ -19,7 +19,7 @@ class RedirectionTest extends TestCase { public function testAcceptRedirectionfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::redirection) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptRedirectionfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::redirection) ->foreach(function($code) { $request = Request::of( diff --git a/tests/ServerErrorTest.php b/tests/ServerErrorTest.php index fa82c6b..6d7d66b 100644 --- a/tests/ServerErrorTest.php +++ b/tests/ServerErrorTest.php @@ -19,7 +19,7 @@ class ServerErrorTest extends TestCase { public function testAcceptServerErrorfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::serverError) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptServerErrorfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::serverError) ->foreach(function($code) { $request = Request::of( diff --git a/tests/SuccessTest.php b/tests/SuccessTest.php index 88f965b..b397bf8 100644 --- a/tests/SuccessTest.php +++ b/tests/SuccessTest.php @@ -19,7 +19,7 @@ class SuccessTest extends TestCase { public function testAcceptSuccessfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::successful) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptSuccessfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::successful) ->foreach(function($code) { $request = Request::of( From 70bafbcab298e7c1f8b58d4a37b81df2b1cbf9cd Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 11 Dec 2025 14:12:57 +0100 Subject: [PATCH 14/18] fix php 8.5 deprecation --- CHANGELOG.md | 4 ++++ src/Curl/Ready.php | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e15f46..5f178b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ - `Innmind\HttpTransport\Curl::proxy()` use `::map()` instead - `Innmind\HttpTransport\Curl::heartbeat()` (as it was intended for internal use) +### Fixed + +- PHP `8.5` deprecation + ## 8.1.0 - 2025-09-18 ### Added diff --git a/src/Curl/Ready.php b/src/Curl/Ready.php index 03f7317..7f76227 100644 --- a/src/Curl/Ready.php +++ b/src/Curl/Ready.php @@ -167,7 +167,6 @@ private function finalize(int $errorCode): Either ->toEncoding(Str\Encoding::ascii) ->length(), ); - \curl_close($this->handle); } } From 3765a78140d5131a89391d22a981fd3bff4dd8e3 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 1 Feb 2026 20:39:43 +0100 Subject: [PATCH 15/18] tag dependencies --- .github/workflows/ci.yml | 8 ++++---- composer.json | 19 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8ff166..17789c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,13 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main with: scenarii: 20 coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main secrets: inherit psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@next + uses: innmind/github-workflows/.github/workflows/cs.yml@main diff --git a/composer.json b/composer.json index 79e5dc4..9344c6e 100644 --- a/composer.json +++ b/composer.json @@ -17,18 +17,15 @@ "require": { "php": "~8.4", "ext-curl": "*", - "innmind/http": "dev-next", - "innmind/filesystem": "dev-next", + "innmind/http": "~9.0", + "innmind/filesystem": "~9.0", "psr/log": "~3.0", "ramsey/uuid": "^4.7", - "innmind/time-warp": "dev-next", - "innmind/time-continuum": "dev-next", - "innmind/immutable": "dev-next", - "innmind/url": "dev-next", - "innmind/io": "dev-next", - "innmind/media-type": "dev-next", - "innmind/validation": "dev-next", - "innmind/ip": "dev-next" + "innmind/time-warp": "~5.0", + "innmind/time-continuum": "~5.0", + "innmind/immutable": "~6.0", + "innmind/url": "~5.0", + "innmind/io": "~4.0" }, "autoload": { "psr-4": { @@ -41,7 +38,7 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/coding-standard": "~2.0", "innmind/black-box": "~6.5" } From a58efd5da06899a6a603797d3f500ee41f0fc207 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 1 Feb 2026 20:40:45 +0100 Subject: [PATCH 16/18] add extensive CI --- .github/workflows/extensive.yml | 12 ++++++++++++ blackbox.php | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 .github/workflows/extensive.yml diff --git a/.github/workflows/extensive.yml b/.github/workflows/extensive.yml new file mode 100644 index 0000000..257f139 --- /dev/null +++ b/.github/workflows/extensive.yml @@ -0,0 +1,12 @@ +name: Extensive CI + +on: + push: + tags: + - '*' + paths: + - '.github/workflows/extensive.yml' + +jobs: + blackbox: + uses: innmind/github-workflows/.github/workflows/extensive.yml@main diff --git a/blackbox.php b/blackbox.php index 50022e2..67a2468 100644 --- a/blackbox.php +++ b/blackbox.php @@ -10,6 +10,10 @@ }; Application::new($argv) + ->when( + \getenv('BLACKBOX_SET_SIZE') !== false, + static fn(Application $app) => $app->scenariiPerProof((int) \getenv('BLACKBOX_SET_SIZE')), + ) ->when( \getenv('ENABLE_COVERAGE') !== false, static fn(Application $app) => $app From 8473b6bc0116183f29e4dc5d84c52aae46f3a5a5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 1 Feb 2026 20:51:17 +0100 Subject: [PATCH 17/18] replace innmind/time-continuum and innmind/time-warp by innmind/time --- CHANGELOG.md | 1 + composer.json | 3 +-- src/CircuitBreaker.php | 6 +++--- src/Curl.php | 2 +- src/Curl/Concurrency.php | 2 +- src/ExponentialBackoff.php | 12 ++++++++---- src/Transport.php | 4 ++-- tests/CircuitBreakerTest.php | 2 +- tests/CurlTest.php | 2 +- tests/ExponentialBackoffTest.php | 6 ++++-- tests/LoggerTest.php | 2 +- 11 files changed, 24 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f178b6..7739d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Requires PHP `8.4` - `Innmind\HttpTransport\Transport` is now a final class, all previous implementations are now flagged as internal +- Requires `innmind/time:~1.0` ### Removed diff --git a/composer.json b/composer.json index 9344c6e..5efad05 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,7 @@ "innmind/filesystem": "~9.0", "psr/log": "~3.0", "ramsey/uuid": "^4.7", - "innmind/time-warp": "~5.0", - "innmind/time-continuum": "~5.0", + "innmind/time": "~1.0", "innmind/immutable": "~6.0", "innmind/url": "~5.0", "innmind/io": "~4.0" diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index e93dddb..35aa594 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -13,10 +13,10 @@ Header\Value, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, - PointInTime, + Point, }; use Innmind\Immutable\{ Map, @@ -30,7 +30,7 @@ final class CircuitBreaker implements Implementation { /** - * @param Map $openedCircuits + * @param Map $openedCircuits */ private function __construct( private Implementation $fulfill, diff --git a/src/Curl.php b/src/Curl.php index c891018..9226ce5 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -12,7 +12,7 @@ Factory\Header\Factory, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, }; diff --git a/src/Curl/Concurrency.php b/src/Curl/Concurrency.php index 30102ee..0b7db21 100644 --- a/src/Curl/Concurrency.php +++ b/src/Curl/Concurrency.php @@ -8,7 +8,7 @@ Failure, Success, }; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Immutable\{ Sequence, Map, diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index d631450..304b46a 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -3,10 +3,14 @@ namespace Innmind\HttpTransport; -use Innmind\Http\Request; -use Innmind\Http\Response\StatusCode; -use Innmind\TimeWarp\Halt; -use Innmind\TimeContinuum\Period; +use Innmind\Http\{ + Request, + Response\StatusCode, +}; +use Innmind\Time\{ + Halt, + Period, +}; use Innmind\Immutable\{ Sequence, Either, diff --git a/src/Transport.php b/src/Transport.php index 2f830d2..4ed65b0 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -3,12 +3,12 @@ namespace Innmind\HttpTransport; -use Innmind\TimeWarp\Halt; use Innmind\Http\Request; use Innmind\IO\IO; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; use Innmind\Immutable\Either; use Psr\Log\LoggerInterface; diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 67388d8..7283b1a 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -19,7 +19,7 @@ ProtocolVersion, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, }; diff --git a/tests/CurlTest.php b/tests/CurlTest.php index c9c68d7..a5d8a66 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -28,7 +28,7 @@ Name, }; use Innmind\IO\IO; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, }; diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index 3755645..f881f83 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -21,8 +21,10 @@ ProtocolVersion, Response\StatusCode, }; -use Innmind\TimeWarp\Halt; -use Innmind\TimeContinuum\Period; +use Innmind\Time\{ + Halt, + Period, +}; use Innmind\Url\Url; use Innmind\Immutable\{ Either, diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index c177e42..90c7f3b 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -7,7 +7,7 @@ Transport, Success, }; -use Innmind\TimeContinuum\Clock; +use Innmind\Time\Clock; use Innmind\Http\{ Request, Method, From e09f68840a3f8d2b59dd604fafc502c1b66a4fd5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 1 Feb 2026 20:51:48 +0100 Subject: [PATCH 18/18] add missing line in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7739d4f..636b32a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Requires PHP `8.4` - `Innmind\HttpTransport\Transport` is now a final class, all previous implementations are now flagged as internal +- Requires `innmind/http:~9.0` - Requires `innmind/time:~1.0` ### Removed