From 60402f52e98948be552a9e9390b410125ff80f2f Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Thu, 2 Apr 2026 09:02:38 +0200 Subject: [PATCH] Add ContentTypeAwarePayload and ContentAwareEncoder Introduce a generic DTO that pairs any payload with a content type, and a decorator encoder that wraps any existing encoder to set the Content-Type header from the payload metadata. --- docs/transports.md | 15 +++--- .../ContentType/ContentTypeAwareEncoder.php | 34 ++++++++++++++ .../ContentType/ContentTypeAwarePayload.php | 20 ++++++++ .../ContentTypeAwareEncoderTest.php | 47 +++++++++++++++++++ 4 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/Encoding/ContentType/ContentTypeAwareEncoder.php create mode 100644 src/Encoding/ContentType/ContentTypeAwarePayload.php create mode 100644 tests/Unit/Encoding/ContentType/ContentTypeAwareEncoderTest.php diff --git a/docs/transports.md b/docs/transports.md index 51eaac2..dd8f803 100644 --- a/docs/transports.md +++ b/docs/transports.md @@ -29,20 +29,21 @@ This package contains some frequently used encoders / decoders for you: | Class | EncodingType | Action | |-------------------------|---------------------------------------|-------------------------------------------------------------------------------------| -| `EmptyBodyEncoder` | `EncoderInterface` | Creates empty request body | | `BinaryFileDecoder` | `DecoderInterface` | Parses file information from the HTTP response and returns a `BinaryFile` DTO | -| `FormUrlencodedEncoder` | `EncoderInterface` | Adds form urlencoded body and headers to request | +| `ContentTypeAwareEncoder` | `EncoderInterface>` | Decorator that wraps any encoder and sets the Content-Type header from the payload | +| `EmptyBodyEncoder` | `EncoderInterface` | Creates empty request body | | `FormUrlencodedDecoder` | `DecoderInterface` | Converts form urlencoded response body to array | -| `JsonEncoder` | `EncoderInterface` | Adds json body and headers to request | +| `FormUrlencodedEncoder` | `EncoderInterface` | Adds form urlencoded body and headers to request | | `JsonDecoder` | `DecoderInterface` | Converts json response body to array | +| `JsonEncoder` | `EncoderInterface` | Adds json body and headers to request | | `MultiPartEncoder` | `EncoderInterface` | Adds symfony/mime `AbstractMultipartPart`as HTTP body. Handy for form data + files. | -| `StreamEncoder` | `EncoderInterface` | Adds PSR-7 Stream as request body | -| `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 | +| `RawEncoder` | `EncoderInterface` | Adds raw string as request body | | `ResourceStreamDecoder` | `DecoderInterface` | Returns `phpro/resource-stream` from response body | +| `ResourceStreamEncoder` | `EncoderInterface` | Adds `phpro/resource-stream` as request body | | `ResponseDecoder` | `DecoderInterface` | Returns the received PSR-7 response as result | +| `StreamDecoder` | `DecoderInterface` | Returns the PSR-7 Stream as response result | +| `StreamEncoder` | `EncoderInterface` | Adds PSR-7 Stream as request body | ## Built-in transport presets: diff --git a/src/Encoding/ContentType/ContentTypeAwareEncoder.php b/src/Encoding/ContentType/ContentTypeAwareEncoder.php new file mode 100644 index 0000000..c871995 --- /dev/null +++ b/src/Encoding/ContentType/ContentTypeAwareEncoder.php @@ -0,0 +1,34 @@ +> + */ +final class ContentTypeAwareEncoder implements EncoderInterface +{ + /** + * @param EncoderInterface $encoder + */ + public function __construct( + private EncoderInterface $encoder, + ) { + } + + /** + * @param ContentTypeAwarePayload $data + */ + public function __invoke(RequestInterface $request, $data): RequestInterface + { + $request = ($this->encoder)($request, $data->payload); + + return $request->withHeader('Content-Type', $data->contentType); + } +} diff --git a/src/Encoding/ContentType/ContentTypeAwarePayload.php b/src/Encoding/ContentType/ContentTypeAwarePayload.php new file mode 100644 index 0000000..694d7e8 --- /dev/null +++ b/src/Encoding/ContentType/ContentTypeAwarePayload.php @@ -0,0 +1,20 @@ +createRequest('POST', '/hello'); + $payload = new ContentTypeAwarePayload('application/pdf', 'raw-content'); + + $actual = $encoder($request, $payload); + + self::assertSame('raw-content', (string) $actual->getBody()); + self::assertSame('application/pdf', $actual->getHeaderLine('Content-Type')); + } + + #[Test] + public function it_overrides_content_type_set_by_inner_encoder(): void + { + $encoder = new ContentTypeAwareEncoder( + RawEncoder::createWithAutodiscoveredPsrFactories() + ); + $request = $this->createRequest('POST', '/hello') + ->withHeader('Content-Type', 'text/plain'); + $payload = new ContentTypeAwarePayload('application/xml', 'raw-content'); + + $actual = $encoder($request, $payload); + + self::assertSame('application/xml', $actual->getHeaderLine('Content-Type')); + } +}