From c41e144873580e9cc006ae0bdf9e182f2a5d47c6 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Thu, 2 Apr 2026 08:45:30 +0200 Subject: [PATCH] Add ResourceStream encoder and decoder Introduce encoding support for phpro/resource-stream, bridging ResourceStream with the PSR-7 HTTP layer. The encoder converts a ResourceStream to a PSR-7 request body, the decoder wraps a PSR-7 response body into a ResourceStream. --- composer.json | 15 +++--- docs/transports.md | 2 + .../ResourceStream/ResourceStreamDecoder.php | 26 ++++++++++ .../ResourceStream/ResourceStreamEncoder.php | 36 ++++++++++++++ .../ResourceStreamDecoderTest.php | 29 +++++++++++ .../ResourceStreamEncoderTest.php | 48 +++++++++++++++++++ 6 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 src/Encoding/ResourceStream/ResourceStreamDecoder.php create mode 100644 src/Encoding/ResourceStream/ResourceStreamEncoder.php create mode 100644 tests/Unit/Encoding/ResourceStream/ResourceStreamDecoderTest.php create mode 100644 tests/Unit/Encoding/ResourceStream/ResourceStreamEncoderTest.php diff --git a/composer.json b/composer.json index 242e8da..cf9b9a7 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,13 @@ "require": { "php": "~8.4.0 || ~8.5.0", "ext-json": "*", + "cardinalby/content-disposition": "^1.1", + "league/uri": "^7.3", + "php-http/client-common": "^2.7", + "php-http/discovery": "^1.19", + "php-http/httplug": "^2.4", + "php-http/logger-plugin": "^1.3", + "php-http/message": "^1.16 || ^2.0", "php-standard-library/foundation": "^6.1", "php-standard-library/fun": "^6.1", "php-standard-library/hash": "^6.1", @@ -25,13 +32,7 @@ "php-standard-library/regex": "^6.1", "php-standard-library/result": "^6.1", "php-standard-library/type": "^6.1", - "cardinalby/content-disposition": "^1.1", - "league/uri": "^7.3", - "php-http/client-common": "^2.7", - "php-http/discovery": "^1.19", - "php-http/httplug": "^2.4", - "php-http/logger-plugin": "^1.3", - "php-http/message": "^1.16 || ^2.0", + "phpro/resource-stream": "^1.3", "psr/http-client-implementation": "^1.0", "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", diff --git a/docs/transports.md b/docs/transports.md index 7dfc307..51eaac2 100644 --- a/docs/transports.md +++ b/docs/transports.md @@ -40,6 +40,8 @@ This package contains some frequently used encoders / decoders for you: | `StreamDecoder` | `DecoderInterface` | Returns the PSR-7 Stream as response result | | `RawEncoder` | `EncoderInterface` | Adds raw string as request body | | `RawDecoder` | `DecoderInterface` | Returns the raw PSR-7 body string as response result | +| `ResourceStreamEncoder` | `EncoderInterface` | Adds `phpro/resource-stream` as request body | +| `ResourceStreamDecoder` | `DecoderInterface` | Returns `phpro/resource-stream` from response body | | `ResponseDecoder` | `DecoderInterface` | Returns the received PSR-7 response as result | ## Built-in transport presets: diff --git a/src/Encoding/ResourceStream/ResourceStreamDecoder.php b/src/Encoding/ResourceStream/ResourceStreamDecoder.php new file mode 100644 index 0000000..3a0a482 --- /dev/null +++ b/src/Encoding/ResourceStream/ResourceStreamDecoder.php @@ -0,0 +1,26 @@ +> + */ +final class ResourceStreamDecoder implements DecoderInterface +{ + public static function createWithAutodiscoveredPsrFactories(): self + { + return new self(); + } + + public function __invoke(ResponseInterface $response): ResourceStream + { + return Psr7Stream::createFromResponse($response); + } +} diff --git a/src/Encoding/ResourceStream/ResourceStreamEncoder.php b/src/Encoding/ResourceStream/ResourceStreamEncoder.php new file mode 100644 index 0000000..a8e453d --- /dev/null +++ b/src/Encoding/ResourceStream/ResourceStreamEncoder.php @@ -0,0 +1,36 @@ +> + */ +final class ResourceStreamEncoder implements EncoderInterface +{ + public function __construct( + private StreamFactoryInterface $streamFactory, + ) { + } + + public static function createWithAutodiscoveredPsrFactories(): self + { + return new self( + Psr17FactoryDiscovery::findStreamFactory(), + ); + } + + public function __invoke(RequestInterface $request, $data): RequestInterface + { + return $request->withBody( + $this->streamFactory->createStreamFromResource($data->unwrap()), + ); + } +} diff --git a/tests/Unit/Encoding/ResourceStream/ResourceStreamDecoderTest.php b/tests/Unit/Encoding/ResourceStream/ResourceStreamDecoderTest.php new file mode 100644 index 0000000..9de357b --- /dev/null +++ b/tests/Unit/Encoding/ResourceStream/ResourceStreamDecoderTest.php @@ -0,0 +1,29 @@ +createResponse() + ->withBody($this->createStream($content = '{"hello": "world"}')); + + $decoded = $decoder($response); + + self::assertInstanceOf(ResourceStream::class, $decoded); + self::assertSame($content, $decoded->getContents()); + } +} diff --git a/tests/Unit/Encoding/ResourceStream/ResourceStreamEncoderTest.php b/tests/Unit/Encoding/ResourceStream/ResourceStreamEncoderTest.php new file mode 100644 index 0000000..49b1d8b --- /dev/null +++ b/tests/Unit/Encoding/ResourceStream/ResourceStreamEncoderTest.php @@ -0,0 +1,48 @@ +write($content = 'Hello world'); + $resourceStream->rewind(); + + $encoder = ResourceStreamEncoder::createWithAutodiscoveredPsrFactories(); + $request = $this->createRequest('POST', '/hello'); + + $actual = $encoder($request, $resourceStream); + + self::assertSame($request->getMethod(), $actual->getMethod()); + self::assertSame($request->getUri(), $actual->getUri()); + self::assertSame($content, (string) $actual->getBody()); + self::assertFalse($actual->hasHeader('Content-Type')); + } + + #[Test] + public function it_can_encode_resource_stream_without_rewinding(): void + { + $resourceStream = MemoryStream::create(); + $resourceStream->write($content = 'Hello world'); + + $encoder = ResourceStreamEncoder::createWithAutodiscoveredPsrFactories(); + $request = $this->createRequest('POST', '/hello'); + + $actual = $encoder($request, $resourceStream); + + self::assertSame($content, (string) $actual->getBody()); + } +}