diff --git a/src/ContentTypeMiddleware.php b/src/ContentTypeMiddleware.php index 6d7724bb..002cb75d 100644 --- a/src/ContentTypeMiddleware.php +++ b/src/ContentTypeMiddleware.php @@ -107,7 +107,7 @@ private function formatResponse(UnformattedResponse $response, ?Formatter $forma return $response->withStatus(StatusCodeInterface::STATUS_NOT_ACCEPTABLE); } - $body->write($formatter->format($response->getUnformattedContent())); + $body->write($formatter->format($response->getUnformattedContent(), $response->getAttributes())); $body->rewind(); return $response; diff --git a/src/Formatter.php b/src/Formatter.php index 1ea6b29c..57d47399 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -6,9 +6,10 @@ interface Formatter { /** - * @param mixed $content + * @param mixed $content + * @param mixed[] $attributes * * @throw ContentCouldNotBeFormatted */ - public function format($content): string; + public function format($content, array $attributes = []): string; } diff --git a/src/Formatter/JmsSerializer.php b/src/Formatter/JmsSerializer.php index d1abbac9..cded0b66 100644 --- a/src/Formatter/JmsSerializer.php +++ b/src/Formatter/JmsSerializer.php @@ -30,7 +30,7 @@ public function __construct(SerializerInterface $serializer, string $format) /** * {@inheritdoc} */ - public function format($content): string + public function format($content, array $attributes = []): string { try { return $this->serializer->serialize($content, $this->format); diff --git a/src/Formatter/Json.php b/src/Formatter/Json.php index aec70d5e..74d0b00c 100644 --- a/src/Formatter/Json.php +++ b/src/Formatter/Json.php @@ -33,7 +33,7 @@ public function __construct(int $flags = self::DEFAULT_FLAGS) /** * {@inheritdoc} */ - public function format($content): string + public function format($content, array $attributes = []): string { try { $encoded = json_encode($content, $this->flags); diff --git a/src/Formatter/StringCast.php b/src/Formatter/StringCast.php index c6aa103c..22a70f01 100644 --- a/src/Formatter/StringCast.php +++ b/src/Formatter/StringCast.php @@ -13,7 +13,7 @@ final class StringCast implements Formatter /** * {@inheritdoc} */ - public function format($content): string + public function format($content, array $attributes = []): string { if (is_object($content) && ! method_exists($content, '__toString')) { throw new ContentCouldNotBeFormatted('Given data could not be cast to string'); diff --git a/src/UnformattedResponse.php b/src/UnformattedResponse.php index cb2b8109..153da3d9 100644 --- a/src/UnformattedResponse.php +++ b/src/UnformattedResponse.php @@ -19,12 +19,22 @@ final class UnformattedResponse implements ResponseInterface private $unformattedContent; /** - * @param mixed $unformattedContent + * @var mixed[] */ - public function __construct(ResponseInterface $decoratedResponse, $unformattedContent) - { + private $attributes; + + /** + * @param mixed $unformattedContent + * @param mixed[] $attributes + */ + public function __construct( + ResponseInterface $decoratedResponse, + $unformattedContent, + array $attributes = [] + ) { $this->decoratedResponse = $decoratedResponse; $this->unformattedContent = $unformattedContent; + $this->attributes = $attributes; } /** @@ -50,7 +60,8 @@ public function withProtocolVersion($version) { return new self( $this->decoratedResponse->withProtocolVersion($version), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -93,7 +104,8 @@ public function withHeader($name, $value) { return new self( $this->decoratedResponse->withHeader($name, $value), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -104,7 +116,8 @@ public function withAddedHeader($name, $value) { return new self( $this->decoratedResponse->withAddedHeader($name, $value), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -115,7 +128,8 @@ public function withoutHeader($name) { return new self( $this->decoratedResponse->withoutHeader($name), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -134,7 +148,8 @@ public function withBody(StreamInterface $body) { return new self( $this->decoratedResponse->withBody($body), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -153,7 +168,8 @@ public function withStatus($code, $reasonPhrase = '') { return new self( $this->decoratedResponse->withStatus($code, $reasonPhrase), - $this->unformattedContent + $this->unformattedContent, + $this->attributes ); } @@ -164,4 +180,28 @@ public function getReasonPhrase() { return $this->decoratedResponse->getReasonPhrase(); } + + /** + * Returns an instance with the specified attribute + * + * @param mixed $value + */ + public function withAttribute(string $name, $value): self + { + return new self( + $this->decoratedResponse, + $this->unformattedContent, + [$name => $value] + $this->attributes + ); + } + + /** + * Retrieve the configured attributes + * + * @return mixed[] + */ + public function getAttributes(): array + { + return $this->attributes; + } } diff --git a/tests/ContentTypeMiddlewareTest.php b/tests/ContentTypeMiddlewareTest.php index ddcc8735..0ade5445 100644 --- a/tests/ContentTypeMiddlewareTest.php +++ b/tests/ContentTypeMiddlewareTest.php @@ -7,6 +7,7 @@ use Fig\Http\Message\StatusCodeInterface; use Lcobucci\ContentNegotiation\ContentTypeMiddleware; use Lcobucci\ContentNegotiation\Formatter; +use Lcobucci\ContentNegotiation\Tests\Formatter\NaiveTemplateEngine; use Lcobucci\ContentNegotiation\UnformattedResponse; use PHPUnit\Framework\Error\Warning; use PHPUnit\Framework\TestCase; @@ -60,9 +61,7 @@ public function processShouldReturnAResponseWithErrorWhenFormatterWasNotFound(): $response = $middleware->process( (new ServerRequest())->withAddedHeader('Accept', 'text/plain'), - $this->createRequestHandler( - new UnformattedResponse(new Response(), new PersonDto(1, 'Testing')) - ) + $this->createRequestHandler($this->createResponse()) ); self::assertInstanceOf(UnformattedResponse::class, $response); @@ -88,9 +87,7 @@ public function processShouldReturnAResponseWithFormattedContent(): void $response = $middleware->process( new ServerRequest(), - $this->createRequestHandler( - new UnformattedResponse(new Response(), new PersonDto(1, 'Testing')) - ) + $this->createRequestHandler($this->createResponse()) ); self::assertInstanceOf(UnformattedResponse::class, $response); @@ -99,6 +96,37 @@ public function processShouldReturnAResponseWithFormattedContent(): void self::assertJsonStringEqualsJsonString('{"id":1,"name":"Testing"}', (string) $response->getBody()); } + /** + * @test + * + * @covers ::__construct() + * @covers ::fromRecommendedSettings() + * @covers ::process() + * @covers ::extractContentType() + * @covers ::formatResponse() + * + * @uses \Lcobucci\ContentNegotiation\UnformattedResponse + * @uses \Lcobucci\ContentNegotiation\Formatter\Json + */ + public function processShouldPassAttributesToTheFormatterProperly(): void + { + $middleware = $this->createMiddleware(); + + $response = $middleware->process( + (new ServerRequest())->withAddedHeader('Accept', 'text/html'), + $this->createRequestHandler($this->createResponse(['template' => 'person'])) + ); + + self::assertInstanceOf(UnformattedResponse::class, $response); + self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode()); + self::assertSame('text/html; charset=UTF-8', $response->getHeaderLine('Content-Type')); + + $body = (string) $response->getBody(); + + self::assertContains('
1
', $body); + self::assertContains('
Testing
', $body); + } + /** * @test * @@ -117,9 +145,7 @@ public function processShouldReturnAResponseWithFormattedContentEvenWithoutForci $response = $middleware->process( new ServerRequest(), - $this->createRequestHandler( - new UnformattedResponse(new Response(), new PersonDto(1, 'Testing')) - ) + $this->createRequestHandler($this->createResponse()) ); self::assertInstanceOf(UnformattedResponse::class, $response); @@ -179,9 +205,19 @@ function () { $middleware->process( new ServerRequest(), - $this->createRequestHandler( - new UnformattedResponse(new Response(), new PersonDto(1, 'Testing')) - ) + $this->createRequestHandler($this->createResponse()) + ); + } + + /** + * @param mixed[] $attributes + */ + private function createResponse(array $attributes = []): UnformattedResponse + { + return new UnformattedResponse( + new Response(), + new PersonDto(1, 'Testing'), + $attributes ); } @@ -223,8 +259,16 @@ private function createMiddleware(bool $forceCharset = true, ?callable $streamFa 'mime-type' => ['text/plain'], 'charset' => $forceCharset, ], + 'html' => [ + 'extension' => ['html', 'htm'], + 'mime-type' => ['text/html', 'application/xhtml+xml'], + 'charset' => $forceCharset, + ], + ], + [ + 'application/json' => new Formatter\Json(), + 'text/html' => new NaiveTemplateEngine(), ], - ['application/json' => new Formatter\Json()], $streamFactory ); } diff --git a/tests/Formatter/NaiveTemplateEngine.php b/tests/Formatter/NaiveTemplateEngine.php new file mode 100644 index 00000000..fff5e125 --- /dev/null +++ b/tests/Formatter/NaiveTemplateEngine.php @@ -0,0 +1,57 @@ +getTemplateContent($attributes); + + return $this->render($template, (array) $content); + } + + /** + * @param mixed[] $attributes + */ + private function getTemplateContent(array $attributes): string + { + $template = $attributes['template'] ?? ''; + assert(is_string($template)); + + $file = new SplFileObject(self::BASE_DIR . $template . '.' . self::EXTENSION); + + return $file->fread($file->getSize()); + } + + /** + * @param mixed[] $data + */ + private function render(string $template, array $data): string + { + $variables = array_map( + function (string $attribute): string { + return '{' . $attribute . '}'; + }, + array_keys($data) + ); + + return trim(str_replace($variables, $data, $template)); + } +} diff --git a/tests/Formatter/templates/naive/person.html b/tests/Formatter/templates/naive/person.html new file mode 100644 index 00000000..001b0e21 --- /dev/null +++ b/tests/Formatter/templates/naive/person.html @@ -0,0 +1,8 @@ +
+
+
Identifier
+
{id}
+
Name
+
{name}
+
+
diff --git a/tests/UnformattedResponseTest.php b/tests/UnformattedResponseTest.php index 0825f692..fa8d56bd 100644 --- a/tests/UnformattedResponseTest.php +++ b/tests/UnformattedResponseTest.php @@ -27,6 +27,56 @@ public function getUnformattedContentShouldReturnTheConfiguredValue(): void self::assertSame($dto, $response->getUnformattedContent()); } + /** + * @test + * + * @covers ::withAttribute() + * + * @uses \Lcobucci\ContentNegotiation\UnformattedResponse::__construct() + */ + public function withAttributeShouldReturnANewInstanceWithTheAddedAttribute(): void + { + $response1 = new UnformattedResponse(new Response(), new PersonDto(1, 'Testing')); + $response2 = $response1->withAttribute('test', 1); + + self::assertAttributeSame([], 'attributes', $response1); + self::assertAttributeSame(['test' => 1], 'attributes', $response2); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::withAttribute() + */ + public function withAttributeShouldOverrideExistingAttributes(): void + { + $response = new UnformattedResponse( + new Response(), + new PersonDto(1, 'Testing'), + ['test' => 1] + ); + + self::assertAttributeSame(['test' => 2], 'attributes', $response->withAttribute('test', 2)); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::getAttributes() + */ + public function getAttributesShouldReturnTheConfiguredAttributes(): void + { + $response = new UnformattedResponse( + new Response(), + new PersonDto(1, 'Testing'), + ['test' => 1] + ); + + self::assertSame(['test' => 1], $response->getAttributes()); + } + /** * @test * @@ -200,10 +250,11 @@ private function assertSetterReturn(string $method, ...$arguments): void $decoratedResponse = new Response(); $dto = new PersonDto(1, 'Testing'); - $response = new UnformattedResponse($decoratedResponse, $dto); + $response = new UnformattedResponse($decoratedResponse, $dto, ['test' => 1]); $expected = new UnformattedResponse( $decoratedResponse->$method(...$arguments), - $dto + $dto, + ['test' => 1] ); self::assertEquals($expected, $response->$method(...$arguments));