From 4b6d6fdeb7663cb8168460b16686caca56f0b46a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 15 Jan 2026 18:10:00 +0300 Subject: [PATCH 01/64] start --- src/Modern/DataResponse.php | 110 +++++++++++++++++++++++++++ src/Modern/DataResponseFormatter.php | 28 +++++++ 2 files changed, 138 insertions(+) create mode 100644 src/Modern/DataResponse.php create mode 100644 src/Modern/DataResponseFormatter.php diff --git a/src/Modern/DataResponse.php b/src/Modern/DataResponse.php new file mode 100644 index 0000000..5e76b94 --- /dev/null +++ b/src/Modern/DataResponse.php @@ -0,0 +1,110 @@ +response->getProtocolVersion(); + } + + public function withProtocolVersion(string $version): MessageInterface + { + $new = clone $this; + $new->response = $this->response->withProtocolVersion($version); + return $new; + } + + public function getHeaders(): array + { + return $this->response->getHeaders(); + } + + public function hasHeader(string $name): bool + { + return $this->response->hasHeader($name); + } + + public function getHeader(string $name): array + { + return $this->response->getHeader($name); + } + + public function getHeaderLine(string $name): string + { + return $this->response->getHeaderLine($name); + } + + public function withHeader(string $name, $value): MessageInterface + { + $new = clone $this; + $new->response = $this->response->withHeader($name, $value); + return $new; + } + + public function withAddedHeader(string $name, $value): MessageInterface + { + $new = clone $this; + $new->response = $this->response->withAddedHeader($name, $value); + return $new; + } + + public function withoutHeader(string $name): MessageInterface + { + $new = clone $this; + $new->response = $this->response->withoutHeader($name); + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->formatted) { + throw new LogicException('The response body is not formatted.'); + } + + return $this->response->getBody(); + } + + public function withBody(StreamInterface $body): MessageInterface + { + $new = clone $this; + $new->response = $this->response->withBody($body); + $new->formatted = true; + return $new; + } + + public function getStatusCode(): int + { + return $this->response->getStatusCode(); + } + + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface + { + $new = clone $this; + $new->response = $this->response->withStatus($code, $reasonPhrase); + return $new; + } + + public function getReasonPhrase(): string + { + return $this->response->getReasonPhrase(); + } +} diff --git a/src/Modern/DataResponseFormatter.php b/src/Modern/DataResponseFormatter.php new file mode 100644 index 0000000..1ef360c --- /dev/null +++ b/src/Modern/DataResponseFormatter.php @@ -0,0 +1,28 @@ +handle($request); + if ($response instanceof DataResponse) { + $response = ($response->responseFormatter ?? $this->defaultFormatter)->format($response); + } + return $response; + } +} From cf6f77fa93c9a155ced059593c42c02217dd0360 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 15 Jan 2026 20:18:51 +0300 Subject: [PATCH 02/64] implement --- src/Modern/DataResponse.php | 14 +- src/Modern/DataResponseFormatter.php | 28 --- .../Formatter/HtmlResponseFormatter.php | 50 ++++ .../Formatter/JsonResponseFormatter.php | 49 ++++ .../Formatter/PlainTextResponseFormatter.php | 48 ++++ src/Modern/Formatter/XmlDataInterface.php | 38 +++ src/Modern/Formatter/XmlResponseFormatter.php | 222 ++++++++++++++++++ src/Modern/Middleware/ContentNegotiator.php | 81 +++++++ .../Middleware/DataResponseFormatter.php | 29 +++ .../Middleware/HtmlDataResponseFormatter.php | 18 ++ .../Middleware/JsonDataResponseFormatter.php | 18 ++ .../PlainTextDataResponseFormatter.php | 18 ++ .../Middleware/XmlDataResponseFormatter.php | 18 ++ src/Modern/ResponseFormatterInterface.php | 23 ++ 14 files changed, 617 insertions(+), 37 deletions(-) delete mode 100644 src/Modern/DataResponseFormatter.php create mode 100644 src/Modern/Formatter/HtmlResponseFormatter.php create mode 100644 src/Modern/Formatter/JsonResponseFormatter.php create mode 100644 src/Modern/Formatter/PlainTextResponseFormatter.php create mode 100644 src/Modern/Formatter/XmlDataInterface.php create mode 100644 src/Modern/Formatter/XmlResponseFormatter.php create mode 100644 src/Modern/Middleware/ContentNegotiator.php create mode 100644 src/Modern/Middleware/DataResponseFormatter.php create mode 100644 src/Modern/Middleware/HtmlDataResponseFormatter.php create mode 100644 src/Modern/Middleware/JsonDataResponseFormatter.php create mode 100644 src/Modern/Middleware/PlainTextDataResponseFormatter.php create mode 100644 src/Modern/Middleware/XmlDataResponseFormatter.php create mode 100644 src/Modern/ResponseFormatterInterface.php diff --git a/src/Modern/DataResponse.php b/src/Modern/DataResponse.php index 5e76b94..d6071a2 100644 --- a/src/Modern/DataResponse.php +++ b/src/Modern/DataResponse.php @@ -8,13 +8,9 @@ use Psr\Http\Message\MessageInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; -use Yiisoft\DataResponse\DataResponseFormatterInterface; final class DataResponse implements ResponseInterface { - private bool $formatted = false; - public ?DataResponseFormatterInterface $responseFormatter = null; - public function __construct( public readonly mixed $data, private ResponseInterface $response, @@ -76,10 +72,6 @@ public function withoutHeader(string $name): MessageInterface public function getBody(): StreamInterface { - if (!$this->formatted) { - throw new LogicException('The response body is not formatted.'); - } - return $this->response->getBody(); } @@ -87,7 +79,6 @@ public function withBody(StreamInterface $body): MessageInterface { $new = clone $this; $new->response = $this->response->withBody($body); - $new->formatted = true; return $new; } @@ -107,4 +98,9 @@ public function getReasonPhrase(): string { return $this->response->getReasonPhrase(); } + + public function getResponse(): ResponseInterface + { + return $this->response; + } } diff --git a/src/Modern/DataResponseFormatter.php b/src/Modern/DataResponseFormatter.php deleted file mode 100644 index 1ef360c..0000000 --- a/src/Modern/DataResponseFormatter.php +++ /dev/null @@ -1,28 +0,0 @@ -handle($request); - if ($response instanceof DataResponse) { - $response = ($response->responseFormatter ?? $this->defaultFormatter)->format($response); - } - return $response; - } -} diff --git a/src/Modern/Formatter/HtmlResponseFormatter.php b/src/Modern/Formatter/HtmlResponseFormatter.php new file mode 100644 index 0000000..c51f991 --- /dev/null +++ b/src/Modern/Formatter/HtmlResponseFormatter.php @@ -0,0 +1,50 @@ +getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/JsonResponseFormatter.php b/src/Modern/Formatter/JsonResponseFormatter.php new file mode 100644 index 0000000..e000e63 --- /dev/null +++ b/src/Modern/Formatter/JsonResponseFormatter.php @@ -0,0 +1,49 @@ +getBody() + ->write(Json::encode($data, $this->options)); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/PlainTextResponseFormatter.php b/src/Modern/Formatter/PlainTextResponseFormatter.php new file mode 100644 index 0000000..f474685 --- /dev/null +++ b/src/Modern/Formatter/PlainTextResponseFormatter.php @@ -0,0 +1,48 @@ +getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/XmlDataInterface.php b/src/Modern/Formatter/XmlDataInterface.php new file mode 100644 index 0000000..7e0b3a6 --- /dev/null +++ b/src/Modern/Formatter/XmlDataInterface.php @@ -0,0 +1,38 @@ + The attributes of the XML tag. + */ + public function xmlTagAttributes(): array; + + /** + * Returns an array of data to format as XML. + * + * The data can be any scalar values, instances of `XmlDataInterface`, + * and nested arrays of any level consisting of the above values. + * + * @return array The data to format as XML. + */ + public function xmlData(): array; +} diff --git a/src/Modern/Formatter/XmlResponseFormatter.php b/src/Modern/Formatter/XmlResponseFormatter.php new file mode 100644 index 0000000..ae7a07a --- /dev/null +++ b/src/Modern/Formatter/XmlResponseFormatter.php @@ -0,0 +1,222 @@ +withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $dom = new DOMDocument($this->version, $this->encoding); + + if (empty($this->rootTag)) { + $this->buildXml($dom, $dom, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $root = new DOMElement($this->rootTag); + $dom->appendChild($root); + $this->buildXml($dom, $root, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + /** + * Builds the data to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param mixed $data Data for building XML. + */ + private function buildXml(DOMDocument $dom, $element, mixed $data): void + { + if (empty($data)) { + return; + } + + if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { + /** @var int|string $name */ + foreach ($data as $name => $value) { + if (is_object($value)) { + $this->buildObject($dom, $element, $value, $name); + continue; + } + + $child = $this->safeCreateDomElement($dom, $name); + $element->appendChild($child); + + if (is_array($value)) { + $this->buildXml($dom, $child, $value); + continue; + } + + /** @psalm-var scalar $value */ + + $this->setScalarValueToDomElement($child, $value); + } + + return; + } + + if (is_object($data)) { + $this->buildObject($dom, $element, $data); + return; + } + + /** @psalm-var scalar $data */ + + $this->setScalarValueToDomElement($element, $data); + } + + /** + * Builds the object to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param object $object To build. + * @param int|string|null $tagName The tag name. + */ + private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void + { + if ($object instanceof XmlDataInterface) { + $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); + + foreach ($object->xmlTagAttributes() as $name => $value) { + $child->setAttribute($name, $value); + } + + $element->appendChild($child); + $this->buildXml($dom, $child, $object->xmlData()); + return; + } + + $child = $this->safeCreateDomElement($dom, $tagName); + $element->appendChild($child); + + if ($object instanceof Traversable) { + $this->buildXml($dom, $child, $object); + return; + } + + $data = []; + + /** + * @var string $property + */ + foreach ($object as $property => $value) { + $data[$property] = $value; + } + + $this->buildXml($dom, $child, $data); + } + + /** + * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, + * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. + * + * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 + * + * @param DOMDocument $dom The root DOM document. + * @param int|string|null $tagName The tag name. + * + * @return DOMElement + */ + private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement + { + if (empty($tagName) || is_int($tagName)) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + + try { + if (!$element = $dom->createElement($tagName)) { + throw new DOMException(); + } + return $element; + } catch (DOMException) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + } + + /** + * Sets the scalar value to DOM Element instance if the value is not empty. + * + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param bool|float|int|string|null $value + */ + private function setScalarValueToDomElement($element, $value): void + { + $value = $this->formatScalarValue($value); + + if ($value !== '') { + $element->appendChild(new DOMText($value)); + } + } + + /** + * Formats scalar value for use in XML node. + * + * @param bool|float|int|string|null $value To format. + * + * @return string The string representation of the value. + */ + private function formatScalarValue($value): string + { + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value)) { + return NumericHelper::normalize($value); + } + + return (string) $value; + } +} diff --git a/src/Modern/Middleware/ContentNegotiator.php b/src/Modern/Middleware/ContentNegotiator.php new file mode 100644 index 0000000..135a81e --- /dev/null +++ b/src/Modern/Middleware/ContentNegotiator.php @@ -0,0 +1,81 @@ + $contentFormatters + */ + public function __construct( + private readonly array $contentFormatters, + ) { + $this->checkFormatters($contentFormatters); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + if ($response instanceof DataResponse) { + $accepted = $request->getHeader(Header::ACCEPT); + + foreach ($accepted as $accept) { + foreach ($this->contentFormatters as $contentType => $formatter) { + if (str_contains($accept, $contentType)) { + return $formatter->format($response->data, $response->getResponse()); + } + } + } + } + + return $response; + } + + /** + * Checks the content formatters. + * + * @param array $contentFormatters The content formatters to check. + */ + private function checkFormatters(array $contentFormatters): void + { + foreach ($contentFormatters as $contentType => $formatter) { + if (!is_string($contentType)) { + throw new RuntimeException(sprintf( + 'Invalid formatter content type. A string is expected, "%s" is received.', + gettype($contentType), + )); + } + + if (!($formatter instanceof ResponseFormatterInterface)) { + throw new RuntimeException(sprintf( + 'Invalid formatter. A "%s" instance is expected, "%s" is received.', + ResponseFormatterInterface::class, + get_debug_type($formatter), + )); + } + } + } +} diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php new file mode 100644 index 0000000..d43672e --- /dev/null +++ b/src/Modern/Middleware/DataResponseFormatter.php @@ -0,0 +1,29 @@ +handle($request); + if ($response instanceof DataResponse) { + $response = $this->defaultFormatter->format($response->data, $response->getResponse()); + } + return $response; + } +} diff --git a/src/Modern/Middleware/HtmlDataResponseFormatter.php b/src/Modern/Middleware/HtmlDataResponseFormatter.php new file mode 100644 index 0000000..2fa3905 --- /dev/null +++ b/src/Modern/Middleware/HtmlDataResponseFormatter.php @@ -0,0 +1,18 @@ + Date: Thu, 15 Jan 2026 17:29:30 +0000 Subject: [PATCH 03/64] Apply PHP CS Fixer and Rector changes (CI) --- src/Modern/DataResponse.php | 4 +--- src/Modern/Formatter/HtmlResponseFormatter.php | 3 +-- src/Modern/Formatter/JsonResponseFormatter.php | 3 +-- src/Modern/Formatter/PlainTextResponseFormatter.php | 3 +-- src/Modern/Formatter/XmlResponseFormatter.php | 3 +-- src/Modern/Middleware/DataResponseFormatter.php | 3 +-- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Modern/DataResponse.php b/src/Modern/DataResponse.php index d6071a2..ac6b02e 100644 --- a/src/Modern/DataResponse.php +++ b/src/Modern/DataResponse.php @@ -4,7 +4,6 @@ namespace Yiisoft\DataResponse\Modern; -use LogicException; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; @@ -14,8 +13,7 @@ final class DataResponse implements ResponseInterface public function __construct( public readonly mixed $data, private ResponseInterface $response, - ) { - } + ) {} public function getProtocolVersion(): string { diff --git a/src/Modern/Formatter/HtmlResponseFormatter.php b/src/Modern/Formatter/HtmlResponseFormatter.php index c51f991..8bd84a6 100644 --- a/src/Modern/Formatter/HtmlResponseFormatter.php +++ b/src/Modern/Formatter/HtmlResponseFormatter.php @@ -25,8 +25,7 @@ final class HtmlResponseFormatter implements ResponseFormatterInterface public function __construct( private readonly string $contentType = 'text/html', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(mixed $data, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/Formatter/JsonResponseFormatter.php b/src/Modern/Formatter/JsonResponseFormatter.php index e000e63..b1fb9ee 100644 --- a/src/Modern/Formatter/JsonResponseFormatter.php +++ b/src/Modern/Formatter/JsonResponseFormatter.php @@ -28,8 +28,7 @@ public function __construct( private readonly string $contentType = 'application/json', private readonly string $encoding = 'UTF-8', private readonly int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, - ) { - } + ) {} /** * @inheritDoc diff --git a/src/Modern/Formatter/PlainTextResponseFormatter.php b/src/Modern/Formatter/PlainTextResponseFormatter.php index f474685..24a1008 100644 --- a/src/Modern/Formatter/PlainTextResponseFormatter.php +++ b/src/Modern/Formatter/PlainTextResponseFormatter.php @@ -25,8 +25,7 @@ final class PlainTextResponseFormatter implements ResponseFormatterInterface public function __construct( private readonly string $contentType = 'text/plain', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(mixed $data, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/Formatter/XmlResponseFormatter.php b/src/Modern/Formatter/XmlResponseFormatter.php index ae7a07a..39926ce 100644 --- a/src/Modern/Formatter/XmlResponseFormatter.php +++ b/src/Modern/Formatter/XmlResponseFormatter.php @@ -37,8 +37,7 @@ public function __construct( private readonly string $encoding = 'UTF-8', private readonly string $version = '1.0', private readonly string $rootTag = 'response', - ) { - } + ) {} public function format(mixed $data, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php index d43672e..15ce0c1 100644 --- a/src/Modern/Middleware/DataResponseFormatter.php +++ b/src/Modern/Middleware/DataResponseFormatter.php @@ -15,8 +15,7 @@ class DataResponseFormatter implements MiddlewareInterface { public function __construct( private readonly ResponseFormatterInterface $defaultFormatter, - ) { - } + ) {} final public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { From 1b7d1371076363d64d9a0e9cfec7dbf817b579d0 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 15 Jan 2026 20:36:47 +0300 Subject: [PATCH 04/64] rename --- src/Modern/Middleware/DataResponseFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php index d43672e..52d8b34 100644 --- a/src/Modern/Middleware/DataResponseFormatter.php +++ b/src/Modern/Middleware/DataResponseFormatter.php @@ -14,7 +14,7 @@ class DataResponseFormatter implements MiddlewareInterface { public function __construct( - private readonly ResponseFormatterInterface $defaultFormatter, + private readonly ResponseFormatterInterface $formatter, ) { } @@ -22,7 +22,7 @@ final public function process(ServerRequestInterface $request, RequestHandlerInt { $response = $handler->handle($request); if ($response instanceof DataResponse) { - $response = $this->defaultFormatter->format($response->data, $response->getResponse()); + $response = $this->formatter->format($response->data, $response->getResponse()); } return $response; } From 20e0eff0291e0cf68d55632918b8a6d1cc4c8726 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 17 Jan 2026 08:51:23 +0300 Subject: [PATCH 05/64] refactor --- .../Formatter/HtmlResponseFormatter.php | 49 ---- .../Formatter/JsonResponseFormatter.php | 48 ---- .../Formatter/PlainTextResponseFormatter.php | 47 ---- src/Modern/Formatter/XmlResponseFormatter.php | 221 ----------------- src/Modern/Middleware/ContentNegotiator.php | 81 ------- .../Middleware/DataResponseFormatter.php | 28 --- .../Middleware/HtmlDataResponseFormatter.php | 50 +++- .../Middleware/JsonDataResponseFormatter.php | 47 +++- .../PlainTextDataResponseFormatter.php | 48 +++- .../Middleware/XmlDataResponseFormatter.php | 223 +++++++++++++++++- src/Modern/ResponseFormatterInterface.php | 23 -- .../{Formatter => }/XmlDataInterface.php | 2 +- 12 files changed, 353 insertions(+), 514 deletions(-) delete mode 100644 src/Modern/Formatter/HtmlResponseFormatter.php delete mode 100644 src/Modern/Formatter/JsonResponseFormatter.php delete mode 100644 src/Modern/Formatter/PlainTextResponseFormatter.php delete mode 100644 src/Modern/Formatter/XmlResponseFormatter.php delete mode 100644 src/Modern/Middleware/ContentNegotiator.php delete mode 100644 src/Modern/Middleware/DataResponseFormatter.php delete mode 100644 src/Modern/ResponseFormatterInterface.php rename src/Modern/{Formatter => }/XmlDataInterface.php (95%) diff --git a/src/Modern/Formatter/HtmlResponseFormatter.php b/src/Modern/Formatter/HtmlResponseFormatter.php deleted file mode 100644 index 8bd84a6..0000000 --- a/src/Modern/Formatter/HtmlResponseFormatter.php +++ /dev/null @@ -1,49 +0,0 @@ -getBody() - ->write((string) $data); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/Formatter/JsonResponseFormatter.php b/src/Modern/Formatter/JsonResponseFormatter.php deleted file mode 100644 index b1fb9ee..0000000 --- a/src/Modern/Formatter/JsonResponseFormatter.php +++ /dev/null @@ -1,48 +0,0 @@ -getBody() - ->write(Json::encode($data, $this->options)); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/Formatter/PlainTextResponseFormatter.php b/src/Modern/Formatter/PlainTextResponseFormatter.php deleted file mode 100644 index 24a1008..0000000 --- a/src/Modern/Formatter/PlainTextResponseFormatter.php +++ /dev/null @@ -1,47 +0,0 @@ -getBody() - ->write((string) $data); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/Formatter/XmlResponseFormatter.php b/src/Modern/Formatter/XmlResponseFormatter.php deleted file mode 100644 index 39926ce..0000000 --- a/src/Modern/Formatter/XmlResponseFormatter.php +++ /dev/null @@ -1,221 +0,0 @@ -withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $dom = new DOMDocument($this->version, $this->encoding); - - if (empty($this->rootTag)) { - $this->buildXml($dom, $dom, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $root = new DOMElement($this->rootTag); - $dom->appendChild($root); - $this->buildXml($dom, $root, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - /** - * Builds the data to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param mixed $data Data for building XML. - */ - private function buildXml(DOMDocument $dom, $element, mixed $data): void - { - if (empty($data)) { - return; - } - - if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { - /** @var int|string $name */ - foreach ($data as $name => $value) { - if (is_object($value)) { - $this->buildObject($dom, $element, $value, $name); - continue; - } - - $child = $this->safeCreateDomElement($dom, $name); - $element->appendChild($child); - - if (is_array($value)) { - $this->buildXml($dom, $child, $value); - continue; - } - - /** @psalm-var scalar $value */ - - $this->setScalarValueToDomElement($child, $value); - } - - return; - } - - if (is_object($data)) { - $this->buildObject($dom, $element, $data); - return; - } - - /** @psalm-var scalar $data */ - - $this->setScalarValueToDomElement($element, $data); - } - - /** - * Builds the object to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param object $object To build. - * @param int|string|null $tagName The tag name. - */ - private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void - { - if ($object instanceof XmlDataInterface) { - $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); - - foreach ($object->xmlTagAttributes() as $name => $value) { - $child->setAttribute($name, $value); - } - - $element->appendChild($child); - $this->buildXml($dom, $child, $object->xmlData()); - return; - } - - $child = $this->safeCreateDomElement($dom, $tagName); - $element->appendChild($child); - - if ($object instanceof Traversable) { - $this->buildXml($dom, $child, $object); - return; - } - - $data = []; - - /** - * @var string $property - */ - foreach ($object as $property => $value) { - $data[$property] = $value; - } - - $this->buildXml($dom, $child, $data); - } - - /** - * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, - * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. - * - * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 - * - * @param DOMDocument $dom The root DOM document. - * @param int|string|null $tagName The tag name. - * - * @return DOMElement - */ - private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement - { - if (empty($tagName) || is_int($tagName)) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - - try { - if (!$element = $dom->createElement($tagName)) { - throw new DOMException(); - } - return $element; - } catch (DOMException) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - } - - /** - * Sets the scalar value to DOM Element instance if the value is not empty. - * - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param bool|float|int|string|null $value - */ - private function setScalarValueToDomElement($element, $value): void - { - $value = $this->formatScalarValue($value); - - if ($value !== '') { - $element->appendChild(new DOMText($value)); - } - } - - /** - * Formats scalar value for use in XML node. - * - * @param bool|float|int|string|null $value To format. - * - * @return string The string representation of the value. - */ - private function formatScalarValue($value): string - { - if ($value === true) { - return 'true'; - } - - if ($value === false) { - return 'false'; - } - - if (is_float($value)) { - return NumericHelper::normalize($value); - } - - return (string) $value; - } -} diff --git a/src/Modern/Middleware/ContentNegotiator.php b/src/Modern/Middleware/ContentNegotiator.php deleted file mode 100644 index 135a81e..0000000 --- a/src/Modern/Middleware/ContentNegotiator.php +++ /dev/null @@ -1,81 +0,0 @@ - $contentFormatters - */ - public function __construct( - private readonly array $contentFormatters, - ) { - $this->checkFormatters($contentFormatters); - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - if ($response instanceof DataResponse) { - $accepted = $request->getHeader(Header::ACCEPT); - - foreach ($accepted as $accept) { - foreach ($this->contentFormatters as $contentType => $formatter) { - if (str_contains($accept, $contentType)) { - return $formatter->format($response->data, $response->getResponse()); - } - } - } - } - - return $response; - } - - /** - * Checks the content formatters. - * - * @param array $contentFormatters The content formatters to check. - */ - private function checkFormatters(array $contentFormatters): void - { - foreach ($contentFormatters as $contentType => $formatter) { - if (!is_string($contentType)) { - throw new RuntimeException(sprintf( - 'Invalid formatter content type. A string is expected, "%s" is received.', - gettype($contentType), - )); - } - - if (!($formatter instanceof ResponseFormatterInterface)) { - throw new RuntimeException(sprintf( - 'Invalid formatter. A "%s" instance is expected, "%s" is received.', - ResponseFormatterInterface::class, - get_debug_type($formatter), - )); - } - } - } -} diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php deleted file mode 100644 index 6e8659d..0000000 --- a/src/Modern/Middleware/DataResponseFormatter.php +++ /dev/null @@ -1,28 +0,0 @@ -handle($request); - if ($response instanceof DataResponse) { - $response = $this->formatter->format($response->data, $response->getResponse()); - } - return $response; - } -} diff --git a/src/Modern/Middleware/HtmlDataResponseFormatter.php b/src/Modern/Middleware/HtmlDataResponseFormatter.php index 2fa3905..aa9fd14 100644 --- a/src/Modern/Middleware/HtmlDataResponseFormatter.php +++ b/src/Modern/Middleware/HtmlDataResponseFormatter.php @@ -4,15 +4,57 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\HtmlResponseFormatter; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use RuntimeException; +use Stringable; +use Yiisoft\DataResponse\Modern\DataResponse; +use Yiisoft\Http\Header; + +use function is_scalar; +use function sprintf; /** * Formats DataResponse as HTML. */ -final class HtmlDataResponseFormatter extends DataResponseFormatter +final class HtmlDataResponseFormatter implements MiddlewareInterface { - public function __construct(HtmlResponseFormatter $formatter = new HtmlResponseFormatter()) + /** + * @param string $contentType The Content-Type header for the response. + * @param string $encoding The encoding for the Content-Type header. + */ + public function __construct( + private readonly string $contentType = 'text/html', + private readonly string $encoding = 'UTF-8', + ) {} + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - parent::__construct($formatter); + $response = $handler->handle($request); + if (!$response instanceof DataResponse) { + return $response; + } + + $data = $response->data; + $response = $response->getResponse(); + + if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { + throw new RuntimeException( + sprintf( + 'Data must be either a scalar value, null, or a stringable object. %s given.', + get_debug_type($data), + ), + ); + } + + if (!empty($data)) { + $response + ->getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); } } diff --git a/src/Modern/Middleware/JsonDataResponseFormatter.php b/src/Modern/Middleware/JsonDataResponseFormatter.php index bfdc112..383ed0d 100644 --- a/src/Modern/Middleware/JsonDataResponseFormatter.php +++ b/src/Modern/Middleware/JsonDataResponseFormatter.php @@ -4,15 +4,54 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\JsonResponseFormatter; +use JsonException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Yiisoft\DataResponse\Modern\DataResponse; +use Yiisoft\Http\Header; +use Yiisoft\Json\Json; + +use const JSON_UNESCAPED_SLASHES; +use const JSON_UNESCAPED_UNICODE; /** * Formats DataResponse as JSON. */ -final class JsonDataResponseFormatter extends DataResponseFormatter +final class JsonDataResponseFormatter implements MiddlewareInterface { - public function __construct(JsonResponseFormatter $formatter = new JsonResponseFormatter()) + /** + * @param string $contentType The Content-Type header for the response. + * @param string $encoding The encoding for the Content-Type header. + * @param int $options The encoding options. For more details please refer to + * {@link https://www.php.net/manual/en/function.json-encode.php}. + */ + public function __construct( + private readonly string $contentType = 'application/json', + private readonly string $encoding = 'UTF-8', + private readonly int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, + ) {} + + /** + * @throws JsonException + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - parent::__construct($formatter); + $response = $handler->handle($request); + if (!$response instanceof DataResponse) { + return $response; + } + + $data = $response->data; + $response = $response->getResponse(); + + if ($data !== null) { + $response + ->getBody() + ->write(Json::encode($data, $this->options)); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); } } diff --git a/src/Modern/Middleware/PlainTextDataResponseFormatter.php b/src/Modern/Middleware/PlainTextDataResponseFormatter.php index de34218..f6e73ec 100644 --- a/src/Modern/Middleware/PlainTextDataResponseFormatter.php +++ b/src/Modern/Middleware/PlainTextDataResponseFormatter.php @@ -4,15 +4,55 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextResponseFormatter; +use LogicException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Stringable; +use Yiisoft\DataResponse\Modern\DataResponse; +use Yiisoft\Http\Header; + +use function is_scalar; +use function sprintf; /** * Formats DataResponse as plain text. */ -final class PlainTextDataResponseFormatter extends DataResponseFormatter +final class PlainTextDataResponseFormatter implements MiddlewareInterface { - public function __construct(PlainTextResponseFormatter $formatter = new PlainTextResponseFormatter()) + /** + * @param string $contentType The Content-Type header for the response. + * @param string $encoding The encoding for the Content-Type header. + */ + public function __construct( + private readonly string $contentType = 'text/plain', + private readonly string $encoding = 'UTF-8', + ) {} + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - parent::__construct($formatter); + $response = $handler->handle($request); + if (!$response instanceof DataResponse) { + return $response; + } + + $data = $response->data; + $response = $response->getResponse(); + + if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { + throw new LogicException(sprintf( + 'Data must be either a scalar value, null, or a stringable object. %s given.', + get_debug_type($data), + )); + } + + if (!empty($data)) { + $response + ->getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); } } diff --git a/src/Modern/Middleware/XmlDataResponseFormatter.php b/src/Modern/Middleware/XmlDataResponseFormatter.php index 68e702f..c7a55b2 100644 --- a/src/Modern/Middleware/XmlDataResponseFormatter.php +++ b/src/Modern/Middleware/XmlDataResponseFormatter.php @@ -4,15 +4,230 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\XmlResponseFormatter; +use DOMDocument; +use DOMElement; +use DOMException; +use DOMText; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Traversable; +use Yiisoft\DataResponse\Modern\DataResponse; +use Yiisoft\DataResponse\Modern\XmlDataInterface; +use Yiisoft\Http\Header; +use Yiisoft\Strings\NumericHelper; + +use function is_array; +use function is_float; +use function is_int; +use function is_object; /** * Formats DataResponse as XML. */ -final class XmlDataResponseFormatter extends DataResponseFormatter +final class XmlDataResponseFormatter implements MiddlewareInterface { - public function __construct(XmlResponseFormatter $formatter = new XmlResponseFormatter()) + private const DEFAULT_ITEM_TAG_NAME = 'item'; + + /** + * @param string $contentType The Content-Type header for the response. + * @param string $encoding The encoding for the Content-Type header. + * @param string $version The XML version. + * @param string $rootTag The name of the root element. If an empty value is set, the root tag should not be added. + */ + public function __construct( + private readonly string $contentType = 'application/xml', + private readonly string $encoding = 'UTF-8', + private readonly string $version = '1.0', + private readonly string $rootTag = 'response', + ) {} + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + if (!$response instanceof DataResponse) { + return $response; + } + + $data = $response->data; + $response = $response->getResponse(); + + if (empty($data)) { + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $dom = new DOMDocument($this->version, $this->encoding); + + if (empty($this->rootTag)) { + $this->buildXml($dom, $dom, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $root = new DOMElement($this->rootTag); + $dom->appendChild($root); + $this->buildXml($dom, $root, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + /** + * Builds the data to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param mixed $data Data for building XML. + */ + private function buildXml(DOMDocument $dom, $element, mixed $data): void + { + if (empty($data)) { + return; + } + + if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { + /** @var int|string $name */ + foreach ($data as $name => $value) { + if (is_object($value)) { + $this->buildObject($dom, $element, $value, $name); + continue; + } + + $child = $this->safeCreateDomElement($dom, $name); + $element->appendChild($child); + + if (is_array($value)) { + $this->buildXml($dom, $child, $value); + continue; + } + + /** @psalm-var scalar $value */ + + $this->setScalarValueToDomElement($child, $value); + } + + return; + } + + if (is_object($data)) { + $this->buildObject($dom, $element, $data); + return; + } + + /** @psalm-var scalar $data */ + + $this->setScalarValueToDomElement($element, $data); + } + + /** + * Builds the object to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param object $object To build. + * @param int|string|null $tagName The tag name. + */ + private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void + { + if ($object instanceof XmlDataInterface) { + $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); + + foreach ($object->xmlTagAttributes() as $name => $value) { + $child->setAttribute($name, $value); + } + + $element->appendChild($child); + $this->buildXml($dom, $child, $object->xmlData()); + return; + } + + $child = $this->safeCreateDomElement($dom, $tagName); + $element->appendChild($child); + + if ($object instanceof Traversable) { + $this->buildXml($dom, $child, $object); + return; + } + + $data = []; + + /** + * @var string $property + */ + foreach ($object as $property => $value) { + $data[$property] = $value; + } + + $this->buildXml($dom, $child, $data); + } + + /** + * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, + * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. + * + * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 + * + * @param DOMDocument $dom The root DOM document. + * @param int|string|null $tagName The tag name. + * + * @return DOMElement + */ + private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement { - parent::__construct($formatter); + if (empty($tagName) || is_int($tagName)) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + + try { + if (!$element = $dom->createElement($tagName)) { + throw new DOMException(); + } + return $element; + } catch (DOMException) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + } + + /** + * Sets the scalar value to DOM Element instance if the value is not empty. + * + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param bool|float|int|string|null $value + */ + private function setScalarValueToDomElement($element, $value): void + { + $value = $this->formatScalarValue($value); + + if ($value !== '') { + $element->appendChild(new DOMText($value)); + } + } + + /** + * Formats scalar value for use in XML node. + * + * @param bool|float|int|string|null $value To format. + * + * @return string The string representation of the value. + */ + private function formatScalarValue($value): string + { + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value)) { + return NumericHelper::normalize($value); + } + + return (string) $value; } } diff --git a/src/Modern/ResponseFormatterInterface.php b/src/Modern/ResponseFormatterInterface.php deleted file mode 100644 index dbf5db2..0000000 --- a/src/Modern/ResponseFormatterInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - Date: Tue, 20 Jan 2026 16:42:18 +0300 Subject: [PATCH 06/64] improve --- src/Modern/DataResponse.php | 14 +++++++++- src/Modern/DataResponseFactory.php | 26 +++++++++++++++++++ src/Modern/DataResponseFactoryInterface.php | 24 +++++++++++++++++ .../Middleware/HtmlDataResponseFormatter.php | 10 ++++--- .../Middleware/JsonDataResponseFormatter.php | 10 ++++--- .../PlainTextDataResponseFormatter.php | 10 ++++--- .../Middleware/XmlDataResponseFormatter.php | 16 +++++++----- 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 src/Modern/DataResponseFactory.php create mode 100644 src/Modern/DataResponseFactoryInterface.php diff --git a/src/Modern/DataResponse.php b/src/Modern/DataResponse.php index ac6b02e..33e8e5c 100644 --- a/src/Modern/DataResponse.php +++ b/src/Modern/DataResponse.php @@ -11,10 +11,22 @@ final class DataResponse implements ResponseInterface { public function __construct( - public readonly mixed $data, private ResponseInterface $response, + private mixed $data, ) {} + public function getData(): mixed + { + return $this->data; + } + + public function withData(mixed $data): self + { + $new = clone $this; + $new->data = $data; + return $new; + } + public function getProtocolVersion(): string { return $this->response->getProtocolVersion(); diff --git a/src/Modern/DataResponseFactory.php b/src/Modern/DataResponseFactory.php new file mode 100644 index 0000000..f23d22e --- /dev/null +++ b/src/Modern/DataResponseFactory.php @@ -0,0 +1,26 @@ +responseFactory->createResponse($code, $reasonPhrase), + $data, + ); + } +} diff --git a/src/Modern/DataResponseFactoryInterface.php b/src/Modern/DataResponseFactoryInterface.php new file mode 100644 index 0000000..8d7466b --- /dev/null +++ b/src/Modern/DataResponseFactoryInterface.php @@ -0,0 +1,24 @@ +data; + $data = $response->getData(); $response = $response->getResponse(); if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { @@ -50,9 +52,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } if (!empty($data)) { - $response - ->getBody() - ->write((string) $data); + $response = $response->withBody( + $this->streamFactory->createStream((string) $data), + ); } return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); diff --git a/src/Modern/Middleware/JsonDataResponseFormatter.php b/src/Modern/Middleware/JsonDataResponseFormatter.php index 383ed0d..97b24b2 100644 --- a/src/Modern/Middleware/JsonDataResponseFormatter.php +++ b/src/Modern/Middleware/JsonDataResponseFormatter.php @@ -7,6 +7,7 @@ use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Yiisoft\DataResponse\Modern\DataResponse; @@ -28,6 +29,7 @@ final class JsonDataResponseFormatter implements MiddlewareInterface * {@link https://www.php.net/manual/en/function.json-encode.php}. */ public function __construct( + private readonly StreamFactoryInterface $streamFactory, private readonly string $contentType = 'application/json', private readonly string $encoding = 'UTF-8', private readonly int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, @@ -43,13 +45,13 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - $data = $response->data; + $data = $response->getData(); $response = $response->getResponse(); if ($data !== null) { - $response - ->getBody() - ->write(Json::encode($data, $this->options)); + $response = $response->withBody( + $this->streamFactory->createStream(Json::encode($data, $this->options)), + ); } return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); diff --git a/src/Modern/Middleware/PlainTextDataResponseFormatter.php b/src/Modern/Middleware/PlainTextDataResponseFormatter.php index f6e73ec..8d3e11b 100644 --- a/src/Modern/Middleware/PlainTextDataResponseFormatter.php +++ b/src/Modern/Middleware/PlainTextDataResponseFormatter.php @@ -7,6 +7,7 @@ use LogicException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Stringable; @@ -26,6 +27,7 @@ final class PlainTextDataResponseFormatter implements MiddlewareInterface * @param string $encoding The encoding for the Content-Type header. */ public function __construct( + private readonly StreamFactoryInterface $streamFactory, private readonly string $contentType = 'text/plain', private readonly string $encoding = 'UTF-8', ) {} @@ -37,7 +39,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - $data = $response->data; + $data = $response->getData(); $response = $response->getResponse(); if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { @@ -48,9 +50,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } if (!empty($data)) { - $response - ->getBody() - ->write((string) $data); + $response = $response->withBody( + $this->streamFactory->createStream((string) $data), + ); } return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); diff --git a/src/Modern/Middleware/XmlDataResponseFormatter.php b/src/Modern/Middleware/XmlDataResponseFormatter.php index c7a55b2..73ad275 100644 --- a/src/Modern/Middleware/XmlDataResponseFormatter.php +++ b/src/Modern/Middleware/XmlDataResponseFormatter.php @@ -10,6 +10,7 @@ use DOMText; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Traversable; @@ -37,6 +38,7 @@ final class XmlDataResponseFormatter implements MiddlewareInterface * @param string $rootTag The name of the root element. If an empty value is set, the root tag should not be added. */ public function __construct( + private readonly StreamFactoryInterface $streamFactory, private readonly string $contentType = 'application/xml', private readonly string $encoding = 'UTF-8', private readonly string $version = '1.0', @@ -50,7 +52,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - $data = $response->data; + $data = $response->getData(); $response = $response->getResponse(); if (empty($data)) { @@ -61,18 +63,18 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if (empty($this->rootTag)) { $this->buildXml($dom, $dom, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); + $response = $response->withBody( + $this->streamFactory->createStream((string) $dom->saveXML()), + ); return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); } $root = new DOMElement($this->rootTag); $dom->appendChild($root); $this->buildXml($dom, $root, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); + $response = $response->withBody( + $this->streamFactory->createStream((string) $dom->saveXML()), + ); return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); } From 67e783fadd5644246d4160d913dd298f19891dd7 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 25 Jan 2026 11:26:30 +0300 Subject: [PATCH 07/64] revert formatters --- .../Formatter/HtmlResponseFormatter.php | 49 ++++ .../Formatter/JsonResponseFormatter.php | 46 ++++ .../Formatter/PlainTextResponseFormatter.php | 47 ++++ src/Modern/Formatter/XmlResponseFormatter.php | 223 +++++++++++++++++ .../Middleware/DataResponseFormatter.php | 28 +++ .../Middleware/HtmlDataResponseFormatter.php | 52 +--- .../Middleware/JsonDataResponseFormatter.php | 49 +--- .../PlainTextDataResponseFormatter.php | 50 +--- .../Middleware/XmlDataResponseFormatter.php | 225 +----------------- src/Modern/ResponseFormatterInterface.php | 23 ++ 10 files changed, 432 insertions(+), 360 deletions(-) create mode 100644 src/Modern/Formatter/HtmlResponseFormatter.php create mode 100644 src/Modern/Formatter/JsonResponseFormatter.php create mode 100644 src/Modern/Formatter/PlainTextResponseFormatter.php create mode 100644 src/Modern/Formatter/XmlResponseFormatter.php create mode 100644 src/Modern/Middleware/DataResponseFormatter.php create mode 100644 src/Modern/ResponseFormatterInterface.php diff --git a/src/Modern/Formatter/HtmlResponseFormatter.php b/src/Modern/Formatter/HtmlResponseFormatter.php new file mode 100644 index 0000000..65aa74f --- /dev/null +++ b/src/Modern/Formatter/HtmlResponseFormatter.php @@ -0,0 +1,49 @@ +getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/JsonResponseFormatter.php b/src/Modern/Formatter/JsonResponseFormatter.php new file mode 100644 index 0000000..fdecefd --- /dev/null +++ b/src/Modern/Formatter/JsonResponseFormatter.php @@ -0,0 +1,46 @@ +getBody() + ->write(Json::encode($data, $this->options)); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/PlainTextResponseFormatter.php b/src/Modern/Formatter/PlainTextResponseFormatter.php new file mode 100644 index 0000000..bd06bb8 --- /dev/null +++ b/src/Modern/Formatter/PlainTextResponseFormatter.php @@ -0,0 +1,47 @@ +getBody() + ->write((string) $data); + } + + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/XmlResponseFormatter.php b/src/Modern/Formatter/XmlResponseFormatter.php new file mode 100644 index 0000000..ab003a4 --- /dev/null +++ b/src/Modern/Formatter/XmlResponseFormatter.php @@ -0,0 +1,223 @@ +withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $dom = new DOMDocument($this->version, $this->encoding); + + if (empty($this->rootTag)) { + $this->buildXml($dom, $dom, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + $root = new DOMElement($this->rootTag); + $dom->appendChild($root); + $this->buildXml($dom, $root, $data); + $response + ->getBody() + ->write((string) $dom->saveXML()); + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + + /** + * Builds the data to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param mixed $data Data for building XML. + */ + private function buildXml(DOMDocument $dom, $element, mixed $data): void + { + if (empty($data)) { + return; + } + + if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { + /** @var int|string $name */ + foreach ($data as $name => $value) { + if (is_object($value)) { + $this->buildObject($dom, $element, $value, $name); + continue; + } + + $child = $this->safeCreateDomElement($dom, $name); + $element->appendChild($child); + + if (is_array($value)) { + $this->buildXml($dom, $child, $value); + continue; + } + + /** @psalm-var scalar $value */ + + $this->setScalarValueToDomElement($child, $value); + } + + return; + } + + if (is_object($data)) { + $this->buildObject($dom, $element, $data); + return; + } + + /** @psalm-var scalar $data */ + + $this->setScalarValueToDomElement($element, $data); + } + + /** + * Builds the object to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param object $object To build. + * @param int|string|null $tagName The tag name. + */ + private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void + { + if ($object instanceof XmlDataInterface) { + $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); + + foreach ($object->xmlTagAttributes() as $name => $value) { + $child->setAttribute($name, $value); + } + + $element->appendChild($child); + $this->buildXml($dom, $child, $object->xmlData()); + return; + } + + $child = $this->safeCreateDomElement($dom, $tagName); + $element->appendChild($child); + + if ($object instanceof Traversable) { + $this->buildXml($dom, $child, $object); + return; + } + + $data = []; + + /** + * @var string $property + */ + foreach ($object as $property => $value) { + $data[$property] = $value; + } + + $this->buildXml($dom, $child, $data); + } + + /** + * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, + * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. + * + * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 + * + * @param DOMDocument $dom The root DOM document. + * @param int|string|null $tagName The tag name. + * + * @return DOMElement + */ + private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement + { + if (empty($tagName) || is_int($tagName)) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + + try { + if (!$element = $dom->createElement($tagName)) { + throw new DOMException(); + } + return $element; + } catch (DOMException) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + } + + /** + * Sets the scalar value to DOM Element instance if the value is not empty. + * + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param bool|float|int|string|null $value + */ + private function setScalarValueToDomElement($element, $value): void + { + $value = $this->formatScalarValue($value); + + if ($value !== '') { + $element->appendChild(new DOMText($value)); + } + } + + /** + * Formats scalar value for use in XML node. + * + * @param bool|float|int|string|null $value To format. + * + * @return string The string representation of the value. + */ + private function formatScalarValue($value): string + { + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value)) { + return NumericHelper::normalize($value); + } + + return (string) $value; + } +} diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php new file mode 100644 index 0000000..3c606b1 --- /dev/null +++ b/src/Modern/Middleware/DataResponseFormatter.php @@ -0,0 +1,28 @@ +handle($request); + if ($response instanceof DataResponse) { + $response = $this->defaultFormatter->format($response->getData(), $response->getResponse()); + } + return $response; + } +} diff --git a/src/Modern/Middleware/HtmlDataResponseFormatter.php b/src/Modern/Middleware/HtmlDataResponseFormatter.php index ad9ddd2..2fa3905 100644 --- a/src/Modern/Middleware/HtmlDataResponseFormatter.php +++ b/src/Modern/Middleware/HtmlDataResponseFormatter.php @@ -4,59 +4,15 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use RuntimeException; -use Stringable; -use Yiisoft\DataResponse\Modern\DataResponse; -use Yiisoft\Http\Header; - -use function is_scalar; -use function sprintf; +use Yiisoft\DataResponse\Modern\Formatter\HtmlResponseFormatter; /** * Formats DataResponse as HTML. */ -final class HtmlDataResponseFormatter implements MiddlewareInterface +final class HtmlDataResponseFormatter extends DataResponseFormatter { - /** - * @param string $contentType The Content-Type header for the response. - * @param string $encoding The encoding for the Content-Type header. - */ - public function __construct( - private readonly StreamFactoryInterface $streamFactory, - private readonly string $contentType = 'text/html', - private readonly string $encoding = 'UTF-8', - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(HtmlResponseFormatter $formatter = new HtmlResponseFormatter()) { - $response = $handler->handle($request); - if (!$response instanceof DataResponse) { - return $response; - } - - $data = $response->getData(); - $response = $response->getResponse(); - - if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { - throw new RuntimeException( - sprintf( - 'Data must be either a scalar value, null, or a stringable object. %s given.', - get_debug_type($data), - ), - ); - } - - if (!empty($data)) { - $response = $response->withBody( - $this->streamFactory->createStream((string) $data), - ); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/JsonDataResponseFormatter.php b/src/Modern/Middleware/JsonDataResponseFormatter.php index 97b24b2..bfdc112 100644 --- a/src/Modern/Middleware/JsonDataResponseFormatter.php +++ b/src/Modern/Middleware/JsonDataResponseFormatter.php @@ -4,56 +4,15 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use JsonException; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponse; -use Yiisoft\Http\Header; -use Yiisoft\Json\Json; - -use const JSON_UNESCAPED_SLASHES; -use const JSON_UNESCAPED_UNICODE; +use Yiisoft\DataResponse\Modern\Formatter\JsonResponseFormatter; /** * Formats DataResponse as JSON. */ -final class JsonDataResponseFormatter implements MiddlewareInterface +final class JsonDataResponseFormatter extends DataResponseFormatter { - /** - * @param string $contentType The Content-Type header for the response. - * @param string $encoding The encoding for the Content-Type header. - * @param int $options The encoding options. For more details please refer to - * {@link https://www.php.net/manual/en/function.json-encode.php}. - */ - public function __construct( - private readonly StreamFactoryInterface $streamFactory, - private readonly string $contentType = 'application/json', - private readonly string $encoding = 'UTF-8', - private readonly int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, - ) {} - - /** - * @throws JsonException - */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(JsonResponseFormatter $formatter = new JsonResponseFormatter()) { - $response = $handler->handle($request); - if (!$response instanceof DataResponse) { - return $response; - } - - $data = $response->getData(); - $response = $response->getResponse(); - - if ($data !== null) { - $response = $response->withBody( - $this->streamFactory->createStream(Json::encode($data, $this->options)), - ); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/PlainTextDataResponseFormatter.php b/src/Modern/Middleware/PlainTextDataResponseFormatter.php index 8d3e11b..de34218 100644 --- a/src/Modern/Middleware/PlainTextDataResponseFormatter.php +++ b/src/Modern/Middleware/PlainTextDataResponseFormatter.php @@ -4,57 +4,15 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use LogicException; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Stringable; -use Yiisoft\DataResponse\Modern\DataResponse; -use Yiisoft\Http\Header; - -use function is_scalar; -use function sprintf; +use Yiisoft\DataResponse\Modern\Formatter\PlainTextResponseFormatter; /** * Formats DataResponse as plain text. */ -final class PlainTextDataResponseFormatter implements MiddlewareInterface +final class PlainTextDataResponseFormatter extends DataResponseFormatter { - /** - * @param string $contentType The Content-Type header for the response. - * @param string $encoding The encoding for the Content-Type header. - */ - public function __construct( - private readonly StreamFactoryInterface $streamFactory, - private readonly string $contentType = 'text/plain', - private readonly string $encoding = 'UTF-8', - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(PlainTextResponseFormatter $formatter = new PlainTextResponseFormatter()) { - $response = $handler->handle($request); - if (!$response instanceof DataResponse) { - return $response; - } - - $data = $response->getData(); - $response = $response->getResponse(); - - if (!is_scalar($data) && $data !== null && !$data instanceof Stringable) { - throw new LogicException(sprintf( - 'Data must be either a scalar value, null, or a stringable object. %s given.', - get_debug_type($data), - )); - } - - if (!empty($data)) { - $response = $response->withBody( - $this->streamFactory->createStream((string) $data), - ); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/XmlDataResponseFormatter.php b/src/Modern/Middleware/XmlDataResponseFormatter.php index 73ad275..68e702f 100644 --- a/src/Modern/Middleware/XmlDataResponseFormatter.php +++ b/src/Modern/Middleware/XmlDataResponseFormatter.php @@ -4,232 +4,15 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use DOMDocument; -use DOMElement; -use DOMException; -use DOMText; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Traversable; -use Yiisoft\DataResponse\Modern\DataResponse; -use Yiisoft\DataResponse\Modern\XmlDataInterface; -use Yiisoft\Http\Header; -use Yiisoft\Strings\NumericHelper; - -use function is_array; -use function is_float; -use function is_int; -use function is_object; +use Yiisoft\DataResponse\Modern\Formatter\XmlResponseFormatter; /** * Formats DataResponse as XML. */ -final class XmlDataResponseFormatter implements MiddlewareInterface +final class XmlDataResponseFormatter extends DataResponseFormatter { - private const DEFAULT_ITEM_TAG_NAME = 'item'; - - /** - * @param string $contentType The Content-Type header for the response. - * @param string $encoding The encoding for the Content-Type header. - * @param string $version The XML version. - * @param string $rootTag The name of the root element. If an empty value is set, the root tag should not be added. - */ - public function __construct( - private readonly StreamFactoryInterface $streamFactory, - private readonly string $contentType = 'application/xml', - private readonly string $encoding = 'UTF-8', - private readonly string $version = '1.0', - private readonly string $rootTag = 'response', - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - if (!$response instanceof DataResponse) { - return $response; - } - - $data = $response->getData(); - $response = $response->getResponse(); - - if (empty($data)) { - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $dom = new DOMDocument($this->version, $this->encoding); - - if (empty($this->rootTag)) { - $this->buildXml($dom, $dom, $data); - $response = $response->withBody( - $this->streamFactory->createStream((string) $dom->saveXML()), - ); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $root = new DOMElement($this->rootTag); - $dom->appendChild($root); - $this->buildXml($dom, $root, $data); - $response = $response->withBody( - $this->streamFactory->createStream((string) $dom->saveXML()), - ); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - /** - * Builds the data to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param mixed $data Data for building XML. - */ - private function buildXml(DOMDocument $dom, $element, mixed $data): void - { - if (empty($data)) { - return; - } - - if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { - /** @var int|string $name */ - foreach ($data as $name => $value) { - if (is_object($value)) { - $this->buildObject($dom, $element, $value, $name); - continue; - } - - $child = $this->safeCreateDomElement($dom, $name); - $element->appendChild($child); - - if (is_array($value)) { - $this->buildXml($dom, $child, $value); - continue; - } - - /** @psalm-var scalar $value */ - - $this->setScalarValueToDomElement($child, $value); - } - - return; - } - - if (is_object($data)) { - $this->buildObject($dom, $element, $data); - return; - } - - /** @psalm-var scalar $data */ - - $this->setScalarValueToDomElement($element, $data); - } - - /** - * Builds the object to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param object $object To build. - * @param int|string|null $tagName The tag name. - */ - private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void - { - if ($object instanceof XmlDataInterface) { - $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); - - foreach ($object->xmlTagAttributes() as $name => $value) { - $child->setAttribute($name, $value); - } - - $element->appendChild($child); - $this->buildXml($dom, $child, $object->xmlData()); - return; - } - - $child = $this->safeCreateDomElement($dom, $tagName); - $element->appendChild($child); - - if ($object instanceof Traversable) { - $this->buildXml($dom, $child, $object); - return; - } - - $data = []; - - /** - * @var string $property - */ - foreach ($object as $property => $value) { - $data[$property] = $value; - } - - $this->buildXml($dom, $child, $data); - } - - /** - * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, - * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. - * - * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 - * - * @param DOMDocument $dom The root DOM document. - * @param int|string|null $tagName The tag name. - * - * @return DOMElement - */ - private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement + public function __construct(XmlResponseFormatter $formatter = new XmlResponseFormatter()) { - if (empty($tagName) || is_int($tagName)) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - - try { - if (!$element = $dom->createElement($tagName)) { - throw new DOMException(); - } - return $element; - } catch (DOMException) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - } - - /** - * Sets the scalar value to DOM Element instance if the value is not empty. - * - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param bool|float|int|string|null $value - */ - private function setScalarValueToDomElement($element, $value): void - { - $value = $this->formatScalarValue($value); - - if ($value !== '') { - $element->appendChild(new DOMText($value)); - } - } - - /** - * Formats scalar value for use in XML node. - * - * @param bool|float|int|string|null $value To format. - * - * @return string The string representation of the value. - */ - private function formatScalarValue($value): string - { - if ($value === true) { - return 'true'; - } - - if ($value === false) { - return 'false'; - } - - if (is_float($value)) { - return NumericHelper::normalize($value); - } - - return (string) $value; + parent::__construct($formatter); } } diff --git a/src/Modern/ResponseFormatterInterface.php b/src/Modern/ResponseFormatterInterface.php new file mode 100644 index 0000000..dbf5db2 --- /dev/null +++ b/src/Modern/ResponseFormatterInterface.php @@ -0,0 +1,23 @@ + Date: Sun, 25 Jan 2026 11:29:03 +0300 Subject: [PATCH 08/64] Rename formatter files and update namespaces to improve organization --- .../HtmlResponseFormatter.php | 2 +- .../JsonResponseFormatter.php | 2 +- .../PlainTextResponseFormatter.php | 2 +- .../XmlResponseFormatter.php | 2 +- src/Modern/Middleware/HtmlDataResponseFormatter.php | 2 +- src/Modern/Middleware/JsonDataResponseFormatter.php | 2 +- src/Modern/Middleware/PlainTextDataResponseFormatter.php | 2 +- src/Modern/Middleware/XmlDataResponseFormatter.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename src/Modern/{Formatter => DataResponseFormatter}/HtmlResponseFormatter.php (95%) rename src/Modern/{Formatter => DataResponseFormatter}/JsonResponseFormatter.php (95%) rename src/Modern/{Formatter => DataResponseFormatter}/PlainTextResponseFormatter.php (95%) rename src/Modern/{Formatter => DataResponseFormatter}/XmlResponseFormatter.php (99%) diff --git a/src/Modern/Formatter/HtmlResponseFormatter.php b/src/Modern/DataResponseFormatter/HtmlResponseFormatter.php similarity index 95% rename from src/Modern/Formatter/HtmlResponseFormatter.php rename to src/Modern/DataResponseFormatter/HtmlResponseFormatter.php index 65aa74f..6567ce3 100644 --- a/src/Modern/Formatter/HtmlResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/HtmlResponseFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; use Psr\Http\Message\ResponseInterface; use RuntimeException; diff --git a/src/Modern/Formatter/JsonResponseFormatter.php b/src/Modern/DataResponseFormatter/JsonResponseFormatter.php similarity index 95% rename from src/Modern/Formatter/JsonResponseFormatter.php rename to src/Modern/DataResponseFormatter/JsonResponseFormatter.php index fdecefd..4a0ccee 100644 --- a/src/Modern/Formatter/JsonResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/JsonResponseFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; use JsonException; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/Formatter/PlainTextResponseFormatter.php b/src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php similarity index 95% rename from src/Modern/Formatter/PlainTextResponseFormatter.php rename to src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php index bd06bb8..48426e6 100644 --- a/src/Modern/Formatter/PlainTextResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; use LogicException; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/Formatter/XmlResponseFormatter.php b/src/Modern/DataResponseFormatter/XmlResponseFormatter.php similarity index 99% rename from src/Modern/Formatter/XmlResponseFormatter.php rename to src/Modern/DataResponseFormatter/XmlResponseFormatter.php index ab003a4..cd2b5a8 100644 --- a/src/Modern/Formatter/XmlResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/XmlResponseFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; use DOMDocument; use DOMElement; diff --git a/src/Modern/Middleware/HtmlDataResponseFormatter.php b/src/Modern/Middleware/HtmlDataResponseFormatter.php index 2fa3905..daac710 100644 --- a/src/Modern/Middleware/HtmlDataResponseFormatter.php +++ b/src/Modern/Middleware/HtmlDataResponseFormatter.php @@ -4,7 +4,7 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\HtmlResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponseFormatter\HtmlResponseFormatter; /** * Formats DataResponse as HTML. diff --git a/src/Modern/Middleware/JsonDataResponseFormatter.php b/src/Modern/Middleware/JsonDataResponseFormatter.php index bfdc112..b680090 100644 --- a/src/Modern/Middleware/JsonDataResponseFormatter.php +++ b/src/Modern/Middleware/JsonDataResponseFormatter.php @@ -4,7 +4,7 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\JsonResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponseFormatter\JsonResponseFormatter; /** * Formats DataResponse as JSON. diff --git a/src/Modern/Middleware/PlainTextDataResponseFormatter.php b/src/Modern/Middleware/PlainTextDataResponseFormatter.php index de34218..ec7f292 100644 --- a/src/Modern/Middleware/PlainTextDataResponseFormatter.php +++ b/src/Modern/Middleware/PlainTextDataResponseFormatter.php @@ -4,7 +4,7 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponseFormatter\PlainTextResponseFormatter; /** * Formats DataResponse as plain text. diff --git a/src/Modern/Middleware/XmlDataResponseFormatter.php b/src/Modern/Middleware/XmlDataResponseFormatter.php index 68e702f..c09cc65 100644 --- a/src/Modern/Middleware/XmlDataResponseFormatter.php +++ b/src/Modern/Middleware/XmlDataResponseFormatter.php @@ -4,7 +4,7 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\XmlResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponseFormatter\XmlResponseFormatter; /** * Formats DataResponse as XML. From cdf773f28d17eddc18f3a78f0135d2af2505fa05 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 25 Jan 2026 16:03:27 +0300 Subject: [PATCH 09/64] Add DataStream --- .../DataFormatter/JsonDataFormatter.php | 38 ++++ .../DataFormatter/StringDataFormatter.php | 39 ++++ src/Modern/DataFormatter/XmlDataFormatter.php | 212 ++++++++++++++++++ src/Modern/DataFormatterInterface.php | 24 ++ src/Modern/DataStream.php | 155 +++++++++++++ src/Modern/StringStream.php | 163 ++++++++++++++ 6 files changed, 631 insertions(+) create mode 100644 src/Modern/DataFormatter/JsonDataFormatter.php create mode 100644 src/Modern/DataFormatter/StringDataFormatter.php create mode 100644 src/Modern/DataFormatter/XmlDataFormatter.php create mode 100644 src/Modern/DataFormatterInterface.php create mode 100644 src/Modern/DataStream.php create mode 100644 src/Modern/StringStream.php diff --git a/src/Modern/DataFormatter/JsonDataFormatter.php b/src/Modern/DataFormatter/JsonDataFormatter.php new file mode 100644 index 0000000..f9d291f --- /dev/null +++ b/src/Modern/DataFormatter/JsonDataFormatter.php @@ -0,0 +1,38 @@ +options); + } +} diff --git a/src/Modern/DataFormatter/StringDataFormatter.php b/src/Modern/DataFormatter/StringDataFormatter.php new file mode 100644 index 0000000..1e13d3c --- /dev/null +++ b/src/Modern/DataFormatter/StringDataFormatter.php @@ -0,0 +1,39 @@ +version, $this->encoding); + + if (empty($this->rootTag)) { + $this->buildXml($dom, $dom, $data); + } else { + $root = new DOMElement($this->rootTag); + $dom->appendChild($root); + $this->buildXml($dom, $root, $data); + } + + return (string) $dom->saveXML(); + } + + /** + * Builds the data to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param mixed $data Data for building XML. + */ + private function buildXml(DOMDocument $dom, $element, mixed $data): void + { + if (empty($data)) { + return; + } + + if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { + /** @var int|string $name */ + foreach ($data as $name => $value) { + if (is_object($value)) { + $this->buildObject($dom, $element, $value, $name); + continue; + } + + $child = $this->safeCreateDomElement($dom, $name); + $element->appendChild($child); + + if (is_array($value)) { + $this->buildXml($dom, $child, $value); + continue; + } + + /** @psalm-var scalar $value */ + + $this->setScalarValueToDomElement($child, $value); + } + + return; + } + + if (is_object($data)) { + $this->buildObject($dom, $element, $data); + return; + } + + /** @psalm-var scalar $data */ + + $this->setScalarValueToDomElement($element, $data); + } + + /** + * Builds the object to use in XML. + * + * @param DOMDocument $dom The root DOM document. + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param object $object To build. + * @param int|string|null $tagName The tag name. + */ + private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void + { + if ($object instanceof XmlDataInterface) { + $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); + + foreach ($object->xmlTagAttributes() as $name => $value) { + $child->setAttribute($name, $value); + } + + $element->appendChild($child); + $this->buildXml($dom, $child, $object->xmlData()); + return; + } + + $child = $this->safeCreateDomElement($dom, $tagName); + $element->appendChild($child); + + if ($object instanceof Traversable) { + $this->buildXml($dom, $child, $object); + return; + } + + $data = []; + + /** + * @var string $property + */ + foreach ($object as $property => $value) { + $data[$property] = $value; + } + + $this->buildXml($dom, $child, $data); + } + + /** + * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, + * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. + * + * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 + * + * @param DOMDocument $dom The root DOM document. + * @param int|string|null $tagName The tag name. + * + * @return DOMElement + */ + private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement + { + if (empty($tagName) || is_int($tagName)) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + + try { + if (!$element = $dom->createElement($tagName)) { + throw new DOMException(); + } + return $element; + } catch (DOMException) { + return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); + } + } + + /** + * Sets the scalar value to DOM Element instance if the value is not empty. + * + * @param DOMDocument|DOMElement $element The current DOM element being processed. + * @param bool|float|int|string|null $value + */ + private function setScalarValueToDomElement($element, $value): void + { + $value = $this->formatScalarValue($value); + + if ($value !== '') { + $element->appendChild(new DOMText($value)); + } + } + + /** + * Formats scalar value for use in XML node. + * + * @param bool|float|int|string|null $value To format. + * + * @return string The string representation of the value. + */ + private function formatScalarValue($value): string + { + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value)) { + return NumericHelper::normalize($value); + } + + return (string) $value; + } +} diff --git a/src/Modern/DataFormatterInterface.php b/src/Modern/DataFormatterInterface.php new file mode 100644 index 0000000..140e150 --- /dev/null +++ b/src/Modern/DataFormatterInterface.php @@ -0,0 +1,24 @@ +formatter = $formatter; + $this->resetState(); + } + + /** + * Changes the data and resets the stream state. + * + * @param mixed $data The new data to be formatted. + */ + public function changeData(mixed $data): void + { + $this->data = $data; + $this->resetState(); + } + + public function __toString(): string + { + return (string) $this->getFormatted(); + } + + public function close(): void + { + $this->getFormatted()->close(); + } + + public function detach() + { + return $this->getFormatted()->detach(); + } + + public function getSize(): ?int + { + return $this->getFormatted()->getSize(); + } + + public function tell(): int + { + return $this->getFormatted()->tell(); + } + + public function eof(): bool + { + return $this->getFormatted()->eof(); + } + + public function isSeekable(): bool + { + return $this->getFormatted()->isSeekable(); + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + $this->getFormatted()->seek($offset, $whence); + } + + public function rewind(): void + { + $this->getFormatted()->rewind(); + } + + public function isWritable(): bool + { + return $this->getFormatted()->isWritable(); + } + + public function write(string $string): int + { + return $this->getFormatted()->write($string); + } + + public function isReadable(): bool + { + return $this->getFormatted()->isReadable(); + } + + public function read(int $length): string + { + return $this->getFormatted()->read($length); + } + + public function getContents(): string + { + return $this->getFormatted()->getContents(); + } + + public function getMetadata(?string $key = null) + { + return $this->getFormatted()->getMetadata($key); + } + + /** + * Gets or creates the inner stream by formatting the data. + */ + private function getFormatted(): StreamInterface + { + if ($this->formatted !== null) { + return $this->formatted; + } + + $content = $this->formatter->format($this->data); + + $this->formatted = $content instanceof StreamInterface + ? $content + : new StringStream($content); + + return $this->formatted; + } + + /** + * Resets the stream state. + */ + private function resetState(): void + { + if ($this->formatted !== null) { + $this->formatted->close(); + $this->formatted = null; + } + } +} diff --git a/src/Modern/StringStream.php b/src/Modern/StringStream.php new file mode 100644 index 0000000..acf600c --- /dev/null +++ b/src/Modern/StringStream.php @@ -0,0 +1,163 @@ +content; + } + + public function close(): void + { + $this->closed = true; + } + + public function detach() + { + $this->close(); + return null; + } + + public function getSize(): int + { + return $this->getContentSize(); + } + + public function tell(): int + { + if ($this->closed) { + throw new RuntimeException('Stream is closed.'); + } + + return $this->position; + } + + public function eof(): bool + { + return $this->closed || $this->position >= $this->getContentSize(); + } + + public function isSeekable(): bool + { + return !$this->closed; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + if ($this->closed) { + throw new RuntimeException('Stream is closed.'); + } + + $size = $this->getContentSize(); + + $newPosition = match ($whence) { + SEEK_SET => $offset, + SEEK_CUR => $this->position + $offset, + SEEK_END => $size + $offset, + default => throw new RuntimeException('Invalid whence value.'), + }; + + if ($newPosition < 0 || $newPosition > $size) { + throw new RuntimeException('Invalid seek position.'); + } + + $this->position = $newPosition; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new RuntimeException('Stream is not writable.'); + } + + public function isReadable(): bool + { + return !$this->closed; + } + + public function read(int $length): string + { + if ($this->closed) { + throw new RuntimeException('Stream is closed.'); + } + + if ($length < 0) { + throw new RuntimeException('Length must be non-negative.'); + } + + if ($this->position >= $this->getContentSize()) { + return ''; + } + + $data = substr($this->content, $this->position, $length); + $this->position += strlen($data); + + return $data; + } + + public function getContents(): string + { + return $this->read( + $this->getContentSize() - $this->position + ); + } + + public function getMetadata(?string $key = null) + { + if ($this->closed) { + return $key === null ? [] : null; + } + + $metadata = [ + 'eof' => $this->eof(), + 'seekable' => $this->isSeekable(), + ]; + + if ($key === null) { + return $metadata; + } + + return $metadata[$key] ?? null; + } + + private function getContentSize(): int + { + if ($this->size === null) { + $this->size = strlen($this->content); + } + + return $this->size; + } +} From 7efb55376784b61ada10853207592bb9a566c3fc Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Sun, 25 Jan 2026 13:04:09 +0000 Subject: [PATCH 10/64] Apply PHP CS Fixer and Rector changes (CI) --- .../DataResponseFormatter/XmlResponseFormatter.php | 3 +-- src/Modern/DataStream.php | 13 +++++++------ src/Modern/StringStream.php | 9 ++++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Modern/DataResponseFormatter/XmlResponseFormatter.php b/src/Modern/DataResponseFormatter/XmlResponseFormatter.php index cd2b5a8..5941f93 100644 --- a/src/Modern/DataResponseFormatter/XmlResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/XmlResponseFormatter.php @@ -38,8 +38,7 @@ public function __construct( private readonly string $encoding = 'UTF-8', private readonly string $version = '1.0', private readonly string $rootTag = 'response', - ) { - } + ) {} public function format(mixed $data, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/DataStream.php b/src/Modern/DataStream.php index 648503a..0320f71 100644 --- a/src/Modern/DataStream.php +++ b/src/Modern/DataStream.php @@ -7,6 +7,8 @@ use Psr\Http\Message\StreamInterface; use Yiisoft\DataResponse\Modern\DataFormatter\StringDataFormatter; +use const SEEK_SET; + /** * A lazy stream that formats data only when it's being read. * @@ -24,7 +26,11 @@ final class DataStream implements StreamInterface public function __construct( private mixed $data, private DataFormatterInterface $formatter = new StringDataFormatter(), - ) { + ) {} + + public function __toString(): string + { + return (string) $this->getFormatted(); } /** @@ -49,11 +55,6 @@ public function changeData(mixed $data): void $this->resetState(); } - public function __toString(): string - { - return (string) $this->getFormatted(); - } - public function close(): void { $this->getFormatted()->close(); diff --git a/src/Modern/StringStream.php b/src/Modern/StringStream.php index acf600c..a3e09ed 100644 --- a/src/Modern/StringStream.php +++ b/src/Modern/StringStream.php @@ -9,6 +9,10 @@ use function strlen; +use const SEEK_CUR; +use const SEEK_END; +use const SEEK_SET; + /** * A read-only stream implementation for string content. * @@ -22,8 +26,7 @@ final class StringStream implements StreamInterface public function __construct( private readonly string $content, - ) { - } + ) {} public function __toString(): string { @@ -130,7 +133,7 @@ public function read(int $length): string public function getContents(): string { return $this->read( - $this->getContentSize() - $this->position + $this->getContentSize() - $this->position, ); } From a831d666357f1be0e615328dd2586fe7c71b4ef5 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 25 Jan 2026 16:11:52 +0300 Subject: [PATCH 11/64] improve --- src/Modern/{ => DataStream}/DataFormatterInterface.php | 2 +- src/Modern/{ => DataStream}/DataStream.php | 5 +++-- .../Formatter}/JsonDataFormatter.php | 4 ++-- .../Formatter}/StringDataFormatter.php | 4 ++-- .../Formatter}/XmlDataFormatter.php | 5 ++--- src/Modern/{ => DataStream/Formatter}/XmlDataInterface.php | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename src/Modern/{ => DataStream}/DataFormatterInterface.php (92%) rename src/Modern/{ => DataStream}/DataStream.php (95%) rename src/Modern/{DataFormatter => DataStream/Formatter}/JsonDataFormatter.php (85%) rename src/Modern/{DataFormatter => DataStream/Formatter}/StringDataFormatter.php (87%) rename src/Modern/{DataFormatter => DataStream/Formatter}/XmlDataFormatter.php (97%) rename src/Modern/{ => DataStream/Formatter}/XmlDataInterface.php (94%) diff --git a/src/Modern/DataFormatterInterface.php b/src/Modern/DataStream/DataFormatterInterface.php similarity index 92% rename from src/Modern/DataFormatterInterface.php rename to src/Modern/DataStream/DataFormatterInterface.php index 140e150..38a0b90 100644 --- a/src/Modern/DataFormatterInterface.php +++ b/src/Modern/DataStream/DataFormatterInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern; +namespace Yiisoft\DataResponse\Modern\DataStream; use Psr\Http\Message\StreamInterface; diff --git a/src/Modern/DataStream.php b/src/Modern/DataStream/DataStream.php similarity index 95% rename from src/Modern/DataStream.php rename to src/Modern/DataStream/DataStream.php index 648503a..3f49023 100644 --- a/src/Modern/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern; +namespace Yiisoft\DataResponse\Modern\DataStream; use Psr\Http\Message\StreamInterface; -use Yiisoft\DataResponse\Modern\DataFormatter\StringDataFormatter; +use Yiisoft\DataResponse\Modern\DataStream\Formatter\StringDataFormatter; +use Yiisoft\DataResponse\Modern\StringStream; /** * A lazy stream that formats data only when it's being read. diff --git a/src/Modern/DataFormatter/JsonDataFormatter.php b/src/Modern/DataStream/Formatter/JsonDataFormatter.php similarity index 85% rename from src/Modern/DataFormatter/JsonDataFormatter.php rename to src/Modern/DataStream/Formatter/JsonDataFormatter.php index f9d291f..5ccbcbf 100644 --- a/src/Modern/DataFormatter/JsonDataFormatter.php +++ b/src/Modern/DataStream/Formatter/JsonDataFormatter.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataFormatter; +namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; use JsonException; -use Yiisoft\DataResponse\Modern\DataFormatterInterface; +use Yiisoft\DataResponse\Modern\DataStream\DataFormatterInterface; use Yiisoft\Json\Json; use const JSON_UNESCAPED_SLASHES; diff --git a/src/Modern/DataFormatter/StringDataFormatter.php b/src/Modern/DataStream/Formatter/StringDataFormatter.php similarity index 87% rename from src/Modern/DataFormatter/StringDataFormatter.php rename to src/Modern/DataStream/Formatter/StringDataFormatter.php index 1e13d3c..57678fe 100644 --- a/src/Modern/DataFormatter/StringDataFormatter.php +++ b/src/Modern/DataStream/Formatter/StringDataFormatter.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataFormatter; +namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; use RuntimeException; use Stringable; -use Yiisoft\DataResponse\Modern\DataFormatterInterface; +use Yiisoft\DataResponse\Modern\DataStream\DataFormatterInterface; use function is_scalar; use function sprintf; diff --git a/src/Modern/DataFormatter/XmlDataFormatter.php b/src/Modern/DataStream/Formatter/XmlDataFormatter.php similarity index 97% rename from src/Modern/DataFormatter/XmlDataFormatter.php rename to src/Modern/DataStream/Formatter/XmlDataFormatter.php index 37e4f58..9f833d5 100644 --- a/src/Modern/DataFormatter/XmlDataFormatter.php +++ b/src/Modern/DataStream/Formatter/XmlDataFormatter.php @@ -2,15 +2,14 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataFormatter; +namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; use DOMDocument; use DOMElement; use DOMException; use DOMText; use Traversable; -use Yiisoft\DataResponse\Modern\DataFormatterInterface; -use Yiisoft\DataResponse\Modern\XmlDataInterface; +use Yiisoft\DataResponse\Modern\DataStream\DataFormatterInterface; use Yiisoft\Strings\NumericHelper; use function is_array; diff --git a/src/Modern/XmlDataInterface.php b/src/Modern/DataStream/Formatter/XmlDataInterface.php similarity index 94% rename from src/Modern/XmlDataInterface.php rename to src/Modern/DataStream/Formatter/XmlDataInterface.php index 326e337..64b3736 100644 --- a/src/Modern/XmlDataInterface.php +++ b/src/Modern/DataStream/Formatter/XmlDataInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern; +namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; /** * XmlFormatDataInterface provides methods used when formatting objects {@see XmlDataResponseFormatter} as XML data. From 46e55554ec76fe0713ec036b14d8d64be170db67 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 12:39:30 +0300 Subject: [PATCH 12/64] improve --- src/Modern/DataStream/DataStream.php | 1 - src/Modern/{ => DataStream}/StringStream.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename src/Modern/{ => DataStream}/StringStream.php (98%) diff --git a/src/Modern/DataStream/DataStream.php b/src/Modern/DataStream/DataStream.php index 68f1bc5..3a002fc 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -6,7 +6,6 @@ use Psr\Http\Message\StreamInterface; use Yiisoft\DataResponse\Modern\DataStream\Formatter\StringDataFormatter; -use Yiisoft\DataResponse\Modern\StringStream; use const SEEK_SET; diff --git a/src/Modern/StringStream.php b/src/Modern/DataStream/StringStream.php similarity index 98% rename from src/Modern/StringStream.php rename to src/Modern/DataStream/StringStream.php index a3e09ed..a4c67ea 100644 --- a/src/Modern/StringStream.php +++ b/src/Modern/DataStream/StringStream.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern; +namespace Yiisoft\DataResponse\Modern\DataStream; use Psr\Http\Message\StreamInterface; use RuntimeException; From 4f09ea9e08e121b39b3b5a53fa84ba4532780959 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 13:34:52 +0300 Subject: [PATCH 13/64] improve --- src/Modern/DataResponse.php | 116 --------- src/Modern/DataResponseFactory.php | 26 -- .../DataResponseFactory.php | 28 +++ .../DataResponseFactoryInterface.php | 17 ++ .../HtmlDataResponseFactory.php | 31 +++ .../JsonDataResponseFactory.php | 31 +++ .../PlainTextDataResponseFactory.php | 31 +++ .../XmlDataResponseFactory.php | 31 +++ src/Modern/DataResponseFactoryInterface.php | 24 -- .../DataResponseFormatterInterface.php | 13 + .../HtmlDataResponseFormatter.php | 29 +++ .../HtmlResponseFormatter.php | 49 ---- .../JsonDataResponseFormatter.php | 29 +++ .../JsonResponseFormatter.php | 46 ---- .../PlainTextDataResponseFormatter.php | 29 +++ .../PlainTextResponseFormatter.php | 47 ---- .../XmlDataResponseFormatter.php | 29 +++ .../XmlResponseFormatter.php | 222 ------------------ .../DataStream/Formatter/XmlDataInterface.php | 2 + .../Middleware/DataResponseFormatter.php | 28 --- .../Middleware/HtmlDataResponseFormatter.php | 18 -- .../Middleware/HtmlDataResponseMiddleware.php | 29 +++ .../Middleware/JsonDataResponseFormatter.php | 18 -- .../Middleware/JsonDataResponseMiddleware.php | 29 +++ .../PlainTextDataResponseFormatter.php | 18 -- .../PlainTextDataResponseMiddleware.php | 29 +++ .../Middleware/XmlDataResponseFormatter.php | 18 -- .../Middleware/XmlDataResponseMiddleware.php | 29 +++ src/Modern/ResponseFormatterInterface.php | 23 -- 29 files changed, 416 insertions(+), 653 deletions(-) delete mode 100644 src/Modern/DataResponse.php delete mode 100644 src/Modern/DataResponseFactory.php create mode 100644 src/Modern/DataResponseFactory/DataResponseFactory.php create mode 100644 src/Modern/DataResponseFactory/DataResponseFactoryInterface.php create mode 100644 src/Modern/DataResponseFactory/HtmlDataResponseFactory.php create mode 100644 src/Modern/DataResponseFactory/JsonDataResponseFactory.php create mode 100644 src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php create mode 100644 src/Modern/DataResponseFactory/XmlDataResponseFactory.php delete mode 100644 src/Modern/DataResponseFactoryInterface.php create mode 100644 src/Modern/DataResponseFormatter/DataResponseFormatterInterface.php create mode 100644 src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php delete mode 100644 src/Modern/DataResponseFormatter/HtmlResponseFormatter.php create mode 100644 src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php delete mode 100644 src/Modern/DataResponseFormatter/JsonResponseFormatter.php create mode 100644 src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php delete mode 100644 src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php create mode 100644 src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php delete mode 100644 src/Modern/DataResponseFormatter/XmlResponseFormatter.php delete mode 100644 src/Modern/Middleware/DataResponseFormatter.php delete mode 100644 src/Modern/Middleware/HtmlDataResponseFormatter.php create mode 100644 src/Modern/Middleware/HtmlDataResponseMiddleware.php delete mode 100644 src/Modern/Middleware/JsonDataResponseFormatter.php create mode 100644 src/Modern/Middleware/JsonDataResponseMiddleware.php delete mode 100644 src/Modern/Middleware/PlainTextDataResponseFormatter.php create mode 100644 src/Modern/Middleware/PlainTextDataResponseMiddleware.php delete mode 100644 src/Modern/Middleware/XmlDataResponseFormatter.php create mode 100644 src/Modern/Middleware/XmlDataResponseMiddleware.php delete mode 100644 src/Modern/ResponseFormatterInterface.php diff --git a/src/Modern/DataResponse.php b/src/Modern/DataResponse.php deleted file mode 100644 index 33e8e5c..0000000 --- a/src/Modern/DataResponse.php +++ /dev/null @@ -1,116 +0,0 @@ -data; - } - - public function withData(mixed $data): self - { - $new = clone $this; - $new->data = $data; - return $new; - } - - public function getProtocolVersion(): string - { - return $this->response->getProtocolVersion(); - } - - public function withProtocolVersion(string $version): MessageInterface - { - $new = clone $this; - $new->response = $this->response->withProtocolVersion($version); - return $new; - } - - public function getHeaders(): array - { - return $this->response->getHeaders(); - } - - public function hasHeader(string $name): bool - { - return $this->response->hasHeader($name); - } - - public function getHeader(string $name): array - { - return $this->response->getHeader($name); - } - - public function getHeaderLine(string $name): string - { - return $this->response->getHeaderLine($name); - } - - public function withHeader(string $name, $value): MessageInterface - { - $new = clone $this; - $new->response = $this->response->withHeader($name, $value); - return $new; - } - - public function withAddedHeader(string $name, $value): MessageInterface - { - $new = clone $this; - $new->response = $this->response->withAddedHeader($name, $value); - return $new; - } - - public function withoutHeader(string $name): MessageInterface - { - $new = clone $this; - $new->response = $this->response->withoutHeader($name); - return $new; - } - - public function getBody(): StreamInterface - { - return $this->response->getBody(); - } - - public function withBody(StreamInterface $body): MessageInterface - { - $new = clone $this; - $new->response = $this->response->withBody($body); - return $new; - } - - public function getStatusCode(): int - { - return $this->response->getStatusCode(); - } - - public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface - { - $new = clone $this; - $new->response = $this->response->withStatus($code, $reasonPhrase); - return $new; - } - - public function getReasonPhrase(): string - { - return $this->response->getReasonPhrase(); - } - - public function getResponse(): ResponseInterface - { - return $this->response; - } -} diff --git a/src/Modern/DataResponseFactory.php b/src/Modern/DataResponseFactory.php deleted file mode 100644 index f23d22e..0000000 --- a/src/Modern/DataResponseFactory.php +++ /dev/null @@ -1,26 +0,0 @@ -responseFactory->createResponse($code, $reasonPhrase), - $data, - ); - } -} diff --git a/src/Modern/DataResponseFactory/DataResponseFactory.php b/src/Modern/DataResponseFactory/DataResponseFactory.php new file mode 100644 index 0000000..57b4d63 --- /dev/null +++ b/src/Modern/DataResponseFactory/DataResponseFactory.php @@ -0,0 +1,28 @@ +responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody(new DataStream($data)); + } +} diff --git a/src/Modern/DataResponseFactory/DataResponseFactoryInterface.php b/src/Modern/DataResponseFactory/DataResponseFactoryInterface.php new file mode 100644 index 0000000..d565142 --- /dev/null +++ b/src/Modern/DataResponseFactory/DataResponseFactoryInterface.php @@ -0,0 +1,17 @@ +formatter->format( + new DataStream($data), + $this->responseFactory->createResponse($code, $reasonPhrase), + ); + } +} diff --git a/src/Modern/DataResponseFactory/JsonDataResponseFactory.php b/src/Modern/DataResponseFactory/JsonDataResponseFactory.php new file mode 100644 index 0000000..85f7bdb --- /dev/null +++ b/src/Modern/DataResponseFactory/JsonDataResponseFactory.php @@ -0,0 +1,31 @@ +formatter->format( + new DataStream($data), + $this->responseFactory->createResponse($code, $reasonPhrase), + ); + } +} diff --git a/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php b/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php new file mode 100644 index 0000000..ca4227f --- /dev/null +++ b/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php @@ -0,0 +1,31 @@ +formatter->format( + new DataStream($data), + $this->responseFactory->createResponse($code, $reasonPhrase), + ); + } +} diff --git a/src/Modern/DataResponseFactory/XmlDataResponseFactory.php b/src/Modern/DataResponseFactory/XmlDataResponseFactory.php new file mode 100644 index 0000000..0dc6764 --- /dev/null +++ b/src/Modern/DataResponseFactory/XmlDataResponseFactory.php @@ -0,0 +1,31 @@ +formatter->format( + new DataStream($data), + $this->responseFactory->createResponse($code, $reasonPhrase), + ); + } +} diff --git a/src/Modern/DataResponseFactoryInterface.php b/src/Modern/DataResponseFactoryInterface.php deleted file mode 100644 index 8d7466b..0000000 --- a/src/Modern/DataResponseFactoryInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -changeFormatter($this->formatter); + + return $response + ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") + ->withBody($body); + } +} diff --git a/src/Modern/DataResponseFormatter/HtmlResponseFormatter.php b/src/Modern/DataResponseFormatter/HtmlResponseFormatter.php deleted file mode 100644 index 6567ce3..0000000 --- a/src/Modern/DataResponseFormatter/HtmlResponseFormatter.php +++ /dev/null @@ -1,49 +0,0 @@ -getBody() - ->write((string) $data); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php b/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php new file mode 100644 index 0000000..80c4d5e --- /dev/null +++ b/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php @@ -0,0 +1,29 @@ +changeFormatter($this->formatter); + + return $response + ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") + ->withBody($body); + } +} diff --git a/src/Modern/DataResponseFormatter/JsonResponseFormatter.php b/src/Modern/DataResponseFormatter/JsonResponseFormatter.php deleted file mode 100644 index 4a0ccee..0000000 --- a/src/Modern/DataResponseFormatter/JsonResponseFormatter.php +++ /dev/null @@ -1,46 +0,0 @@ -getBody() - ->write(Json::encode($data, $this->options)); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php b/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php new file mode 100644 index 0000000..e46b22b --- /dev/null +++ b/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php @@ -0,0 +1,29 @@ +changeFormatter($this->formatter); + + return $response + ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") + ->withBody($body); + } +} diff --git a/src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php b/src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php deleted file mode 100644 index 48426e6..0000000 --- a/src/Modern/DataResponseFormatter/PlainTextResponseFormatter.php +++ /dev/null @@ -1,47 +0,0 @@ -getBody() - ->write((string) $data); - } - - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } -} diff --git a/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php b/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php new file mode 100644 index 0000000..6785a5f --- /dev/null +++ b/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php @@ -0,0 +1,29 @@ +changeFormatter($this->formatter); + + return $response + ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") + ->withBody($body); + } +} diff --git a/src/Modern/DataResponseFormatter/XmlResponseFormatter.php b/src/Modern/DataResponseFormatter/XmlResponseFormatter.php deleted file mode 100644 index 5941f93..0000000 --- a/src/Modern/DataResponseFormatter/XmlResponseFormatter.php +++ /dev/null @@ -1,222 +0,0 @@ -withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $dom = new DOMDocument($this->version, $this->encoding); - - if (empty($this->rootTag)) { - $this->buildXml($dom, $dom, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - $root = new DOMElement($this->rootTag); - $dom->appendChild($root); - $this->buildXml($dom, $root, $data); - $response - ->getBody() - ->write((string) $dom->saveXML()); - return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); - } - - /** - * Builds the data to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param mixed $data Data for building XML. - */ - private function buildXml(DOMDocument $dom, $element, mixed $data): void - { - if (empty($data)) { - return; - } - - if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) { - /** @var int|string $name */ - foreach ($data as $name => $value) { - if (is_object($value)) { - $this->buildObject($dom, $element, $value, $name); - continue; - } - - $child = $this->safeCreateDomElement($dom, $name); - $element->appendChild($child); - - if (is_array($value)) { - $this->buildXml($dom, $child, $value); - continue; - } - - /** @psalm-var scalar $value */ - - $this->setScalarValueToDomElement($child, $value); - } - - return; - } - - if (is_object($data)) { - $this->buildObject($dom, $element, $data); - return; - } - - /** @psalm-var scalar $data */ - - $this->setScalarValueToDomElement($element, $data); - } - - /** - * Builds the object to use in XML. - * - * @param DOMDocument $dom The root DOM document. - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param object $object To build. - * @param int|string|null $tagName The tag name. - */ - private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void - { - if ($object instanceof XmlDataInterface) { - $child = $this->safeCreateDomElement($dom, $object->xmlTagName()); - - foreach ($object->xmlTagAttributes() as $name => $value) { - $child->setAttribute($name, $value); - } - - $element->appendChild($child); - $this->buildXml($dom, $child, $object->xmlData()); - return; - } - - $child = $this->safeCreateDomElement($dom, $tagName); - $element->appendChild($child); - - if ($object instanceof Traversable) { - $this->buildXml($dom, $child, $object); - return; - } - - $data = []; - - /** - * @var string $property - */ - foreach ($object as $property => $value) { - $data[$property] = $value; - } - - $this->buildXml($dom, $child, $data); - } - - /** - * Safely creates a DOMElement instance by the specified tag name if the tag name is not empty, - * is not integer, and is valid. Otherwise {@see DEFAULT_ITEM_TAG_NAME} value is used. - * - * @see https://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 - * - * @param DOMDocument $dom The root DOM document. - * @param int|string|null $tagName The tag name. - * - * @return DOMElement - */ - private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement - { - if (empty($tagName) || is_int($tagName)) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - - try { - if (!$element = $dom->createElement($tagName)) { - throw new DOMException(); - } - return $element; - } catch (DOMException) { - return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); - } - } - - /** - * Sets the scalar value to DOM Element instance if the value is not empty. - * - * @param DOMDocument|DOMElement $element The current DOM element being processed. - * @param bool|float|int|string|null $value - */ - private function setScalarValueToDomElement($element, $value): void - { - $value = $this->formatScalarValue($value); - - if ($value !== '') { - $element->appendChild(new DOMText($value)); - } - } - - /** - * Formats scalar value for use in XML node. - * - * @param bool|float|int|string|null $value To format. - * - * @return string The string representation of the value. - */ - private function formatScalarValue($value): string - { - if ($value === true) { - return 'true'; - } - - if ($value === false) { - return 'false'; - } - - if (is_float($value)) { - return NumericHelper::normalize($value); - } - - return (string) $value; - } -} diff --git a/src/Modern/DataStream/Formatter/XmlDataInterface.php b/src/Modern/DataStream/Formatter/XmlDataInterface.php index 64b3736..89f417a 100644 --- a/src/Modern/DataStream/Formatter/XmlDataInterface.php +++ b/src/Modern/DataStream/Formatter/XmlDataInterface.php @@ -4,6 +4,8 @@ namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; +use Yiisoft\DataResponse\Modern\DataResponseFormatter\XmlDataResponseFormatter; + /** * XmlFormatDataInterface provides methods used when formatting objects {@see XmlDataResponseFormatter} as XML data. */ diff --git a/src/Modern/Middleware/DataResponseFormatter.php b/src/Modern/Middleware/DataResponseFormatter.php deleted file mode 100644 index 3c606b1..0000000 --- a/src/Modern/Middleware/DataResponseFormatter.php +++ /dev/null @@ -1,28 +0,0 @@ -handle($request); - if ($response instanceof DataResponse) { - $response = $this->defaultFormatter->format($response->getData(), $response->getResponse()); - } - return $response; - } -} diff --git a/src/Modern/Middleware/HtmlDataResponseFormatter.php b/src/Modern/Middleware/HtmlDataResponseFormatter.php deleted file mode 100644 index daac710..0000000 --- a/src/Modern/Middleware/HtmlDataResponseFormatter.php +++ /dev/null @@ -1,18 +0,0 @@ -handle($request); + $body = $response->getBody(); + return $body instanceof DataStream + ? $this->formatter->format($body, $response) + : $response; + } +} diff --git a/src/Modern/Middleware/JsonDataResponseFormatter.php b/src/Modern/Middleware/JsonDataResponseFormatter.php deleted file mode 100644 index b680090..0000000 --- a/src/Modern/Middleware/JsonDataResponseFormatter.php +++ /dev/null @@ -1,18 +0,0 @@ -handle($request); + $body = $response->getBody(); + return $body instanceof DataStream + ? $this->formatter->format($body, $response) + : $response; + } +} diff --git a/src/Modern/Middleware/PlainTextDataResponseFormatter.php b/src/Modern/Middleware/PlainTextDataResponseFormatter.php deleted file mode 100644 index ec7f292..0000000 --- a/src/Modern/Middleware/PlainTextDataResponseFormatter.php +++ /dev/null @@ -1,18 +0,0 @@ -handle($request); + $body = $response->getBody(); + return $body instanceof DataStream + ? $this->formatter->format($body, $response) + : $response; + } +} diff --git a/src/Modern/Middleware/XmlDataResponseFormatter.php b/src/Modern/Middleware/XmlDataResponseFormatter.php deleted file mode 100644 index c09cc65..0000000 --- a/src/Modern/Middleware/XmlDataResponseFormatter.php +++ /dev/null @@ -1,18 +0,0 @@ -handle($request); + $body = $response->getBody(); + return $body instanceof DataStream + ? $this->formatter->format($body, $response) + : $response; + } +} diff --git a/src/Modern/ResponseFormatterInterface.php b/src/Modern/ResponseFormatterInterface.php deleted file mode 100644 index dbf5db2..0000000 --- a/src/Modern/ResponseFormatterInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - Date: Mon, 26 Jan 2026 10:46:27 +0000 Subject: [PATCH 14/64] Apply PHP CS Fixer and Rector changes (CI) --- src/Modern/DataResponseFactory/DataResponseFactory.php | 3 +-- src/Modern/DataResponseFactory/HtmlDataResponseFactory.php | 3 +-- src/Modern/DataResponseFactory/JsonDataResponseFactory.php | 3 +-- .../DataResponseFactory/PlainTextDataResponseFactory.php | 3 +-- src/Modern/DataResponseFactory/XmlDataResponseFactory.php | 3 +-- src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php | 3 +-- src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php | 3 +-- .../DataResponseFormatter/PlainTextDataResponseFormatter.php | 3 +-- src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php | 3 +-- src/Modern/Middleware/HtmlDataResponseMiddleware.php | 3 +-- src/Modern/Middleware/JsonDataResponseMiddleware.php | 3 +-- src/Modern/Middleware/PlainTextDataResponseMiddleware.php | 3 +-- src/Modern/Middleware/XmlDataResponseMiddleware.php | 3 +-- 13 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/Modern/DataResponseFactory/DataResponseFactory.php b/src/Modern/DataResponseFactory/DataResponseFactory.php index 57b4d63..301e256 100644 --- a/src/Modern/DataResponseFactory/DataResponseFactory.php +++ b/src/Modern/DataResponseFactory/DataResponseFactory.php @@ -19,8 +19,7 @@ public function createResponse( mixed $data = null, int $code = Status::OK, string $reasonPhrase = '', - ): ResponseInterface - { + ): ResponseInterface { return $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody(new DataStream($data)); diff --git a/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php b/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php index 44ccfbc..656f016 100644 --- a/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php +++ b/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php @@ -15,8 +15,7 @@ final class HtmlDataResponseFactory implements DataResponseFactoryInterface public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly HtmlDataResponseFormatter $formatter, - ) { - } + ) {} public function createResponse( mixed $data = null, diff --git a/src/Modern/DataResponseFactory/JsonDataResponseFactory.php b/src/Modern/DataResponseFactory/JsonDataResponseFactory.php index 85f7bdb..9f190ba 100644 --- a/src/Modern/DataResponseFactory/JsonDataResponseFactory.php +++ b/src/Modern/DataResponseFactory/JsonDataResponseFactory.php @@ -15,8 +15,7 @@ final class JsonDataResponseFactory implements DataResponseFactoryInterface public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly JsonDataResponseFormatter $formatter, - ) { - } + ) {} public function createResponse( mixed $data = null, diff --git a/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php b/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php index ca4227f..a1fec19 100644 --- a/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php +++ b/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php @@ -15,8 +15,7 @@ final class PlainTextDataResponseFactory implements DataResponseFactoryInterface public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly PlainTextDataResponseFormatter $formatter, - ) { - } + ) {} public function createResponse( mixed $data = null, diff --git a/src/Modern/DataResponseFactory/XmlDataResponseFactory.php b/src/Modern/DataResponseFactory/XmlDataResponseFactory.php index 0dc6764..d9209ee 100644 --- a/src/Modern/DataResponseFactory/XmlDataResponseFactory.php +++ b/src/Modern/DataResponseFactory/XmlDataResponseFactory.php @@ -15,8 +15,7 @@ final class XmlDataResponseFactory implements DataResponseFactoryInterface public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly XmlDataResponseFormatter $formatter, - ) { - } + ) {} public function createResponse( mixed $data = null, diff --git a/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php b/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php index 2cd28cf..090e685 100644 --- a/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php @@ -15,8 +15,7 @@ public function __construct( private readonly StringDataFormatter $formatter = new StringDataFormatter(), private readonly string $contentType = 'text/html', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(DataStream $body, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php b/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php index 80c4d5e..9cbaf19 100644 --- a/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php @@ -15,8 +15,7 @@ public function __construct( private readonly JsonDataFormatter $formatter = new JsonDataFormatter(), private readonly string $contentType = 'application/json', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(DataStream $body, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php b/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php index e46b22b..33fcef7 100644 --- a/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php @@ -15,8 +15,7 @@ public function __construct( private readonly StringDataFormatter $formatter = new StringDataFormatter(), private readonly string $contentType = 'text/plain', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(DataStream $body, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php b/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php index 6785a5f..ad79e48 100644 --- a/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php +++ b/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php @@ -15,8 +15,7 @@ public function __construct( private readonly XmlDataFormatter $formatter = new XmlDataFormatter(), private readonly string $contentType = 'application/xml', private readonly string $encoding = 'UTF-8', - ) { - } + ) {} public function format(DataStream $body, ResponseInterface $response): ResponseInterface { diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Modern/Middleware/HtmlDataResponseMiddleware.php index 7b6f00e..f022c99 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/HtmlDataResponseMiddleware.php @@ -15,8 +15,7 @@ final class HtmlDataResponseMiddleware implements MiddlewareInterface { public function __construct( private readonly HtmlDataResponseFormatter $formatter = new HtmlDataResponseFormatter(), - ) { - } + ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Modern/Middleware/JsonDataResponseMiddleware.php index eb26ec2..d749e41 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Modern/Middleware/JsonDataResponseMiddleware.php @@ -15,8 +15,7 @@ final class JsonDataResponseMiddleware implements MiddlewareInterface { public function __construct( private readonly JsonDataResponseFormatter $formatter = new JsonDataResponseFormatter(), - ) { - } + ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php index 7933505..190370b 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php @@ -15,8 +15,7 @@ final class PlainTextDataResponseMiddleware implements MiddlewareInterface { public function __construct( private readonly PlainTextDataResponseFormatter $formatter = new PlainTextDataResponseFormatter(), - ) { - } + ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Modern/Middleware/XmlDataResponseMiddleware.php index 13c0d18..22135c7 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/XmlDataResponseMiddleware.php @@ -15,8 +15,7 @@ final class XmlDataResponseMiddleware implements MiddlewareInterface { public function __construct( private readonly XmlDataResponseFormatter $formatter = new XmlDataResponseFormatter(), - ) { - } + ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { From bff8592743e28ebce3169da4118360f09d49024e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 13:49:47 +0300 Subject: [PATCH 15/64] improve --- .../HtmlResponseFactory.php} | 7 ++++--- .../JsonResponseFactory.php} | 4 ++-- .../PlainTextResponseFactory.php} | 7 ++++--- .../XmlResponseFactory.php} | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) rename src/Modern/{DataResponseFactory/HtmlDataResponseFactory.php => ResponseFactory/HtmlResponseFactory.php} (82%) rename src/Modern/{DataResponseFactory/JsonDataResponseFactory.php => ResponseFactory/JsonResponseFactory.php} (85%) rename src/Modern/{DataResponseFactory/PlainTextDataResponseFactory.php => ResponseFactory/PlainTextResponseFactory.php} (82%) rename src/Modern/{DataResponseFactory/XmlDataResponseFactory.php => ResponseFactory/XmlResponseFactory.php} (85%) diff --git a/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php similarity index 82% rename from src/Modern/DataResponseFactory/HtmlDataResponseFactory.php rename to src/Modern/ResponseFactory/HtmlResponseFactory.php index 44ccfbc..4665e55 100644 --- a/src/Modern/DataResponseFactory/HtmlDataResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -2,15 +2,16 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; +use Stringable; use Yiisoft\DataResponse\Modern\DataResponseFormatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; -final class HtmlDataResponseFactory implements DataResponseFactoryInterface +final class HtmlResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, @@ -19,7 +20,7 @@ public function __construct( } public function createResponse( - mixed $data = null, + string|bool|int|float|null|Stringable $data = null, int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { diff --git a/src/Modern/DataResponseFactory/JsonDataResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php similarity index 85% rename from src/Modern/DataResponseFactory/JsonDataResponseFactory.php rename to src/Modern/ResponseFactory/JsonResponseFactory.php index 85f7bdb..e55beb4 100644 --- a/src/Modern/DataResponseFactory/JsonDataResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -10,7 +10,7 @@ use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; -final class JsonDataResponseFactory implements DataResponseFactoryInterface +final class JsonResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, diff --git a/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php similarity index 82% rename from src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php rename to src/Modern/ResponseFactory/PlainTextResponseFactory.php index ca4227f..cf41fa8 100644 --- a/src/Modern/DataResponseFactory/PlainTextDataResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -2,15 +2,16 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; +use Stringable; use Yiisoft\DataResponse\Modern\DataResponseFormatter\PlainTextDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; -final class PlainTextDataResponseFactory implements DataResponseFactoryInterface +final class PlainTextResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, @@ -19,7 +20,7 @@ public function __construct( } public function createResponse( - mixed $data = null, + string|bool|int|float|null|Stringable $data = null, int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { diff --git a/src/Modern/DataResponseFactory/XmlDataResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php similarity index 85% rename from src/Modern/DataResponseFactory/XmlDataResponseFactory.php rename to src/Modern/ResponseFactory/XmlResponseFactory.php index 0dc6764..b745ccf 100644 --- a/src/Modern/DataResponseFactory/XmlDataResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -10,7 +10,7 @@ use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; -final class XmlDataResponseFactory implements DataResponseFactoryInterface +final class XmlResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, From 0f2a24512866de1181b16c404b75ce0ea42c9480 Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:50:24 +0000 Subject: [PATCH 16/64] Apply PHP CS Fixer and Rector changes (CI) --- src/Modern/ResponseFactory/HtmlResponseFactory.php | 2 +- src/Modern/ResponseFactory/PlainTextResponseFactory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php index 2687b56..6b35b07 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -19,7 +19,7 @@ public function __construct( ) {} public function createResponse( - string|bool|int|float|null|Stringable $data = null, + string|bool|int|float|Stringable|null $data = null, int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index 3bda0da..9029f39 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -19,7 +19,7 @@ public function __construct( ) {} public function createResponse( - string|bool|int|float|null|Stringable $data = null, + string|bool|int|float|Stringable|null $data = null, int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { From 102224ca898a033c64d2fd8e7eaabb0ff8bacb64 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 14:01:46 +0300 Subject: [PATCH 17/64] improve --- .../DataResponseFactory.php | 2 +- .../DataResponseFactoryInterface.php | 2 +- .../DataResponseFormatterInterface.php | 2 +- .../Formatter}/HtmlDataResponseFormatter.php | 3 ++- .../Formatter}/JsonDataResponseFormatter.php | 3 ++- .../Formatter}/PlainTextDataResponseFormatter.php | 3 ++- .../Formatter}/XmlDataResponseFormatter.php | 3 ++- src/Modern/DataStream/Formatter/XmlDataInterface.php | 2 +- src/Modern/Middleware/HtmlDataResponseMiddleware.php | 2 +- src/Modern/Middleware/JsonDataResponseMiddleware.php | 2 +- src/Modern/Middleware/PlainTextDataResponseMiddleware.php | 2 +- src/Modern/Middleware/XmlDataResponseMiddleware.php | 2 +- src/Modern/ResponseFactory/HtmlResponseFactory.php | 2 +- src/Modern/ResponseFactory/JsonResponseFactory.php | 2 +- src/Modern/ResponseFactory/PlainTextResponseFactory.php | 2 +- src/Modern/ResponseFactory/XmlResponseFactory.php | 2 +- 16 files changed, 20 insertions(+), 16 deletions(-) rename src/Modern/{DataResponseFactory => DataResponse}/DataResponseFactory.php (92%) rename src/Modern/{DataResponseFactory => DataResponse}/DataResponseFactoryInterface.php (83%) rename src/Modern/{DataResponseFormatter => DataResponse}/DataResponseFormatterInterface.php (81%) rename src/Modern/{DataResponseFormatter => DataResponse/Formatter}/HtmlDataResponseFormatter.php (86%) rename src/Modern/{DataResponseFormatter => DataResponse/Formatter}/JsonDataResponseFormatter.php (86%) rename src/Modern/{DataResponseFormatter => DataResponse/Formatter}/PlainTextDataResponseFormatter.php (86%) rename src/Modern/{DataResponseFormatter => DataResponse/Formatter}/XmlDataResponseFormatter.php (86%) diff --git a/src/Modern/DataResponseFactory/DataResponseFactory.php b/src/Modern/DataResponse/DataResponseFactory.php similarity index 92% rename from src/Modern/DataResponseFactory/DataResponseFactory.php rename to src/Modern/DataResponse/DataResponseFactory.php index 301e256..075cebb 100644 --- a/src/Modern/DataResponseFactory/DataResponseFactory.php +++ b/src/Modern/DataResponse/DataResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\DataResponse; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/DataResponseFactory/DataResponseFactoryInterface.php b/src/Modern/DataResponse/DataResponseFactoryInterface.php similarity index 83% rename from src/Modern/DataResponseFactory/DataResponseFactoryInterface.php rename to src/Modern/DataResponse/DataResponseFactoryInterface.php index d565142..5d7a424 100644 --- a/src/Modern/DataResponseFactory/DataResponseFactoryInterface.php +++ b/src/Modern/DataResponse/DataResponseFactoryInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFactory; +namespace Yiisoft\DataResponse\Modern\DataResponse; use Psr\Http\Message\ResponseInterface; use Yiisoft\Http\Status; diff --git a/src/Modern/DataResponseFormatter/DataResponseFormatterInterface.php b/src/Modern/DataResponse/DataResponseFormatterInterface.php similarity index 81% rename from src/Modern/DataResponseFormatter/DataResponseFormatterInterface.php rename to src/Modern/DataResponse/DataResponseFormatterInterface.php index addb91a..ed60f5c 100644 --- a/src/Modern/DataResponseFormatter/DataResponseFormatterInterface.php +++ b/src/Modern/DataResponse/DataResponseFormatterInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\DataResponse; use Psr\Http\Message\ResponseInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; diff --git a/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/HtmlDataResponseFormatter.php similarity index 86% rename from src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php rename to src/Modern/DataResponse/Formatter/HtmlDataResponseFormatter.php index 090e685..2c59b95 100644 --- a/src/Modern/DataResponseFormatter/HtmlDataResponseFormatter.php +++ b/src/Modern/DataResponse/Formatter/HtmlDataResponseFormatter.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; +use Yiisoft\DataResponse\Modern\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\DataStream\Formatter\StringDataFormatter; use Yiisoft\Http\Header; diff --git a/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php similarity index 86% rename from src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php rename to src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php index 9cbaf19..296afb1 100644 --- a/src/Modern/DataResponseFormatter/JsonDataResponseFormatter.php +++ b/src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; +use Yiisoft\DataResponse\Modern\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\DataStream\Formatter\JsonDataFormatter; use Yiisoft\Http\Header; diff --git a/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php similarity index 86% rename from src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php rename to src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php index 33fcef7..a98051b 100644 --- a/src/Modern/DataResponseFormatter/PlainTextDataResponseFormatter.php +++ b/src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; +use Yiisoft\DataResponse\Modern\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\DataStream\Formatter\StringDataFormatter; use Yiisoft\Http\Header; diff --git a/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php similarity index 86% rename from src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php rename to src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php index ad79e48..b2766ab 100644 --- a/src/Modern/DataResponseFormatter/XmlDataResponseFormatter.php +++ b/src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; +use Yiisoft\DataResponse\Modern\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\DataStream\Formatter\XmlDataFormatter; use Yiisoft\Http\Header; diff --git a/src/Modern/DataStream/Formatter/XmlDataInterface.php b/src/Modern/DataStream/Formatter/XmlDataInterface.php index 89f417a..39f8d59 100644 --- a/src/Modern/DataStream/Formatter/XmlDataInterface.php +++ b/src/Modern/DataStream/Formatter/XmlDataInterface.php @@ -4,7 +4,7 @@ namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\XmlDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; /** * XmlFormatDataInterface provides methods used when formatting objects {@see XmlDataResponseFormatter} as XML data. diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Modern/Middleware/HtmlDataResponseMiddleware.php index f022c99..715543d 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/HtmlDataResponseMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\HtmlDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; final class HtmlDataResponseMiddleware implements MiddlewareInterface diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Modern/Middleware/JsonDataResponseMiddleware.php index d749e41..870ce10 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Modern/Middleware/JsonDataResponseMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\JsonDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\JsonDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; final class JsonDataResponseMiddleware implements MiddlewareInterface diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php index 190370b..82216ff 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\PlainTextDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\PlainTextDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; final class PlainTextDataResponseMiddleware implements MiddlewareInterface diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Modern/Middleware/XmlDataResponseMiddleware.php index 22135c7..ca53421 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/XmlDataResponseMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\XmlDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; final class XmlDataResponseMiddleware implements MiddlewareInterface diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php index 2687b56..fc1a18f 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Stringable; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\HtmlDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php index b373e90..5b89a03 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\JsonDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\JsonDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index 3bda0da..ecd576e 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Stringable; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\PlainTextDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\PlainTextDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php index 0b652f6..db29c58 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataResponseFormatter\XmlDataResponseFormatter; +use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\Http\Status; From 043a332c4e38adbda09a38e823132e4395d6e27e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 16:42:42 +0300 Subject: [PATCH 18/64] improve --- .../DataResponseFormatterInterface.php | 13 ------ .../Formatter/HtmlDataResponseFormatter.php | 29 ------------ .../Formatter/JsonDataResponseFormatter.php | 29 ------------ .../PlainTextDataResponseFormatter.php | 29 ------------ .../Formatter/XmlDataResponseFormatter.php | 29 ------------ .../DataStream/DataFormatterInterface.php | 24 ---------- src/Modern/DataStream/DataStream.php | 13 +++--- .../Formatter/JsonDataFormatter.php | 38 ---------------- .../Formatter/StringDataFormatter.php | 39 ---------------- src/Modern/Formatter/FormatterInterface.php | 15 +++++++ src/Modern/Formatter/HtmlFormatter.php | 44 +++++++++++++++++++ src/Modern/Formatter/JsonFormatter.php | 39 ++++++++++++++++ src/Modern/Formatter/PlainTextFormatter.php | 44 +++++++++++++++++++ .../Formatter/XmlDataInterface.php | 6 +-- .../XmlFormatter.php} | 25 +++++------ .../Middleware/DataResponseMiddleware.php | 28 ++++++++++++ .../Middleware/HtmlDataResponseMiddleware.php | 11 ++--- .../Middleware/JsonDataResponseMiddleware.php | 11 ++--- .../PlainTextDataResponseMiddleware.php | 11 ++--- .../Middleware/XmlDataResponseMiddleware.php | 11 ++--- .../ResponseFactory/HtmlResponseFactory.php | 13 +++--- .../ResponseFactory/JsonResponseFactory.php | 13 +++--- .../PlainTextResponseFactory.php | 13 +++--- .../ResponseFactory/XmlResponseFactory.php | 13 +++--- 24 files changed, 243 insertions(+), 297 deletions(-) delete mode 100644 src/Modern/DataResponse/DataResponseFormatterInterface.php delete mode 100644 src/Modern/DataResponse/Formatter/HtmlDataResponseFormatter.php delete mode 100644 src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php delete mode 100644 src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php delete mode 100644 src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php delete mode 100644 src/Modern/DataStream/DataFormatterInterface.php delete mode 100644 src/Modern/DataStream/Formatter/JsonDataFormatter.php delete mode 100644 src/Modern/DataStream/Formatter/StringDataFormatter.php create mode 100644 src/Modern/Formatter/FormatterInterface.php create mode 100644 src/Modern/Formatter/HtmlFormatter.php create mode 100644 src/Modern/Formatter/JsonFormatter.php create mode 100644 src/Modern/Formatter/PlainTextFormatter.php rename src/Modern/{DataStream => }/Formatter/XmlDataInterface.php (83%) rename src/Modern/{DataStream/Formatter/XmlDataFormatter.php => Formatter/XmlFormatter.php} (91%) create mode 100644 src/Modern/Middleware/DataResponseMiddleware.php diff --git a/src/Modern/DataResponse/DataResponseFormatterInterface.php b/src/Modern/DataResponse/DataResponseFormatterInterface.php deleted file mode 100644 index ed60f5c..0000000 --- a/src/Modern/DataResponse/DataResponseFormatterInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -changeFormatter($this->formatter); - - return $response - ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") - ->withBody($body); - } -} diff --git a/src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php deleted file mode 100644 index 296afb1..0000000 --- a/src/Modern/DataResponse/Formatter/JsonDataResponseFormatter.php +++ /dev/null @@ -1,29 +0,0 @@ -changeFormatter($this->formatter); - - return $response - ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") - ->withBody($body); - } -} diff --git a/src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php deleted file mode 100644 index a98051b..0000000 --- a/src/Modern/DataResponse/Formatter/PlainTextDataResponseFormatter.php +++ /dev/null @@ -1,29 +0,0 @@ -changeFormatter($this->formatter); - - return $response - ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") - ->withBody($body); - } -} diff --git a/src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php b/src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php deleted file mode 100644 index b2766ab..0000000 --- a/src/Modern/DataResponse/Formatter/XmlDataResponseFormatter.php +++ /dev/null @@ -1,29 +0,0 @@ -changeFormatter($this->formatter); - - return $response - ->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding") - ->withBody($body); - } -} diff --git a/src/Modern/DataStream/DataFormatterInterface.php b/src/Modern/DataStream/DataFormatterInterface.php deleted file mode 100644 index 38a0b90..0000000 --- a/src/Modern/DataStream/DataFormatterInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -formatter = $formatter; $this->resetState(); @@ -134,7 +135,7 @@ private function getFormatted(): StreamInterface return $this->formatted; } - $content = $this->formatter->format($this->data); + $content = $this->formatter->formatData($this->data); $this->formatted = $content instanceof StreamInterface ? $content diff --git a/src/Modern/DataStream/Formatter/JsonDataFormatter.php b/src/Modern/DataStream/Formatter/JsonDataFormatter.php deleted file mode 100644 index 5ccbcbf..0000000 --- a/src/Modern/DataStream/Formatter/JsonDataFormatter.php +++ /dev/null @@ -1,38 +0,0 @@ -options); - } -} diff --git a/src/Modern/DataStream/Formatter/StringDataFormatter.php b/src/Modern/DataStream/Formatter/StringDataFormatter.php deleted file mode 100644 index 57678fe..0000000 --- a/src/Modern/DataStream/Formatter/StringDataFormatter.php +++ /dev/null @@ -1,39 +0,0 @@ -withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/JsonFormatter.php b/src/Modern/Formatter/JsonFormatter.php new file mode 100644 index 0000000..521c277 --- /dev/null +++ b/src/Modern/Formatter/JsonFormatter.php @@ -0,0 +1,39 @@ +options); + } + + public function formatResponse(ResponseInterface $response): ResponseInterface + { + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/Formatter/PlainTextFormatter.php b/src/Modern/Formatter/PlainTextFormatter.php new file mode 100644 index 0000000..f762d88 --- /dev/null +++ b/src/Modern/Formatter/PlainTextFormatter.php @@ -0,0 +1,44 @@ +withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } +} diff --git a/src/Modern/DataStream/Formatter/XmlDataInterface.php b/src/Modern/Formatter/XmlDataInterface.php similarity index 83% rename from src/Modern/DataStream/Formatter/XmlDataInterface.php rename to src/Modern/Formatter/XmlDataInterface.php index 39f8d59..7a74a81 100644 --- a/src/Modern/DataStream/Formatter/XmlDataInterface.php +++ b/src/Modern/Formatter/XmlDataInterface.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; - -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; +namespace Yiisoft\DataResponse\Modern\Formatter; /** - * XmlFormatDataInterface provides methods used when formatting objects {@see XmlDataResponseFormatter} as XML data. + * XmlFormatDataInterface provides methods used when formatting objects {@see XmlFormatter} as XML data. */ interface XmlDataInterface { diff --git a/src/Modern/DataStream/Formatter/XmlDataFormatter.php b/src/Modern/Formatter/XmlFormatter.php similarity index 91% rename from src/Modern/DataStream/Formatter/XmlDataFormatter.php rename to src/Modern/Formatter/XmlFormatter.php index 9f833d5..a2b5a24 100644 --- a/src/Modern/DataStream/Formatter/XmlDataFormatter.php +++ b/src/Modern/Formatter/XmlFormatter.php @@ -2,40 +2,34 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataStream\Formatter; +namespace Yiisoft\DataResponse\Modern\Formatter; use DOMDocument; use DOMElement; use DOMException; use DOMText; +use Psr\Http\Message\ResponseInterface; use Traversable; -use Yiisoft\DataResponse\Modern\DataStream\DataFormatterInterface; +use Yiisoft\Http\Header; use Yiisoft\Strings\NumericHelper; -use function is_array; use function is_float; +use function is_array; use function is_int; use function is_object; -/** - * Formats data as XML string. - */ -final class XmlDataFormatter implements DataFormatterInterface +final class XmlFormatter implements FormatterInterface { private const DEFAULT_ITEM_TAG_NAME = 'item'; - /** - * @param string $encoding The encoding for the XML document. - * @param string $version The XML version. - * @param string $rootTag The name of the root element. If an empty value is set, the root tag should not be added. - */ public function __construct( private readonly string $encoding = 'UTF-8', private readonly string $version = '1.0', private readonly string $rootTag = 'response', + private readonly string $contentType = 'application/xml', ) {} - public function format(mixed $data): string + public function formatData(mixed $data): string { if (empty($data)) { return ''; @@ -54,6 +48,11 @@ public function format(mixed $data): string return (string) $dom->saveXML(); } + public function formatResponse(ResponseInterface $response): ResponseInterface + { + return $response->withHeader(Header::CONTENT_TYPE, "$this->contentType; charset=$this->encoding"); + } + /** * Builds the data to use in XML. * diff --git a/src/Modern/Middleware/DataResponseMiddleware.php b/src/Modern/Middleware/DataResponseMiddleware.php new file mode 100644 index 0000000..9a90d8f --- /dev/null +++ b/src/Modern/Middleware/DataResponseMiddleware.php @@ -0,0 +1,28 @@ +handle($request); + $body = $response->getBody(); + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); + } +} diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Modern/Middleware/HtmlDataResponseMiddleware.php index 715543d..1c5ad19 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/HtmlDataResponseMiddleware.php @@ -8,21 +8,22 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; final class HtmlDataResponseMiddleware implements MiddlewareInterface { public function __construct( - private readonly HtmlDataResponseFormatter $formatter = new HtmlDataResponseFormatter(), + private readonly HtmlFormatter $formatter = new HtmlFormatter(), ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); $body = $response->getBody(); - return $body instanceof DataStream - ? $this->formatter->format($body, $response) - : $response; + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Modern/Middleware/JsonDataResponseMiddleware.php index 870ce10..eb79653 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Modern/Middleware/JsonDataResponseMiddleware.php @@ -8,21 +8,22 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\JsonDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; final class JsonDataResponseMiddleware implements MiddlewareInterface { public function __construct( - private readonly JsonDataResponseFormatter $formatter = new JsonDataResponseFormatter(), + private readonly JsonFormatter $formatter = new JsonFormatter(), ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); $body = $response->getBody(); - return $body instanceof DataStream - ? $this->formatter->format($body, $response) - : $response; + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php index 82216ff..145e9f7 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php @@ -8,21 +8,22 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\PlainTextDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; final class PlainTextDataResponseMiddleware implements MiddlewareInterface { public function __construct( - private readonly PlainTextDataResponseFormatter $formatter = new PlainTextDataResponseFormatter(), + private readonly PlainTextFormatter $formatter = new PlainTextFormatter(), ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); $body = $response->getBody(); - return $body instanceof DataStream - ? $this->formatter->format($body, $response) - : $response; + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Modern/Middleware/XmlDataResponseMiddleware.php index ca53421..e6d6cdc 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/XmlDataResponseMiddleware.php @@ -8,21 +8,22 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; final class XmlDataResponseMiddleware implements MiddlewareInterface { public function __construct( - private readonly XmlDataResponseFormatter $formatter = new XmlDataResponseFormatter(), + private readonly XmlFormatter $formatter = new XmlFormatter(), ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); $body = $response->getBody(); - return $body instanceof DataStream - ? $this->formatter->format($body, $response) - : $response; + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php index bd693d5..a90fc9b 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -7,15 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Stringable; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; use Yiisoft\Http\Status; final class HtmlResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly HtmlDataResponseFormatter $formatter, + private readonly HtmlFormatter $formatter, ) {} public function createResponse( @@ -23,9 +23,10 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - return $this->formatter->format( - new DataStream($data), - $this->responseFactory->createResponse($code, $reasonPhrase), - ); + $body = new DataStream($data, $this->formatter); + $response = $this->responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody($body); + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php index 5b89a03..b3d2a5a 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -6,15 +6,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\JsonDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; use Yiisoft\Http\Status; final class JsonResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly JsonDataResponseFormatter $formatter, + private readonly JsonFormatter $formatter, ) {} public function createResponse( @@ -22,9 +22,10 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - return $this->formatter->format( - new DataStream($data), - $this->responseFactory->createResponse($code, $reasonPhrase), - ); + $body = new DataStream($data, $this->formatter); + $response = $this->responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody($body); + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index cab2ee7..7835d67 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -7,15 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Stringable; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\PlainTextDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; use Yiisoft\Http\Status; final class PlainTextResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly PlainTextDataResponseFormatter $formatter, + private readonly PlainTextFormatter $formatter, ) {} public function createResponse( @@ -23,9 +23,10 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - return $this->formatter->format( - new DataStream($data), - $this->responseFactory->createResponse($code, $reasonPhrase), - ); + $body = new DataStream($data, $this->formatter); + $response = $this->responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody($body); + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php index db29c58..5f908ea 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -6,15 +6,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataResponse\Formatter\XmlDataResponseFormatter; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; use Yiisoft\Http\Status; final class XmlResponseFactory { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly XmlDataResponseFormatter $formatter, + private readonly XmlFormatter $formatter, ) {} public function createResponse( @@ -22,9 +22,10 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - return $this->formatter->format( - new DataStream($data), - $this->responseFactory->createResponse($code, $reasonPhrase), - ); + $body = new DataStream($data, $this->formatter); + $response = $this->responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody($body); + return $this->formatter->formatResponse($response); } } From fca3c4ff40e3766df5ab3bfbe3c16cac013d1964 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 26 Jan 2026 16:50:21 +0300 Subject: [PATCH 19/64] improve --- .../DataResponse/DataResponseFactory.php | 5 +++- .../AbstractDataResponseMiddleware.php | 27 +++++++++++++++++++ .../Middleware/DataResponseMiddleware.php | 19 +++---------- .../Middleware/HtmlDataResponseMiddleware.php | 20 +++----------- .../Middleware/JsonDataResponseMiddleware.php | 20 +++----------- .../PlainTextDataResponseMiddleware.php | 20 +++----------- .../Middleware/XmlDataResponseMiddleware.php | 20 +++----------- 7 files changed, 46 insertions(+), 85 deletions(-) create mode 100644 src/Modern/Middleware/AbstractDataResponseMiddleware.php diff --git a/src/Modern/DataResponse/DataResponseFactory.php b/src/Modern/DataResponse/DataResponseFactory.php index 075cebb..29af1ce 100644 --- a/src/Modern/DataResponse/DataResponseFactory.php +++ b/src/Modern/DataResponse/DataResponseFactory.php @@ -7,12 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; +use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; use Yiisoft\Http\Status; final class DataResponseFactory implements DataResponseFactoryInterface { public function __construct( private readonly ResponseFactoryInterface $responseFactory, + private readonly FormatterInterface $defaultFormatter = new PlainTextFormatter(), ) {} public function createResponse( @@ -22,6 +25,6 @@ public function createResponse( ): ResponseInterface { return $this->responseFactory ->createResponse($code, $reasonPhrase) - ->withBody(new DataStream($data)); + ->withBody(new DataStream($data, $this->defaultFormatter)); } } diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php new file mode 100644 index 0000000..0dc2fda --- /dev/null +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -0,0 +1,27 @@ +handle($request); + $body = $response->getBody(); + if ($body instanceof DataStream) { + $body->changeFormatter($this->formatter); + } + return $this->formatter->formatResponse($response); + } +} diff --git a/src/Modern/Middleware/DataResponseMiddleware.php b/src/Modern/Middleware/DataResponseMiddleware.php index 9a90d8f..1fe23f2 100644 --- a/src/Modern/Middleware/DataResponseMiddleware.php +++ b/src/Modern/Middleware/DataResponseMiddleware.php @@ -4,25 +4,12 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; -final class DataResponseMiddleware +final class DataResponseMiddleware extends AbstractDataResponseMiddleware { - public function __construct( - private readonly FormatterInterface $formatter, - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(FormatterInterface $formatter) { - $response = $handler->handle($request); - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - } - return $this->formatter->formatResponse($response); + $this->formatter = $formatter; } } diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Modern/Middleware/HtmlDataResponseMiddleware.php index 1c5ad19..1371f7d 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/HtmlDataResponseMiddleware.php @@ -4,26 +4,12 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; -final class HtmlDataResponseMiddleware implements MiddlewareInterface +final class HtmlDataResponseMiddleware extends AbstractDataResponseMiddleware { - public function __construct( - private readonly HtmlFormatter $formatter = new HtmlFormatter(), - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(HtmlFormatter $formatter = new HtmlFormatter()) { - $response = $handler->handle($request); - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - } - return $this->formatter->formatResponse($response); + $this->formatter = $formatter; } } diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Modern/Middleware/JsonDataResponseMiddleware.php index eb79653..6060415 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Modern/Middleware/JsonDataResponseMiddleware.php @@ -4,26 +4,12 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; -final class JsonDataResponseMiddleware implements MiddlewareInterface +final class JsonDataResponseMiddleware extends AbstractDataResponseMiddleware { - public function __construct( - private readonly JsonFormatter $formatter = new JsonFormatter(), - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(JsonFormatter $formatter = new JsonFormatter()) { - $response = $handler->handle($request); - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - } - return $this->formatter->formatResponse($response); + $this->formatter = $formatter; } } diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php index 145e9f7..1d17b10 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php @@ -4,26 +4,12 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; -final class PlainTextDataResponseMiddleware implements MiddlewareInterface +final class PlainTextDataResponseMiddleware extends AbstractDataResponseMiddleware { - public function __construct( - private readonly PlainTextFormatter $formatter = new PlainTextFormatter(), - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(PlainTextFormatter $formatter = new PlainTextFormatter()) { - $response = $handler->handle($request); - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - } - return $this->formatter->formatResponse($response); + $this->formatter = $formatter; } } diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Modern/Middleware/XmlDataResponseMiddleware.php index e6d6cdc..d96faab 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/XmlDataResponseMiddleware.php @@ -4,26 +4,12 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; -final class XmlDataResponseMiddleware implements MiddlewareInterface +final class XmlDataResponseMiddleware extends AbstractDataResponseMiddleware { - public function __construct( - private readonly XmlFormatter $formatter = new XmlFormatter(), - ) {} - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + public function __construct(XmlFormatter $formatter = new XmlFormatter()) { - $response = $handler->handle($request); - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - } - return $this->formatter->formatResponse($response); + $this->formatter = $formatter; } } From 75605b5dca46c41c079c5159068b748e9fb6ce8a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 27 Jan 2026 11:37:16 +0300 Subject: [PATCH 20/64] improve --- src/Modern/DataStream/DataStream.php | 2 +- src/Modern/Middleware/AbstractDataResponseMiddleware.php | 5 ++++- src/Modern/ResponseFactory/HtmlResponseFactory.php | 2 +- src/Modern/ResponseFactory/JsonResponseFactory.php | 2 +- src/Modern/ResponseFactory/PlainTextResponseFactory.php | 2 +- src/Modern/ResponseFactory/XmlResponseFactory.php | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Modern/DataStream/DataStream.php b/src/Modern/DataStream/DataStream.php index 51950c2..f376d29 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -129,7 +129,7 @@ public function getMetadata(?string $key = null) /** * Gets or creates the inner stream by formatting the data. */ - private function getFormatted(): StreamInterface + public function getFormatted(): StreamInterface { if ($this->formatted !== null) { return $this->formatted; diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php index 0dc2fda..2fb0f12 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -18,10 +18,13 @@ abstract class AbstractDataResponseMiddleware implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); + $body = $response->getBody(); if ($body instanceof DataStream) { $body->changeFormatter($this->formatter); + return $this->formatter->formatResponse($response); } - return $this->formatter->formatResponse($response); + + return $response; } } diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php index a90fc9b..bc82f6c 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -23,7 +23,7 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new DataStream($data, $this->formatter); + $body = (new DataStream($data, $this->formatter))->getFormatted(); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php index b3d2a5a..a9a5de6 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -22,7 +22,7 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new DataStream($data, $this->formatter); + $body = (new DataStream($data, $this->formatter))->getFormatted(); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index 7835d67..617cf88 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -23,7 +23,7 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new DataStream($data, $this->formatter); + $body = (new DataStream($data, $this->formatter))->getFormatted(); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php index 5f908ea..107b488 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -22,7 +22,7 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new DataStream($data, $this->formatter); + $body = (new DataStream($data, $this->formatter))->getFormatted(); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); From 7f7e1462c2a7edafaf5a2afc1c2cfb8087f8d0f3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 27 Jan 2026 15:45:24 +0300 Subject: [PATCH 21/64] Introduce `FormattedResponseFactoryInterface` --- .../AbstractDataResponseMiddleware.php | 2 +- .../AbstractFormattedResponseFactory.php | 29 +++++++++++++++++++ .../FormattedResponseFactory.php | 19 ++++++++++++ .../FormattedResponseFactoryInterface.php | 17 +++++++++++ .../ResponseFactory/HtmlResponseFactory.php | 25 ++++------------ .../ResponseFactory/JsonResponseFactory.php | 24 ++++----------- .../PlainTextResponseFactory.php | 25 ++++------------ .../ResponseFactory/XmlResponseFactory.php | 24 ++++----------- 8 files changed, 90 insertions(+), 75 deletions(-) create mode 100644 src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php create mode 100644 src/Modern/ResponseFactory/FormattedResponseFactory.php create mode 100644 src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php index 2fb0f12..6b90c7e 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -15,7 +15,7 @@ abstract class AbstractDataResponseMiddleware implements MiddlewareInterface { protected readonly FormatterInterface $formatter; - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + final public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php new file mode 100644 index 0000000..171a886 --- /dev/null +++ b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php @@ -0,0 +1,29 @@ +formatter))->getFormatted(); + $response = $this->responseFactory + ->createResponse($code, $reasonPhrase) + ->withBody($body); + return $this->formatter->formatResponse($response); + } +} diff --git a/src/Modern/ResponseFactory/FormattedResponseFactory.php b/src/Modern/ResponseFactory/FormattedResponseFactory.php new file mode 100644 index 0000000..d6bb0c0 --- /dev/null +++ b/src/Modern/ResponseFactory/FormattedResponseFactory.php @@ -0,0 +1,19 @@ +responseFactory = $responseFactory; + $this->formatter = $formatter; + } +} diff --git a/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php b/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php new file mode 100644 index 0000000..d87aeae --- /dev/null +++ b/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php @@ -0,0 +1,17 @@ +formatter))->getFormatted(); - $response = $this->responseFactory - ->createResponse($code, $reasonPhrase) - ->withBody($body); - return $this->formatter->formatResponse($response); + ResponseFactoryInterface $responseFactory, + HtmlFormatter $formatter, + ) { + $this->responseFactory = $responseFactory; + $this->formatter = $formatter; } } diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php index a9a5de6..83c0164 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -5,27 +5,15 @@ namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; -use Yiisoft\Http\Status; -final class JsonResponseFactory +final class JsonResponseFactory extends AbstractFormattedResponseFactory { public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly JsonFormatter $formatter, - ) {} - - public function createResponse( - mixed $data = null, - int $code = Status::OK, - string $reasonPhrase = '', - ): ResponseInterface { - $body = (new DataStream($data, $this->formatter))->getFormatted(); - $response = $this->responseFactory - ->createResponse($code, $reasonPhrase) - ->withBody($body); - return $this->formatter->formatResponse($response); + ResponseFactoryInterface $responseFactory, + JsonFormatter $formatter, + ) { + $this->responseFactory = $responseFactory; + $this->formatter = $formatter; } } diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index 617cf88..304d93e 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -5,28 +5,15 @@ namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Stringable; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; -use Yiisoft\Http\Status; -final class PlainTextResponseFactory +final class PlainTextResponseFactory extends AbstractFormattedResponseFactory { public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly PlainTextFormatter $formatter, - ) {} - - public function createResponse( - string|bool|int|float|Stringable|null $data = null, - int $code = Status::OK, - string $reasonPhrase = '', - ): ResponseInterface { - $body = (new DataStream($data, $this->formatter))->getFormatted(); - $response = $this->responseFactory - ->createResponse($code, $reasonPhrase) - ->withBody($body); - return $this->formatter->formatResponse($response); + ResponseFactoryInterface $responseFactory, + PlainTextFormatter $formatter, + ) { + $this->responseFactory = $responseFactory; + $this->formatter = $formatter; } } diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php index 107b488..c4d7f07 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -5,27 +5,15 @@ namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; -use Yiisoft\Http\Status; -final class XmlResponseFactory +final class XmlResponseFactory extends AbstractFormattedResponseFactory { public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly XmlFormatter $formatter, - ) {} - - public function createResponse( - mixed $data = null, - int $code = Status::OK, - string $reasonPhrase = '', - ): ResponseInterface { - $body = (new DataStream($data, $this->formatter))->getFormatted(); - $response = $this->responseFactory - ->createResponse($code, $reasonPhrase) - ->withBody($body); - return $this->formatter->formatResponse($response); + ResponseFactoryInterface $responseFactory, + XmlFormatter $formatter, + ) { + $this->responseFactory = $responseFactory; + $this->formatter = $formatter; } } From cb1280b98bd679d2bf6153c4bd537ea32c7f59a1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 27 Jan 2026 16:34:38 +0300 Subject: [PATCH 22/64] Introduce `ContentNegotiator` --- ...ontentNegotiatorDataResponseMiddleware.php | 90 +++++++++++++++++++ .../ContentNegotiatorResponseFactory.php | 74 +++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php create mode 100644 src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php diff --git a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php new file mode 100644 index 0000000..cc8c6cf --- /dev/null +++ b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -0,0 +1,90 @@ + $formatters + * @param FormatterInterface|null $fallbackFormatter + */ + public function __construct( + private readonly array $formatters = [], + private readonly ?FormatterInterface $fallbackFormatter = null, + ) { + $this->checkFormatters($formatters); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $accepted = HeaderValueHelper::getSortedAcceptTypes( + $request->getHeader('Accept'), + ); + + $response = $handler->handle($request); + + foreach ($accepted as $accept) { + foreach ($this->formatters as $contentType => $formatter) { + if (str_contains($accept, $contentType)) { + return $this->format($response, $formatter); + } + } + } + + if ($this->fallbackFormatter === null) { + return $response; + } + + return $this->format($response, $this->fallbackFormatter); + } + + private function format(ResponseInterface $response, FormatterInterface $formatter): ResponseInterface + { + $body = $response->getBody(); + if ($body instanceof DataStream) { + $body->changeFormatter($formatter); + return $formatter->formatResponse($response); + } + + return $response; + } + + private function checkFormatters(array $formatters): void + { + foreach ($formatters as $contentType => $formatter) { + if (!is_string($contentType)) { + throw new RuntimeException( + sprintf( + 'Invalid formatter content type. A string is expected, "%s" is received.', + gettype($contentType), + ), + ); + } + + if (!($formatter instanceof FormatterInterface)) { + throw new RuntimeException( + sprintf( + 'Invalid formatter. A "%s" instance is expected, "%s" is received.', + FormatterInterface::class, + get_debug_type($formatter), + ), + ); + } + } + } +} diff --git a/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php new file mode 100644 index 0000000..983c960 --- /dev/null +++ b/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -0,0 +1,74 @@ + $factories + * @param FormattedResponseFactoryInterface $fallbackFactory + */ + public function __construct( + private readonly array $factories, + private readonly FormattedResponseFactoryInterface $fallbackFactory, + ) { + $this->checkFormatters($factories); + } + + public function createResponse( + RequestInterface $request, + mixed $data = null, + int $code = Status::OK, + string $reasonPhrase = '', + ): ResponseInterface { + $accepted = HeaderValueHelper::getSortedAcceptTypes( + $request->getHeader('Accept'), + ); + + foreach ($accepted as $accept) { + foreach ($this->factories as $contentType => $factory) { + if (str_contains($accept, $contentType)) { + return $factory->createResponse($data, $code, $reasonPhrase); + } + } + } + + return $this->fallbackFactory->createResponse($data, $code, $reasonPhrase); + } + + private function checkFormatters(array $formatters): void + { + foreach ($formatters as $contentType => $formatter) { + if (!is_string($contentType)) { + throw new RuntimeException( + sprintf( + 'Invalid formatter content type. A string is expected, "%s" is received.', + gettype($contentType), + ), + ); + } + + if (!($formatter instanceof FormattedResponseFactoryInterface)) { + throw new RuntimeException( + sprintf( + 'Invalid formatter. A "%s" instance is expected, "%s" is received.', + FormattedResponseFactoryInterface::class, + get_debug_type($formatter), + ), + ); + } + } + } +} From a4d0e67fd749e02a46466ee93b1c6223183ebd39 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 29 Jan 2026 18:10:06 +0300 Subject: [PATCH 23/64] Introduce `LazyFormattingStream` --- src/Modern/DataStream/DataStream.php | 70 +++++------ .../DataStream/LazyFormattingStream.php | 116 ++++++++++++++++++ .../AbstractDataResponseMiddleware.php | 7 +- ...ontentNegotiatorDataResponseMiddleware.php | 27 ++-- .../AbstractFormattedResponseFactory.php | 4 +- 5 files changed, 167 insertions(+), 57 deletions(-) create mode 100644 src/Modern/DataStream/LazyFormattingStream.php diff --git a/src/Modern/DataStream/DataStream.php b/src/Modern/DataStream/DataStream.php index f376d29..36bae3c 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -18,7 +18,7 @@ */ final class DataStream implements StreamInterface { - private ?StreamInterface $formatted = null; + private ?StreamInterface $prepared = null; /** * @param mixed $data The raw data to be formatted. @@ -31,24 +31,18 @@ public function __construct( public function __toString(): string { - return (string) $this->getFormatted(); + return (string) $this->getPrepared(); } - /** - * Changes the formatter and resets the stream state. - * - * @param FormatterInterface $formatter The new formatter to use. - */ - public function changeFormatter(FormatterInterface $formatter): void + public function format(FormatterInterface $formatter): StreamInterface { - $this->formatter = $formatter; - $this->resetState(); + return new LazyFormattingStream($this->data, $formatter); } /** * Changes the data and resets the stream state. * - * @param mixed $data The new data to be formatted. + * @param mixed $data The new data. */ public function changeData(mixed $data): void { @@ -58,90 +52,86 @@ public function changeData(mixed $data): void public function close(): void { - $this->getFormatted()->close(); + $this->getPrepared()->close(); } public function detach() { - return $this->getFormatted()->detach(); + return $this->getPrepared()->detach(); } public function getSize(): ?int { - return $this->getFormatted()->getSize(); + return $this->getPrepared()->getSize(); } public function tell(): int { - return $this->getFormatted()->tell(); + return $this->getPrepared()->tell(); } public function eof(): bool { - return $this->getFormatted()->eof(); + return $this->getPrepared()->eof(); } public function isSeekable(): bool { - return $this->getFormatted()->isSeekable(); + return $this->getPrepared()->isSeekable(); } public function seek(int $offset, int $whence = SEEK_SET): void { - $this->getFormatted()->seek($offset, $whence); + $this->getPrepared()->seek($offset, $whence); } public function rewind(): void { - $this->getFormatted()->rewind(); + $this->getPrepared()->rewind(); } public function isWritable(): bool { - return $this->getFormatted()->isWritable(); + return $this->getPrepared()->isWritable(); } public function write(string $string): int { - return $this->getFormatted()->write($string); + return $this->getPrepared()->write($string); } public function isReadable(): bool { - return $this->getFormatted()->isReadable(); + return $this->getPrepared()->isReadable(); } public function read(int $length): string { - return $this->getFormatted()->read($length); + return $this->getPrepared()->read($length); } public function getContents(): string { - return $this->getFormatted()->getContents(); + return $this->getPrepared()->getContents(); } public function getMetadata(?string $key = null) { - return $this->getFormatted()->getMetadata($key); + return $this->getPrepared()->getMetadata($key); } - /** - * Gets or creates the inner stream by formatting the data. - */ - public function getFormatted(): StreamInterface + public function getPrepared(): StreamInterface { - if ($this->formatted !== null) { - return $this->formatted; + if ($this->prepared !== null) { + return $this->prepared; } - $content = $this->formatter->formatData($this->data); - - $this->formatted = $content instanceof StreamInterface - ? $content - : new StringStream($content); + $this->prepared = new LazyFormattingStream( + $this->data, + $this->formatter, + ); - return $this->formatted; + return $this->prepared; } /** @@ -149,9 +139,9 @@ public function getFormatted(): StreamInterface */ private function resetState(): void { - if ($this->formatted !== null) { - $this->formatted->close(); - $this->formatted = null; + if ($this->prepared !== null) { + $this->prepared->close(); + $this->prepared = null; } } } diff --git a/src/Modern/DataStream/LazyFormattingStream.php b/src/Modern/DataStream/LazyFormattingStream.php new file mode 100644 index 0000000..4621911 --- /dev/null +++ b/src/Modern/DataStream/LazyFormattingStream.php @@ -0,0 +1,116 @@ +getFormatted(); + } + + public function close(): void + { + $this->getFormatted()->close(); + } + + public function detach() + { + return $this->getFormatted()->detach(); + } + + public function getSize(): ?int + { + return $this->getFormatted()->getSize(); + } + + public function tell(): int + { + return $this->getFormatted()->tell(); + } + + public function eof(): bool + { + return $this->getFormatted()->eof(); + } + + public function isSeekable(): bool + { + return $this->getFormatted()->isSeekable(); + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + $this->getFormatted()->seek($offset, $whence); + } + + public function rewind(): void + { + $this->getFormatted()->rewind(); + } + + public function isWritable(): bool + { + return $this->getFormatted()->isWritable(); + } + + public function write(string $string): int + { + return $this->getFormatted()->write($string); + } + + public function isReadable(): bool + { + return $this->getFormatted()->isReadable(); + } + + public function read(int $length): string + { + return $this->getFormatted()->read($length); + } + + public function getContents(): string + { + return $this->getFormatted()->getContents(); + } + + public function getMetadata(?string $key = null) + { + return $this->getFormatted()->getMetadata($key); + } + + /** + * Gets or creates the inner stream by formatting the data. + */ + public function getFormatted(): StreamInterface + { + if ($this->formatted !== null) { + return $this->formatted; + } + + $content = $this->formatter->formatData($this->data); + + $this->formatted = $content instanceof StreamInterface + ? $content + : new StringStream($content); + + return $this->formatted; + } +} + diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php index 6b90c7e..d92c1a0 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -21,8 +21,11 @@ final public function process(ServerRequestInterface $request, RequestHandlerInt $body = $response->getBody(); if ($body instanceof DataStream) { - $body->changeFormatter($this->formatter); - return $this->formatter->formatResponse($response); + return $this->formatter + ->formatResponse($response) + ->withBody( + $body->format($this->formatter), + ); } return $response; diff --git a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php index cc8c6cf..9dbf7b2 100644 --- a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -37,11 +37,19 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ); $response = $handler->handle($request); + $body = $response->getBody(); + if (!$body instanceof DataStream) { + return $response; + } foreach ($accepted as $accept) { foreach ($this->formatters as $contentType => $formatter) { if (str_contains($accept, $contentType)) { - return $this->format($response, $formatter); + return $formatter + ->formatResponse($response) + ->withBody( + $body->format($formatter), + ); } } } @@ -50,18 +58,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - return $this->format($response, $this->fallbackFormatter); - } - - private function format(ResponseInterface $response, FormatterInterface $formatter): ResponseInterface - { - $body = $response->getBody(); - if ($body instanceof DataStream) { - $body->changeFormatter($formatter); - return $formatter->formatResponse($response); - } - - return $response; + return $this->fallbackFormatter + ->formatResponse($response) + ->withBody( + $body->format($this->fallbackFormatter), + ); } private function checkFormatters(array $formatters): void diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php index 171a886..7708cc0 100644 --- a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; +use Yiisoft\DataResponse\Modern\DataStream\LazyFormattingStream; use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; use Yiisoft\Http\Status; @@ -20,7 +20,7 @@ final public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = (new DataStream($data, $this->formatter))->getFormatted(); + $body = new LazyFormattingStream($data, $this->formatter); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); From e9ee3d96ffdaec619900b0a831d9f7addd3fc98c Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:10:42 +0000 Subject: [PATCH 24/64] Apply PHP CS Fixer and Rector changes (CI) --- src/Modern/DataStream/LazyFormattingStream.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Modern/DataStream/LazyFormattingStream.php b/src/Modern/DataStream/LazyFormattingStream.php index 4621911..f43fc81 100644 --- a/src/Modern/DataStream/LazyFormattingStream.php +++ b/src/Modern/DataStream/LazyFormattingStream.php @@ -8,6 +8,8 @@ use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; +use const SEEK_SET; + /** * @internal */ @@ -113,4 +115,3 @@ public function getFormatted(): StreamInterface return $this->formatted; } } - From eda5c26dbbc5319dd83e46e2f02d3e04e5517c64 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 29 Jan 2026 21:15:32 +0300 Subject: [PATCH 25/64] improve --- src/Modern/DataStream/DataStream.php | 72 ++++++----- .../DataStream/LazyFormattingStream.php | 117 ------------------ .../AbstractDataResponseMiddleware.php | 11 +- ...ontentNegotiatorDataResponseMiddleware.php | 16 +-- .../AbstractFormattedResponseFactory.php | 4 +- 5 files changed, 52 insertions(+), 168 deletions(-) delete mode 100644 src/Modern/DataStream/LazyFormattingStream.php diff --git a/src/Modern/DataStream/DataStream.php b/src/Modern/DataStream/DataStream.php index 36bae3c..e9b4d7c 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -18,25 +18,33 @@ */ final class DataStream implements StreamInterface { - private ?StreamInterface $prepared = null; + private ?StreamInterface $formatted = null; /** * @param mixed $data The raw data to be formatted. - * @param FormatterInterface $formatter The formatter to use. + * @param FormatterInterface|null $formatter The formatter to use. + * @param FormatterInterface $fallbackFormatter The fallback formatter to use when formatter is not set. */ public function __construct( private mixed $data, - private FormatterInterface $formatter = new PlainTextFormatter(), + private ?FormatterInterface $formatter = null, + private readonly FormatterInterface $fallbackFormatter = new PlainTextFormatter(), ) {} public function __toString(): string { - return (string) $this->getPrepared(); + return (string) $this->getFormatted(); } - public function format(FormatterInterface $formatter): StreamInterface + public function hasFormatter(): bool { - return new LazyFormattingStream($this->data, $formatter); + return $this->formatter !== null; + } + + public function changeFormatter(FormatterInterface $formatter): void + { + $this->formatter = $formatter; + $this->resetState(); } /** @@ -52,86 +60,88 @@ public function changeData(mixed $data): void public function close(): void { - $this->getPrepared()->close(); + $this->getFormatted()->close(); } public function detach() { - return $this->getPrepared()->detach(); + return $this->getFormatted()->detach(); } public function getSize(): ?int { - return $this->getPrepared()->getSize(); + return $this->getFormatted()->getSize(); } public function tell(): int { - return $this->getPrepared()->tell(); + return $this->getFormatted()->tell(); } public function eof(): bool { - return $this->getPrepared()->eof(); + return $this->getFormatted()->eof(); } public function isSeekable(): bool { - return $this->getPrepared()->isSeekable(); + return $this->getFormatted()->isSeekable(); } public function seek(int $offset, int $whence = SEEK_SET): void { - $this->getPrepared()->seek($offset, $whence); + $this->getFormatted()->seek($offset, $whence); } public function rewind(): void { - $this->getPrepared()->rewind(); + $this->getFormatted()->rewind(); } public function isWritable(): bool { - return $this->getPrepared()->isWritable(); + return $this->getFormatted()->isWritable(); } public function write(string $string): int { - return $this->getPrepared()->write($string); + return $this->getFormatted()->write($string); } public function isReadable(): bool { - return $this->getPrepared()->isReadable(); + return $this->getFormatted()->isReadable(); } public function read(int $length): string { - return $this->getPrepared()->read($length); + return $this->getFormatted()->read($length); } public function getContents(): string { - return $this->getPrepared()->getContents(); + return $this->getFormatted()->getContents(); } public function getMetadata(?string $key = null) { - return $this->getPrepared()->getMetadata($key); + return $this->getFormatted()->getMetadata($key); } - public function getPrepared(): StreamInterface + private function getFormatted(): StreamInterface { - if ($this->prepared !== null) { - return $this->prepared; + if ($this->formatted !== null) { + return $this->formatted; } - $this->prepared = new LazyFormattingStream( - $this->data, - $this->formatter, - ); + $formatter = $this->formatter ?? $this->fallbackFormatter; + $content = $formatter->formatData($this->data); + + $this->formatted = $content instanceof StreamInterface + ? $content + : new StringStream($content); - return $this->prepared; + return $this->formatted; } /** @@ -139,9 +149,9 @@ public function getPrepared(): StreamInterface */ private function resetState(): void { - if ($this->prepared !== null) { - $this->prepared->close(); - $this->prepared = null; + if ($this->formatted !== null) { + $this->formatted->close(); + $this->formatted = null; } } } diff --git a/src/Modern/DataStream/LazyFormattingStream.php b/src/Modern/DataStream/LazyFormattingStream.php deleted file mode 100644 index f43fc81..0000000 --- a/src/Modern/DataStream/LazyFormattingStream.php +++ /dev/null @@ -1,117 +0,0 @@ -getFormatted(); - } - - public function close(): void - { - $this->getFormatted()->close(); - } - - public function detach() - { - return $this->getFormatted()->detach(); - } - - public function getSize(): ?int - { - return $this->getFormatted()->getSize(); - } - - public function tell(): int - { - return $this->getFormatted()->tell(); - } - - public function eof(): bool - { - return $this->getFormatted()->eof(); - } - - public function isSeekable(): bool - { - return $this->getFormatted()->isSeekable(); - } - - public function seek(int $offset, int $whence = SEEK_SET): void - { - $this->getFormatted()->seek($offset, $whence); - } - - public function rewind(): void - { - $this->getFormatted()->rewind(); - } - - public function isWritable(): bool - { - return $this->getFormatted()->isWritable(); - } - - public function write(string $string): int - { - return $this->getFormatted()->write($string); - } - - public function isReadable(): bool - { - return $this->getFormatted()->isReadable(); - } - - public function read(int $length): string - { - return $this->getFormatted()->read($length); - } - - public function getContents(): string - { - return $this->getFormatted()->getContents(); - } - - public function getMetadata(?string $key = null) - { - return $this->getFormatted()->getMetadata($key); - } - - /** - * Gets or creates the inner stream by formatting the data. - */ - public function getFormatted(): StreamInterface - { - if ($this->formatted !== null) { - return $this->formatted; - } - - $content = $this->formatter->formatData($this->data); - - $this->formatted = $content instanceof StreamInterface - ? $content - : new StringStream($content); - - return $this->formatted; - } -} diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php index d92c1a0..ad1f31a 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -20,14 +20,11 @@ final public function process(ServerRequestInterface $request, RequestHandlerInt $response = $handler->handle($request); $body = $response->getBody(); - if ($body instanceof DataStream) { - return $this->formatter - ->formatResponse($response) - ->withBody( - $body->format($this->formatter), - ); + if (!$body instanceof DataStream || $body->hasFormatter()) { + return $response; } - return $response; + $body->changeFormatter($this->formatter); + return $this->formatter->formatResponse($response); } } diff --git a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php index 9dbf7b2..8a410e8 100644 --- a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -38,18 +38,15 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $handler->handle($request); $body = $response->getBody(); - if (!$body instanceof DataStream) { + if (!$body instanceof DataStream || $body->hasFormatter()) { return $response; } foreach ($accepted as $accept) { foreach ($this->formatters as $contentType => $formatter) { if (str_contains($accept, $contentType)) { - return $formatter - ->formatResponse($response) - ->withBody( - $body->format($formatter), - ); + $body->changeFormatter($formatter); + return $formatter->formatResponse($response); } } } @@ -58,11 +55,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - return $this->fallbackFormatter - ->formatResponse($response) - ->withBody( - $body->format($this->fallbackFormatter), - ); + $body->changeFormatter($this->fallbackFormatter); + return $this->fallbackFormatter->formatResponse($response); } private function checkFormatters(array $formatters): void diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php index 7708cc0..c1c9bea 100644 --- a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\LazyFormattingStream; +use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; use Yiisoft\Http\Status; @@ -20,7 +20,7 @@ final public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new LazyFormattingStream($data, $this->formatter); + $body = new DataStream($data, $this->formatter); $response = $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); From 343f9f4248f6bf4b64900346ebde9811d6cd802d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 30 Jan 2026 22:05:01 +0300 Subject: [PATCH 26/64] Fix properties --- .../Middleware/AbstractDataResponseMiddleware.php | 4 +++- src/Modern/Middleware/DataResponseMiddleware.php | 10 +--------- .../Middleware/HtmlDataResponseMiddleware.php | 2 +- .../Middleware/JsonDataResponseMiddleware.php | 2 +- .../Middleware/PlainTextDataResponseMiddleware.php | 2 +- .../Middleware/XmlDataResponseMiddleware.php | 2 +- .../AbstractFormattedResponseFactory.php | 6 ++++-- .../ResponseFactory/FormattedResponseFactory.php | 14 +------------- src/Modern/ResponseFactory/HtmlResponseFactory.php | 3 +-- src/Modern/ResponseFactory/JsonResponseFactory.php | 3 +-- .../ResponseFactory/PlainTextResponseFactory.php | 3 +-- src/Modern/ResponseFactory/XmlResponseFactory.php | 3 +-- 12 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Modern/Middleware/AbstractDataResponseMiddleware.php index ad1f31a..a2f85b6 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Modern/Middleware/AbstractDataResponseMiddleware.php @@ -13,7 +13,9 @@ abstract class AbstractDataResponseMiddleware implements MiddlewareInterface { - protected readonly FormatterInterface $formatter; + public function __construct( + private readonly FormatterInterface $formatter, + ) {} final public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/src/Modern/Middleware/DataResponseMiddleware.php b/src/Modern/Middleware/DataResponseMiddleware.php index 1fe23f2..d65d79d 100644 --- a/src/Modern/Middleware/DataResponseMiddleware.php +++ b/src/Modern/Middleware/DataResponseMiddleware.php @@ -4,12 +4,4 @@ namespace Yiisoft\DataResponse\Modern\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; - -final class DataResponseMiddleware extends AbstractDataResponseMiddleware -{ - public function __construct(FormatterInterface $formatter) - { - $this->formatter = $formatter; - } -} +final class DataResponseMiddleware extends AbstractDataResponseMiddleware {} diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Modern/Middleware/HtmlDataResponseMiddleware.php index 1371f7d..975066c 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/HtmlDataResponseMiddleware.php @@ -10,6 +10,6 @@ final class HtmlDataResponseMiddleware extends AbstractDataResponseMiddleware { public function __construct(HtmlFormatter $formatter = new HtmlFormatter()) { - $this->formatter = $formatter; + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Modern/Middleware/JsonDataResponseMiddleware.php index 6060415..7662f4c 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Modern/Middleware/JsonDataResponseMiddleware.php @@ -10,6 +10,6 @@ final class JsonDataResponseMiddleware extends AbstractDataResponseMiddleware { public function __construct(JsonFormatter $formatter = new JsonFormatter()) { - $this->formatter = $formatter; + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php index 1d17b10..c1f32c7 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Modern/Middleware/PlainTextDataResponseMiddleware.php @@ -10,6 +10,6 @@ final class PlainTextDataResponseMiddleware extends AbstractDataResponseMiddlewa { public function __construct(PlainTextFormatter $formatter = new PlainTextFormatter()) { - $this->formatter = $formatter; + parent::__construct($formatter); } } diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Modern/Middleware/XmlDataResponseMiddleware.php index d96faab..c0021cf 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Modern/Middleware/XmlDataResponseMiddleware.php @@ -10,6 +10,6 @@ final class XmlDataResponseMiddleware extends AbstractDataResponseMiddleware { public function __construct(XmlFormatter $formatter = new XmlFormatter()) { - $this->formatter = $formatter; + parent::__construct($formatter); } } diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php index c1c9bea..c667a15 100644 --- a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php @@ -12,8 +12,10 @@ abstract class AbstractFormattedResponseFactory implements FormattedResponseFactoryInterface { - protected ResponseFactoryInterface $responseFactory; - protected FormatterInterface $formatter; + public function __construct( + private readonly ResponseFactoryInterface $responseFactory, + private readonly FormatterInterface $formatter, + ) {} final public function createResponse( mixed $data = null, diff --git a/src/Modern/ResponseFactory/FormattedResponseFactory.php b/src/Modern/ResponseFactory/FormattedResponseFactory.php index d6bb0c0..51646e2 100644 --- a/src/Modern/ResponseFactory/FormattedResponseFactory.php +++ b/src/Modern/ResponseFactory/FormattedResponseFactory.php @@ -4,16 +4,4 @@ namespace Yiisoft\DataResponse\Modern\ResponseFactory; -use Psr\Http\Message\ResponseFactoryInterface; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; - -final class FormattedResponseFactory extends AbstractFormattedResponseFactory -{ - public function __construct( - ResponseFactoryInterface $responseFactory, - FormatterInterface $formatter, - ) { - $this->responseFactory = $responseFactory; - $this->formatter = $formatter; - } -} +final class FormattedResponseFactory extends AbstractFormattedResponseFactory {} diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/Modern/ResponseFactory/HtmlResponseFactory.php index 8e42b5b..ef382e6 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/Modern/ResponseFactory/HtmlResponseFactory.php @@ -13,7 +13,6 @@ public function __construct( ResponseFactoryInterface $responseFactory, HtmlFormatter $formatter, ) { - $this->responseFactory = $responseFactory; - $this->formatter = $formatter; + parent::__construct($responseFactory, $formatter); } } diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/Modern/ResponseFactory/JsonResponseFactory.php index 83c0164..7e707da 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/Modern/ResponseFactory/JsonResponseFactory.php @@ -13,7 +13,6 @@ public function __construct( ResponseFactoryInterface $responseFactory, JsonFormatter $formatter, ) { - $this->responseFactory = $responseFactory; - $this->formatter = $formatter; + parent::__construct($responseFactory, $formatter); } } diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/Modern/ResponseFactory/PlainTextResponseFactory.php index 304d93e..7e42218 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/Modern/ResponseFactory/PlainTextResponseFactory.php @@ -13,7 +13,6 @@ public function __construct( ResponseFactoryInterface $responseFactory, PlainTextFormatter $formatter, ) { - $this->responseFactory = $responseFactory; - $this->formatter = $formatter; + parent::__construct($responseFactory, $formatter); } } diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/Modern/ResponseFactory/XmlResponseFactory.php index c4d7f07..1deaeb3 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/Modern/ResponseFactory/XmlResponseFactory.php @@ -13,7 +13,6 @@ public function __construct( ResponseFactoryInterface $responseFactory, XmlFormatter $formatter, ) { - $this->responseFactory = $responseFactory; - $this->formatter = $formatter; + parent::__construct($responseFactory, $formatter); } } From cc5a83a76c0fb98948f1598424072683bb7273f6 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 30 Jan 2026 22:22:48 +0300 Subject: [PATCH 27/64] Improve `DataResponseFactory` --- src/Modern/DataResponse/DataResponseFactory.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Modern/DataResponse/DataResponseFactory.php b/src/Modern/DataResponse/DataResponseFactory.php index 29af1ce..a80f240 100644 --- a/src/Modern/DataResponse/DataResponseFactory.php +++ b/src/Modern/DataResponse/DataResponseFactory.php @@ -15,7 +15,8 @@ final class DataResponseFactory implements DataResponseFactoryInterface { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly FormatterInterface $defaultFormatter = new PlainTextFormatter(), + private readonly ?FormatterInterface $formatter = null, + private readonly FormatterInterface $fallbackFormatter = new PlainTextFormatter(), ) {} public function createResponse( @@ -23,8 +24,9 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { + $body = new DataStream($data, $this->formatter, $this->fallbackFormatter); return $this->responseFactory ->createResponse($code, $reasonPhrase) - ->withBody(new DataStream($data, $this->defaultFormatter)); + ->withBody($body); } } From 1603a9460df32721e3adc680dbf10b33ace00dd3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 30 Jan 2026 23:01:09 +0300 Subject: [PATCH 28/64] Remove `FormattedResponseFactoryInterface` --- .../AbstractFormattedResponseFactory.php | 2 +- .../ContentNegotiatorResponseFactory.php | 10 +++++----- .../DataResponseFactory.php | 2 +- .../DataResponseFactoryInterface.php | 2 +- .../FormattedResponseFactoryInterface.php | 17 ----------------- 5 files changed, 8 insertions(+), 25 deletions(-) rename src/Modern/{DataResponse => ResponseFactory}/DataResponseFactory.php (94%) rename src/Modern/{DataResponse => ResponseFactory}/DataResponseFactoryInterface.php (84%) delete mode 100644 src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php index c667a15..d8f97cf 100644 --- a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php @@ -10,7 +10,7 @@ use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; use Yiisoft\Http\Status; -abstract class AbstractFormattedResponseFactory implements FormattedResponseFactoryInterface +abstract class AbstractFormattedResponseFactory implements DataResponseFactoryInterface { public function __construct( private readonly ResponseFactoryInterface $responseFactory, diff --git a/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php index 983c960..d81e05d 100644 --- a/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -17,12 +17,12 @@ final class ContentNegotiatorResponseFactory { /** - * @param array $factories - * @param FormattedResponseFactoryInterface $fallbackFactory + * @param array $factories + * @param DataResponseFactoryInterface $fallbackFactory */ public function __construct( private readonly array $factories, - private readonly FormattedResponseFactoryInterface $fallbackFactory, + private readonly DataResponseFactoryInterface $fallbackFactory, ) { $this->checkFormatters($factories); } @@ -60,11 +60,11 @@ private function checkFormatters(array $formatters): void ); } - if (!($formatter instanceof FormattedResponseFactoryInterface)) { + if (!($formatter instanceof DataResponseFactoryInterface)) { throw new RuntimeException( sprintf( 'Invalid formatter. A "%s" instance is expected, "%s" is received.', - FormattedResponseFactoryInterface::class, + DataResponseFactoryInterface::class, get_debug_type($formatter), ), ); diff --git a/src/Modern/DataResponse/DataResponseFactory.php b/src/Modern/ResponseFactory/DataResponseFactory.php similarity index 94% rename from src/Modern/DataResponse/DataResponseFactory.php rename to src/Modern/ResponseFactory/DataResponseFactory.php index a80f240..adcd061 100644 --- a/src/Modern/DataResponse/DataResponseFactory.php +++ b/src/Modern/ResponseFactory/DataResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponse; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/DataResponse/DataResponseFactoryInterface.php b/src/Modern/ResponseFactory/DataResponseFactoryInterface.php similarity index 84% rename from src/Modern/DataResponse/DataResponseFactoryInterface.php rename to src/Modern/ResponseFactory/DataResponseFactoryInterface.php index 5d7a424..1953732 100644 --- a/src/Modern/DataResponse/DataResponseFactoryInterface.php +++ b/src/Modern/ResponseFactory/DataResponseFactoryInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataResponse; +namespace Yiisoft\DataResponse\Modern\ResponseFactory; use Psr\Http\Message\ResponseInterface; use Yiisoft\Http\Status; diff --git a/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php b/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php deleted file mode 100644 index d87aeae..0000000 --- a/src/Modern/ResponseFactory/FormattedResponseFactoryInterface.php +++ /dev/null @@ -1,17 +0,0 @@ - Date: Sat, 31 Jan 2026 15:11:14 +0300 Subject: [PATCH 29/64] Improve `DataResponseFactory` --- src/Modern/DataStream/DataStream.php | 4 ++-- src/Modern/ResponseFactory/DataResponseFactory.php | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Modern/DataStream/DataStream.php b/src/Modern/DataStream/DataStream.php index e9b4d7c..30e3c6c 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/Modern/DataStream/DataStream.php @@ -6,7 +6,7 @@ use Psr\Http\Message\StreamInterface; use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; +use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; use const SEEK_SET; @@ -28,7 +28,7 @@ final class DataStream implements StreamInterface public function __construct( private mixed $data, private ?FormatterInterface $formatter = null, - private readonly FormatterInterface $fallbackFormatter = new PlainTextFormatter(), + private readonly FormatterInterface $fallbackFormatter = new HtmlFormatter(), ) {} public function __toString(): string diff --git a/src/Modern/ResponseFactory/DataResponseFactory.php b/src/Modern/ResponseFactory/DataResponseFactory.php index adcd061..9f1d0cf 100644 --- a/src/Modern/ResponseFactory/DataResponseFactory.php +++ b/src/Modern/ResponseFactory/DataResponseFactory.php @@ -8,15 +8,14 @@ use Psr\Http\Message\ResponseInterface; use Yiisoft\DataResponse\Modern\DataStream\DataStream; use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; +use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; use Yiisoft\Http\Status; final class DataResponseFactory implements DataResponseFactoryInterface { public function __construct( private readonly ResponseFactoryInterface $responseFactory, - private readonly ?FormatterInterface $formatter = null, - private readonly FormatterInterface $fallbackFormatter = new PlainTextFormatter(), + private readonly FormatterInterface $fallbackFormatter = new HtmlFormatter(), ) {} public function createResponse( @@ -24,7 +23,7 @@ public function createResponse( int $code = Status::OK, string $reasonPhrase = '', ): ResponseInterface { - $body = new DataStream($data, $this->formatter, $this->fallbackFormatter); + $body = new DataStream($data, fallbackFormatter: $this->fallbackFormatter); return $this->responseFactory ->createResponse($code, $reasonPhrase) ->withBody($body); From 762efd8106605bb24a62a41265c34b4d04158df5 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 1 Feb 2026 21:05:12 +0300 Subject: [PATCH 30/64] Fix `JsonFormatted` --- src/Modern/Formatter/JsonFormatter.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Modern/Formatter/JsonFormatter.php b/src/Modern/Formatter/JsonFormatter.php index 521c277..86805ee 100644 --- a/src/Modern/Formatter/JsonFormatter.php +++ b/src/Modern/Formatter/JsonFormatter.php @@ -25,10 +25,6 @@ public function __construct( */ public function formatData(mixed $data): string { - if ($data === null) { - return ''; - } - return Json::encode($data, $this->options); } From b1d032f8e365d5a1ffa4f4c812ff5de2721fad15 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 2 Feb 2026 17:52:23 +0300 Subject: [PATCH 31/64] move files --- src/{Modern => }/DataStream/DataStream.php | 6 +-- src/{Modern => }/DataStream/StringStream.php | 2 +- .../Formatter/FormatterInterface.php | 2 +- src/{Modern => }/Formatter/HtmlFormatter.php | 2 +- src/{Modern => }/Formatter/JsonFormatter.php | 2 +- .../Formatter/PlainTextFormatter.php | 2 +- src/Formatter/XmlDataInterface.php | 2 +- src/{Modern => }/Formatter/XmlFormatter.php | 4 +- .../AbstractDataResponseMiddleware.php | 6 +-- ...ontentNegotiatorDataResponseMiddleware.php | 6 +-- .../Middleware/DataResponseMiddleware.php | 2 +- .../Middleware/HtmlDataResponseMiddleware.php | 4 +- .../Middleware/JsonDataResponseMiddleware.php | 4 +- .../PlainTextDataResponseMiddleware.php | 4 +- .../Middleware/XmlDataResponseMiddleware.php | 4 +- src/Modern/Formatter/XmlDataInterface.php | 38 ------------------- .../AbstractFormattedResponseFactory.php | 6 +-- .../ContentNegotiatorResponseFactory.php | 2 +- .../ResponseFactory/DataResponseFactory.php | 8 ++-- .../DataResponseFactoryInterface.php | 2 +- .../FormattedResponseFactory.php | 2 +- .../ResponseFactory/HtmlResponseFactory.php | 4 +- .../ResponseFactory/JsonResponseFactory.php | 4 +- .../PlainTextResponseFactory.php | 4 +- .../ResponseFactory/XmlResponseFactory.php | 4 +- 25 files changed, 44 insertions(+), 82 deletions(-) rename src/{Modern => }/DataStream/DataStream.php (95%) rename src/{Modern => }/DataStream/StringStream.php (98%) rename src/{Modern => }/Formatter/FormatterInterface.php (85%) rename src/{Modern => }/Formatter/HtmlFormatter.php (95%) rename src/{Modern => }/Formatter/JsonFormatter.php (94%) rename src/{Modern => }/Formatter/PlainTextFormatter.php (95%) rename src/{Modern => }/Formatter/XmlFormatter.php (99%) rename src/{Modern => }/Middleware/AbstractDataResponseMiddleware.php (83%) rename src/{Modern => }/Middleware/ContentNegotiatorDataResponseMiddleware.php (93%) rename src/{Modern => }/Middleware/DataResponseMiddleware.php (68%) rename src/{Modern => }/Middleware/HtmlDataResponseMiddleware.php (70%) rename src/{Modern => }/Middleware/JsonDataResponseMiddleware.php (70%) rename src/{Modern => }/Middleware/PlainTextDataResponseMiddleware.php (70%) rename src/{Modern => }/Middleware/XmlDataResponseMiddleware.php (69%) delete mode 100644 src/Modern/Formatter/XmlDataInterface.php rename src/{Modern => }/ResponseFactory/AbstractFormattedResponseFactory.php (82%) rename src/{Modern => }/ResponseFactory/ContentNegotiatorResponseFactory.php (97%) rename src/{Modern => }/ResponseFactory/DataResponseFactory.php (77%) rename src/{Modern => }/ResponseFactory/DataResponseFactoryInterface.php (84%) rename src/{Modern => }/ResponseFactory/FormattedResponseFactory.php (67%) rename src/{Modern => }/ResponseFactory/HtmlResponseFactory.php (75%) rename src/{Modern => }/ResponseFactory/JsonResponseFactory.php (75%) rename src/{Modern => }/ResponseFactory/PlainTextResponseFactory.php (75%) rename src/{Modern => }/ResponseFactory/XmlResponseFactory.php (75%) diff --git a/src/Modern/DataStream/DataStream.php b/src/DataStream/DataStream.php similarity index 95% rename from src/Modern/DataStream/DataStream.php rename to src/DataStream/DataStream.php index 30e3c6c..2370160 100644 --- a/src/Modern/DataStream/DataStream.php +++ b/src/DataStream/DataStream.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataStream; +namespace Yiisoft\DataResponse\DataStream; use Psr\Http\Message\StreamInterface; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; -use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\Formatter\FormatterInterface; +use Yiisoft\DataResponse\Formatter\HtmlFormatter; use const SEEK_SET; diff --git a/src/Modern/DataStream/StringStream.php b/src/DataStream/StringStream.php similarity index 98% rename from src/Modern/DataStream/StringStream.php rename to src/DataStream/StringStream.php index a4c67ea..7a9d128 100644 --- a/src/Modern/DataStream/StringStream.php +++ b/src/DataStream/StringStream.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\DataStream; +namespace Yiisoft\DataResponse\DataStream; use Psr\Http\Message\StreamInterface; use RuntimeException; diff --git a/src/Modern/Formatter/FormatterInterface.php b/src/Formatter/FormatterInterface.php similarity index 85% rename from src/Modern/Formatter/FormatterInterface.php rename to src/Formatter/FormatterInterface.php index 4957204..a024e78 100644 --- a/src/Modern/Formatter/FormatterInterface.php +++ b/src/Formatter/FormatterInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; diff --git a/src/Modern/Formatter/HtmlFormatter.php b/src/Formatter/HtmlFormatter.php similarity index 95% rename from src/Modern/Formatter/HtmlFormatter.php rename to src/Formatter/HtmlFormatter.php index ea7e70b..c6790f1 100644 --- a/src/Modern/Formatter/HtmlFormatter.php +++ b/src/Formatter/HtmlFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; use RuntimeException; diff --git a/src/Modern/Formatter/JsonFormatter.php b/src/Formatter/JsonFormatter.php similarity index 94% rename from src/Modern/Formatter/JsonFormatter.php rename to src/Formatter/JsonFormatter.php index 86805ee..9e0e88f 100644 --- a/src/Modern/Formatter/JsonFormatter.php +++ b/src/Formatter/JsonFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Formatter; use JsonException; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/Formatter/PlainTextFormatter.php b/src/Formatter/PlainTextFormatter.php similarity index 95% rename from src/Modern/Formatter/PlainTextFormatter.php rename to src/Formatter/PlainTextFormatter.php index f762d88..faadcdf 100644 --- a/src/Modern/Formatter/PlainTextFormatter.php +++ b/src/Formatter/PlainTextFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; use RuntimeException; diff --git a/src/Formatter/XmlDataInterface.php b/src/Formatter/XmlDataInterface.php index 86ed959..4b67043 100644 --- a/src/Formatter/XmlDataInterface.php +++ b/src/Formatter/XmlDataInterface.php @@ -5,7 +5,7 @@ namespace Yiisoft\DataResponse\Formatter; /** - * XmlFormatDataInterface provides methods used when formatting objects {@see XmlDataResponseFormatter} as XML data. + * XmlFormatDataInterface provides methods used when formatting objects {@see XmlFormatter} as XML data. */ interface XmlDataInterface { diff --git a/src/Modern/Formatter/XmlFormatter.php b/src/Formatter/XmlFormatter.php similarity index 99% rename from src/Modern/Formatter/XmlFormatter.php rename to src/Formatter/XmlFormatter.php index a2b5a24..d1ea5fc 100644 --- a/src/Modern/Formatter/XmlFormatter.php +++ b/src/Formatter/XmlFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Formatter; +namespace Yiisoft\DataResponse\Formatter; use DOMDocument; use DOMElement; @@ -13,8 +13,8 @@ use Yiisoft\Http\Header; use Yiisoft\Strings\NumericHelper; -use function is_float; use function is_array; +use function is_float; use function is_int; use function is_object; diff --git a/src/Modern/Middleware/AbstractDataResponseMiddleware.php b/src/Middleware/AbstractDataResponseMiddleware.php similarity index 83% rename from src/Modern/Middleware/AbstractDataResponseMiddleware.php rename to src/Middleware/AbstractDataResponseMiddleware.php index a2f85b6..d8efd60 100644 --- a/src/Modern/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Middleware/AbstractDataResponseMiddleware.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; +use Yiisoft\DataResponse\DataStream\DataStream; +use Yiisoft\DataResponse\Formatter\FormatterInterface; abstract class AbstractDataResponseMiddleware implements MiddlewareInterface { diff --git a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php similarity index 93% rename from src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php rename to src/Middleware/ContentNegotiatorDataResponseMiddleware.php index 8a410e8..b1906a0 100644 --- a/src/Modern/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; +use Yiisoft\DataResponse\DataStream\DataStream; +use Yiisoft\DataResponse\Formatter\FormatterInterface; use Yiisoft\Http\HeaderValueHelper; use function gettype; diff --git a/src/Modern/Middleware/DataResponseMiddleware.php b/src/Middleware/DataResponseMiddleware.php similarity index 68% rename from src/Modern/Middleware/DataResponseMiddleware.php rename to src/Middleware/DataResponseMiddleware.php index d65d79d..efc4e33 100644 --- a/src/Modern/Middleware/DataResponseMiddleware.php +++ b/src/Middleware/DataResponseMiddleware.php @@ -2,6 +2,6 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; final class DataResponseMiddleware extends AbstractDataResponseMiddleware {} diff --git a/src/Modern/Middleware/HtmlDataResponseMiddleware.php b/src/Middleware/HtmlDataResponseMiddleware.php similarity index 70% rename from src/Modern/Middleware/HtmlDataResponseMiddleware.php rename to src/Middleware/HtmlDataResponseMiddleware.php index 975066c..9a9bdf5 100644 --- a/src/Modern/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Middleware/HtmlDataResponseMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\Formatter\HtmlFormatter; final class HtmlDataResponseMiddleware extends AbstractDataResponseMiddleware { diff --git a/src/Modern/Middleware/JsonDataResponseMiddleware.php b/src/Middleware/JsonDataResponseMiddleware.php similarity index 70% rename from src/Modern/Middleware/JsonDataResponseMiddleware.php rename to src/Middleware/JsonDataResponseMiddleware.php index 7662f4c..0a43e75 100644 --- a/src/Modern/Middleware/JsonDataResponseMiddleware.php +++ b/src/Middleware/JsonDataResponseMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; +use Yiisoft\DataResponse\Formatter\JsonFormatter; final class JsonDataResponseMiddleware extends AbstractDataResponseMiddleware { diff --git a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php b/src/Middleware/PlainTextDataResponseMiddleware.php similarity index 70% rename from src/Modern/Middleware/PlainTextDataResponseMiddleware.php rename to src/Middleware/PlainTextDataResponseMiddleware.php index c1f32c7..2bf5e2f 100644 --- a/src/Modern/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Middleware/PlainTextDataResponseMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; +use Yiisoft\DataResponse\Formatter\PlainTextFormatter; final class PlainTextDataResponseMiddleware extends AbstractDataResponseMiddleware { diff --git a/src/Modern/Middleware/XmlDataResponseMiddleware.php b/src/Middleware/XmlDataResponseMiddleware.php similarity index 69% rename from src/Modern/Middleware/XmlDataResponseMiddleware.php rename to src/Middleware/XmlDataResponseMiddleware.php index c0021cf..e84c75c 100644 --- a/src/Modern/Middleware/XmlDataResponseMiddleware.php +++ b/src/Middleware/XmlDataResponseMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\Middleware; +namespace Yiisoft\DataResponse\Middleware; -use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; +use Yiisoft\DataResponse\Formatter\XmlFormatter; final class XmlDataResponseMiddleware extends AbstractDataResponseMiddleware { diff --git a/src/Modern/Formatter/XmlDataInterface.php b/src/Modern/Formatter/XmlDataInterface.php deleted file mode 100644 index 7a74a81..0000000 --- a/src/Modern/Formatter/XmlDataInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - The attributes of the XML tag. - */ - public function xmlTagAttributes(): array; - - /** - * Returns an array of data to format as XML. - * - * The data can be any scalar values, instances of `XmlDataInterface`, - * and nested arrays of any level consisting of the above values. - * - * @return array The data to format as XML. - */ - public function xmlData(): array; -} diff --git a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php b/src/ResponseFactory/AbstractFormattedResponseFactory.php similarity index 82% rename from src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php rename to src/ResponseFactory/AbstractFormattedResponseFactory.php index d8f97cf..5c1fb52 100644 --- a/src/Modern/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/ResponseFactory/AbstractFormattedResponseFactory.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; +use Yiisoft\DataResponse\DataStream\DataStream; +use Yiisoft\DataResponse\Formatter\FormatterInterface; use Yiisoft\Http\Status; abstract class AbstractFormattedResponseFactory implements DataResponseFactoryInterface diff --git a/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/ResponseFactory/ContentNegotiatorResponseFactory.php similarity index 97% rename from src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php rename to src/ResponseFactory/ContentNegotiatorResponseFactory.php index d81e05d..c7d3c68 100644 --- a/src/Modern/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; diff --git a/src/Modern/ResponseFactory/DataResponseFactory.php b/src/ResponseFactory/DataResponseFactory.php similarity index 77% rename from src/Modern/ResponseFactory/DataResponseFactory.php rename to src/ResponseFactory/DataResponseFactory.php index 9f1d0cf..d19505f 100644 --- a/src/Modern/ResponseFactory/DataResponseFactory.php +++ b/src/ResponseFactory/DataResponseFactory.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Yiisoft\DataResponse\Modern\DataStream\DataStream; -use Yiisoft\DataResponse\Modern\Formatter\FormatterInterface; -use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\DataStream\DataStream; +use Yiisoft\DataResponse\Formatter\FormatterInterface; +use Yiisoft\DataResponse\Formatter\HtmlFormatter; use Yiisoft\Http\Status; final class DataResponseFactory implements DataResponseFactoryInterface diff --git a/src/Modern/ResponseFactory/DataResponseFactoryInterface.php b/src/ResponseFactory/DataResponseFactoryInterface.php similarity index 84% rename from src/Modern/ResponseFactory/DataResponseFactoryInterface.php rename to src/ResponseFactory/DataResponseFactoryInterface.php index 1953732..142510e 100644 --- a/src/Modern/ResponseFactory/DataResponseFactoryInterface.php +++ b/src/ResponseFactory/DataResponseFactoryInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseInterface; use Yiisoft\Http\Status; diff --git a/src/Modern/ResponseFactory/FormattedResponseFactory.php b/src/ResponseFactory/FormattedResponseFactory.php similarity index 67% rename from src/Modern/ResponseFactory/FormattedResponseFactory.php rename to src/ResponseFactory/FormattedResponseFactory.php index 51646e2..3c75957 100644 --- a/src/Modern/ResponseFactory/FormattedResponseFactory.php +++ b/src/ResponseFactory/FormattedResponseFactory.php @@ -2,6 +2,6 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; final class FormattedResponseFactory extends AbstractFormattedResponseFactory {} diff --git a/src/Modern/ResponseFactory/HtmlResponseFactory.php b/src/ResponseFactory/HtmlResponseFactory.php similarity index 75% rename from src/Modern/ResponseFactory/HtmlResponseFactory.php rename to src/ResponseFactory/HtmlResponseFactory.php index ef382e6..0b9ef40 100644 --- a/src/Modern/ResponseFactory/HtmlResponseFactory.php +++ b/src/ResponseFactory/HtmlResponseFactory.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Yiisoft\DataResponse\Modern\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\Formatter\HtmlFormatter; final class HtmlResponseFactory extends AbstractFormattedResponseFactory { diff --git a/src/Modern/ResponseFactory/JsonResponseFactory.php b/src/ResponseFactory/JsonResponseFactory.php similarity index 75% rename from src/Modern/ResponseFactory/JsonResponseFactory.php rename to src/ResponseFactory/JsonResponseFactory.php index 7e707da..17b999b 100644 --- a/src/Modern/ResponseFactory/JsonResponseFactory.php +++ b/src/ResponseFactory/JsonResponseFactory.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Yiisoft\DataResponse\Modern\Formatter\JsonFormatter; +use Yiisoft\DataResponse\Formatter\JsonFormatter; final class JsonResponseFactory extends AbstractFormattedResponseFactory { diff --git a/src/Modern/ResponseFactory/PlainTextResponseFactory.php b/src/ResponseFactory/PlainTextResponseFactory.php similarity index 75% rename from src/Modern/ResponseFactory/PlainTextResponseFactory.php rename to src/ResponseFactory/PlainTextResponseFactory.php index 7e42218..c2127cc 100644 --- a/src/Modern/ResponseFactory/PlainTextResponseFactory.php +++ b/src/ResponseFactory/PlainTextResponseFactory.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Yiisoft\DataResponse\Modern\Formatter\PlainTextFormatter; +use Yiisoft\DataResponse\Formatter\PlainTextFormatter; final class PlainTextResponseFactory extends AbstractFormattedResponseFactory { diff --git a/src/Modern/ResponseFactory/XmlResponseFactory.php b/src/ResponseFactory/XmlResponseFactory.php similarity index 75% rename from src/Modern/ResponseFactory/XmlResponseFactory.php rename to src/ResponseFactory/XmlResponseFactory.php index 1deaeb3..e6fa704 100644 --- a/src/Modern/ResponseFactory/XmlResponseFactory.php +++ b/src/ResponseFactory/XmlResponseFactory.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Yiisoft\DataResponse\Modern\ResponseFactory; +namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; -use Yiisoft\DataResponse\Modern\Formatter\XmlFormatter; +use Yiisoft\DataResponse\Formatter\XmlFormatter; final class XmlResponseFactory extends AbstractFormattedResponseFactory { From 8180994eff3d2d4ff338141aa3585b0acb7f7532 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 2 Feb 2026 18:01:51 +0300 Subject: [PATCH 32/64] start tests --- composer.json | 2 +- tests/DataStream/DataStreamTest.php | 61 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/DataStream/DataStreamTest.php diff --git a/composer.json b/composer.json index addb593..69fe2b3 100644 --- a/composer.json +++ b/composer.json @@ -76,7 +76,7 @@ } }, "scripts": { - "test": "phpunit --testdox --no-interaction", + "test": "phpunit --testdox", "test-watch": "phpunit-watcher watch" } } diff --git a/tests/DataStream/DataStreamTest.php b/tests/DataStream/DataStreamTest.php new file mode 100644 index 0000000..4f37391 --- /dev/null +++ b/tests/DataStream/DataStreamTest.php @@ -0,0 +1,61 @@ +assertSame('test data', (string) $stream); + $this->assertFalse($stream->hasFormatter()); + } + + public function testFormatter(): void + { + $stream = new DataStream('test', new JsonFormatter()); + + $this->assertTrue($stream->hasFormatter()); + $this->assertSame('"test"', (string) $stream); + } + + public function testFallbackFormatter(): void + { + $stream = new DataStream( + 'test', + fallbackFormatter: new JsonFormatter() + ); + + $this->assertFalse($stream->hasFormatter()); + $this->assertSame('"test"', (string) $stream); + } + + public function testChangeFormatter(): void + { + $formatter = new JsonFormatter(); + $stream = new DataStream('test'); + + $stream->changeFormatter($formatter); + + $this->assertTrue($stream->hasFormatter()); + $this->assertSame('"test"', (string) $stream); + } + + public function testChangeData(): void + { + $stream = new DataStream('hello'); + + $stream->changeData('world'); + + $this->assertSame('world', (string) $stream); + } +} From 2fe60c9bffa8280310359a40f2e3d5f5e8c0675d Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:02:21 +0000 Subject: [PATCH 33/64] Apply PHP CS Fixer and Rector changes (CI) --- tests/DataStream/DataStreamTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/DataStream/DataStreamTest.php b/tests/DataStream/DataStreamTest.php index 4f37391..fe72fd4 100644 --- a/tests/DataStream/DataStreamTest.php +++ b/tests/DataStream/DataStreamTest.php @@ -6,9 +6,7 @@ use PHPUnit\Framework\TestCase; use Yiisoft\DataResponse\DataStream\DataStream; -use Yiisoft\DataResponse\Formatter\FormatterInterface; use Yiisoft\DataResponse\Formatter\JsonFormatter; -use Yiisoft\DataResponse\Formatter\PlainTextFormatter; final class DataStreamTest extends TestCase { @@ -32,7 +30,7 @@ public function testFallbackFormatter(): void { $stream = new DataStream( 'test', - fallbackFormatter: new JsonFormatter() + fallbackFormatter: new JsonFormatter(), ); $this->assertFalse($stream->hasFormatter()); From 6b727573263fdbd508447745309b2e1120195457 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 3 Feb 2026 16:53:04 +0300 Subject: [PATCH 34/64] tests --- composer.json | 3 +- tests/DataStream/DataStreamTest.php | 384 +++++++++++++++++++++++++++- tests/Support/StubFormatter.php | 27 ++ 3 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 tests/Support/StubFormatter.php diff --git a/composer.json b/composer.json index 69fe2b3..a750d4f 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,8 @@ "roave/infection-static-analysis-plugin": "^1.35", "spatie/phpunit-watcher": "^1.24", "vimeo/psalm": "^5.26.1 || ^6.8.0", - "yiisoft/di": "^1.4" + "yiisoft/di": "^1.4", + "yiisoft/test-support": "dev-stream" }, "autoload": { "psr-4": { diff --git a/tests/DataStream/DataStreamTest.php b/tests/DataStream/DataStreamTest.php index 4f37391..37e6e46 100644 --- a/tests/DataStream/DataStreamTest.php +++ b/tests/DataStream/DataStreamTest.php @@ -4,11 +4,13 @@ namespace DataStream; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; +use RuntimeException; use Yiisoft\DataResponse\DataStream\DataStream; -use Yiisoft\DataResponse\Formatter\FormatterInterface; use Yiisoft\DataResponse\Formatter\JsonFormatter; -use Yiisoft\DataResponse\Formatter\PlainTextFormatter; +use Yiisoft\DataResponse\Tests\Support\StubFormatter; +use Yiisoft\Test\Support\HttpMessage\StringStream; final class DataStreamTest extends TestCase { @@ -50,6 +52,18 @@ public function testChangeFormatter(): void $this->assertSame('"test"', (string) $stream); } + public function testCloseStreamOnChangeFormatter(): void + { + $formatted = new StringStream(); + $stream = new DataStream('hello', new StubFormatter($formatted)); + + $stream->getContents(); + $stream->changeFormatter(new JsonFormatter()); + + $this->assertTrue($formatted->isClosed()); + $this->assertFalse($formatted->isDetached()); + } + public function testChangeData(): void { $stream = new DataStream('hello'); @@ -58,4 +72,370 @@ public function testChangeData(): void $this->assertSame('world', (string) $stream); } + + public function testCloseStreamOnChangeData(): void + { + $formatted = new StringStream(); + $stream = new DataStream('hello', new StubFormatter($formatted)); + + $stream->getContents(); + $stream->changeData('world'); + + $this->assertTrue($formatted->isClosed()); + $this->assertFalse($formatted->isDetached()); + } + + public function testClose(): void + { + $formatted = new StringStream(); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $stream->close(); + + $this->assertTrue($formatted->isClosed()); + } + + public function testDetach(): void + { + $formatted = new StringStream(); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $stream->detach(); + + $this->assertTrue($formatted->isDetached()); + } + + public function testGetSize(): void + { + $formatted = new StringStream('hello'); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->getSize(); + + $this->assertSame(5, $result); + } + + public function testTell(): void + { + $formatted = new StringStream('hello', position: 3); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->tell(); + + $this->assertSame(3, $result); + } + + #[TestWith([true, 5])] + #[TestWith([false, 3])] + public function testEof(bool $expected, int $position): void + { + $formatted = new StringStream('hello', position: $position); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->eof(); + + $this->assertSame($expected, $result); + } + + #[TestWith([true])] + #[TestWith([false])] + public function testIsSeekable(bool $expected): void + { + $formatted = new StringStream(seekable: $expected); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->isSeekable(); + + $this->assertSame($expected, $result); + } + + public function testSeekSet(): void + { + $formatted = new StringStream('hello'); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $stream->seek(3); + + $this->assertSame(3, $formatted->getPosition()); + } + + public function testSeekCur(): void + { + $formatted = new StringStream('hello', position: 1); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $stream->seek(3, SEEK_CUR); + + $this->assertSame(4, $formatted->getPosition()); + } + + public function testRewind(): void + { + $formatted = new StringStream('hello', position: 3); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $stream->rewind(); + + $this->assertSame(0, $formatted->getPosition()); + } + + #[TestWith([true])] + #[TestWith([false])] + public function testIsWritable(bool $expected): void + { + $formatted = new StringStream(writable: $expected); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->isWritable(); + + $this->assertSame($expected, $result); + } + + public function testWrite(): void + { + $formatted = new StringStream('hello', position: 5); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->write(', world'); + + $this->assertSame(7, $result); + $this->assertSame('hello, world', (string) $formatted); + } + + #[TestWith([true])] + #[TestWith([false])] + public function testIsReadable(bool $expected): void + { + $formatted = new StringStream(readable: $expected); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->isReadable(); + + $this->assertSame($expected, $result); + } + + public function testRead(): void + { + $formatted = new StringStream('abcdef'); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $this->assertSame('ab', $stream->read(2)); + $this->assertSame('cde', $stream->read(3)); + } + + public function testGetContents(): void + { + $formatted = new StringStream('hello'); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->getContents(); + + $this->assertSame('hello', $result); + } + + public function testGetMetadata(): void + { + $formatted = new StringStream('hello', metadata: ['foo' => 'bar']); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $result = $stream->getMetadata(); + + $this->assertSame(['foo' => 'bar'], $result); + } + + public function testGetMetadataWithKey(): void + { + $formatted = new StringStream('hello', metadata: ['foo' => 'bar']); + $stream = new DataStream('data', new StubFormatter($formatted)); + + $this->assertSame('bar', $stream->getMetadata('foo')); + $this->assertNull($stream->getMetadata('not-exists')); + } + + public function testStringData(): void + { + $stream = new DataStream('test'); + + $this->assertSame('test', (string) $stream); + $this->assertSame('test', $stream->getContents()); + $this->assertSame(4, $stream->getSize()); + $this->assertSame( + [ + 'eof' => true, + 'seekable' => true, + ], + $stream->getMetadata(), + ); + $this->assertTrue($stream->getMetadata('eof')); + $this->assertTrue($stream->getMetadata('seekable')); + $this->assertNull($stream->getMetadata('not-exists')); + $this->assertFalse($stream->isWritable()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isSeekable()); + } + + public function testTellInClosedStreamWithStringData(): void + { + $stream = new DataStream('test'); + $stream->close(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is closed.'); + $stream->tell(); + } + + public function testSeekInClosedStreamWithStringData(): void + { + $stream = new DataStream('test'); + $stream->close(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is closed.'); + $stream->seek(0); + } + + public function testRewindInClosedStreamWithStringData(): void + { + $stream = new DataStream('test'); + $stream->close(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is closed.'); + $stream->seek(0); + } + + public function testReadInClosedStreamWithStringData(): void + { + $stream = new DataStream('test'); + $stream->close(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is closed.'); + $stream->read(5); + } + + public function testDetachWithStringData(): void + { + $stream = new DataStream('test'); + + $result = $stream->detach(); + + $this->assertNull($result); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is closed.'); + $stream->read(5); + } + + public function testTellWithStringData(): void + { + $stream = new DataStream('test'); + + $this->assertSame(0, $stream->tell()); + + $stream->read(2); + $this->assertSame(2, $stream->tell()); + + $stream->read(100); + $this->assertSame(4, $stream->tell()); + } + + public function testSeekSetWithStringData(): void + { + $stream = new DataStream('test'); + + $stream->seek(2); + $result = $stream->read(2); + + $this->assertSame('st', $result); + } + + public function testSeekCurWithStringData(): void + { + $stream = new DataStream('abcdef'); + $stream->read(1); + + $stream->seek(2, SEEK_CUR); + $result = $stream->read(2); + + $this->assertSame('de', $result); + } + + public function testSeekEndWithStringData(): void + { + $stream = new DataStream('abcdefg'); + + $stream->seek(-3, SEEK_END); + $result = $stream->read(3); + + $this->assertSame('efg', $result); + } + + public function testInvalidWhenceWithStringData(): void + { + $stream = new DataStream('test'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid whence value.'); + $stream->seek(1, 9); + } + + #[TestWith([-5])] + #[TestWith([100])] + public function testInvalidOffsetWithStringData(int $value): void + { + $stream = new DataStream('test'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid seek position.'); + $stream->seek($value); + } + + public function testGetMetadataInClosedStreamWithStringData(): void + { + $stream = new DataStream('test'); + $stream->close(); + + $this->assertSame([], $stream->getMetadata()); + $this->assertSame(null, $stream->getMetadata('eof')); + } + + public function testWriteWithStringData(): void + { + $stream = new DataStream('test'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stream is not writable.'); + $stream->write('hello'); + } + + public function testReadNegativeValueWithStringData(): void + { + $stream = new DataStream('test'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Length must be non-negative.'); + $stream->read(-1); + } + + public function testReadOverValueWithStringData(): void + { + $stream = new DataStream('test'); + $stream->getContents(); + + $result = $stream->read(2); + + $this->assertSame('', $result); + } + + public function testRewindWithStringData(): void + { + $stream = new DataStream('test'); + $stream->getContents(); + + $stream->rewind(); + $result = $stream->read(2); + + $this->assertSame('te', $result); + } } diff --git a/tests/Support/StubFormatter.php b/tests/Support/StubFormatter.php new file mode 100644 index 0000000..d0281f5 --- /dev/null +++ b/tests/Support/StubFormatter.php @@ -0,0 +1,27 @@ +formattedData; + } + + public function formatResponse(ResponseInterface $response): ResponseInterface + { + return $response; + } +} From 3880f9cfc5e1c2d71f91b9899a75ddde373a0a05 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 3 Feb 2026 17:41:22 +0300 Subject: [PATCH 35/64] tests --- src/Formatter/XmlFormatter.php | 6 +- tests/Formatter/HtmlFormatterTest.php | 102 +++++++++ tests/Formatter/JsonFormatterTest.php | 110 ++++++++++ tests/Formatter/PlainTextFormatterTest.php | 102 +++++++++ tests/Formatter/XmlFormatterTest.php | 240 +++++++++++++++++++++ 5 files changed, 556 insertions(+), 4 deletions(-) create mode 100644 tests/Formatter/HtmlFormatterTest.php create mode 100644 tests/Formatter/JsonFormatterTest.php create mode 100644 tests/Formatter/PlainTextFormatterTest.php create mode 100644 tests/Formatter/XmlFormatterTest.php diff --git a/src/Formatter/XmlFormatter.php b/src/Formatter/XmlFormatter.php index d1ea5fc..662803b 100644 --- a/src/Formatter/XmlFormatter.php +++ b/src/Formatter/XmlFormatter.php @@ -160,10 +160,8 @@ private function safeCreateDomElement(DOMDocument $dom, $tagName): DOMElement } try { - if (!$element = $dom->createElement($tagName)) { - throw new DOMException(); - } - return $element; + /** @var DOMElement */ + return $dom->createElement($tagName); } catch (DOMException) { return $dom->createElement(self::DEFAULT_ITEM_TAG_NAME); } diff --git a/tests/Formatter/HtmlFormatterTest.php b/tests/Formatter/HtmlFormatterTest.php new file mode 100644 index 0000000..1791d1e --- /dev/null +++ b/tests/Formatter/HtmlFormatterTest.php @@ -0,0 +1,102 @@ + ['', null]; + yield 'string' => ['test', 'test']; + yield 'empty string' => ['', '']; + yield 'integer' => ['42', 42]; + yield 'float' => ['3.14', 3.14]; + yield 'bool true' => ['1', true]; + yield 'bool false' => ['', false]; + yield 'stringable object' => [ + 'stringable content', + new class () implements Stringable { + public function __toString(): string + { + return 'stringable content'; + } + }, + ]; + } + + #[DataProvider('dataFormatData')] + public function testFormatData(string $expected, mixed $data): void + { + $formatter = new HtmlFormatter(); + + $result = $formatter->formatData($data); + + $this->assertSame($expected, $result); + } + + public static function dataFormatDataWithUnsupportedValue(): iterable + { + yield 'array' => [['test']]; + yield 'non-stringable object' => [new stdClass()]; + yield 'resource' => [fopen('php://memory', 'r')]; + } + + #[DataProvider('dataFormatDataWithUnsupportedValue')] + public function testFormatDataWithUnsupportedValue(mixed $data): void + { + $formatter = new HtmlFormatter(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); + $formatter->formatData($data); + } + + public function testFormatResponse(): void + { + $formatter = new HtmlFormatter(); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomContentType(): void + { + $formatter = new HtmlFormatter(contentType: 'text/xhtml'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/xhtml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomEncoding(): void + { + $formatter = new HtmlFormatter(encoding: 'ISO-8859-1'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/html; charset=ISO-8859-1', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseReplacesExistingContentTypeHeader(): void + { + $formatter = new HtmlFormatter(); + + $response = $formatter->formatResponse( + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + ); + + $this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Formatter/JsonFormatterTest.php b/tests/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000..44ecf97 --- /dev/null +++ b/tests/Formatter/JsonFormatterTest.php @@ -0,0 +1,110 @@ + ['null', null]; + yield 'string' => ['"test"', 'test']; + yield 'empty string' => ['""', '']; + yield 'integer' => ['42', 42]; + yield 'float' => ['3.14', 3.14]; + yield 'bool true' => ['true', true]; + yield 'bool false' => ['false', false]; + yield 'array' => ['["a","b"]', ['a', 'b']]; + yield 'associative array' => ['{"key":"value"}', ['key' => 'value']]; + yield 'object' => ['{"property":"value"}', (object) ['property' => 'value']]; + yield 'unicode' => ['"тест"', 'тест']; + yield 'slashes' => ['"/path/to/file"', '/path/to/file']; + } + + #[DataProvider('dataFormatData')] + public function testFormatData(string $expected, mixed $data): void + { + $formatter = new JsonFormatter(); + + $result = $formatter->formatData($data); + + $this->assertSame($expected, $result); + } + + public function testFormatDataWithUnsupportedValue(): void + { + $formatter = new JsonFormatter(); + $resource = fopen('php://memory', 'r'); + + $this->expectException(JsonException::class); + $formatter->formatData($resource); + } + + public function testFormatDataWithCustomOptions(): void + { + $formatter = new JsonFormatter(options: JSON_FORCE_OBJECT); + + $result = $formatter->formatData(['a', 'b']); + + $this->assertSame('{"0":"a","1":"b"}', $result); + } + + public function testFormatDataWithPrettyPrint(): void + { + $formatter = new JsonFormatter(options: JSON_PRETTY_PRINT); + + $result = $formatter->formatData(['key' => 'value']); + + $this->assertSame("{\n \"key\": \"value\"\n}", $result); + } + + public function testFormatResponse(): void + { + $formatter = new JsonFormatter(); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('application/json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomContentType(): void + { + $formatter = new JsonFormatter(contentType: 'application/vnd.api+json'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('application/vnd.api+json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomEncoding(): void + { + $formatter = new JsonFormatter(encoding: 'ISO-8859-1'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('application/json; charset=ISO-8859-1', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseReplacesExistingContentTypeHeader(): void + { + $formatter = new JsonFormatter(); + + $response = $formatter->formatResponse( + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + ); + + $this->assertSame('application/json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Formatter/PlainTextFormatterTest.php b/tests/Formatter/PlainTextFormatterTest.php new file mode 100644 index 0000000..0596b33 --- /dev/null +++ b/tests/Formatter/PlainTextFormatterTest.php @@ -0,0 +1,102 @@ + ['', null]; + yield 'string' => ['test', 'test']; + yield 'empty string' => ['', '']; + yield 'integer' => ['42', 42]; + yield 'float' => ['3.14', 3.14]; + yield 'bool true' => ['1', true]; + yield 'bool false' => ['', false]; + yield 'stringable object' => [ + 'stringable content', + new class () implements Stringable { + public function __toString(): string + { + return 'stringable content'; + } + }, + ]; + } + + #[DataProvider('dataFormatData')] + public function testFormatData(string $expected, mixed $data): void + { + $formatter = new PlainTextFormatter(); + + $result = $formatter->formatData($data); + + $this->assertSame($expected, $result); + } + + public static function dataFormatDataWithUnsupportedValue(): iterable + { + yield 'array' => [['test']]; + yield 'non-stringable object' => [new stdClass()]; + yield 'resource' => [fopen('php://memory', 'r')]; + } + + #[DataProvider('dataFormatDataWithUnsupportedValue')] + public function testFormatDataWithUnsupportedValue(mixed $data): void + { + $formatter = new PlainTextFormatter(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); + $formatter->formatData($data); + } + + public function testFormatResponse(): void + { + $formatter = new PlainTextFormatter(); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomContentType(): void + { + $formatter = new PlainTextFormatter(contentType: 'text/csv'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/csv; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomEncoding(): void + { + $formatter = new PlainTextFormatter(encoding: 'ISO-8859-1'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/plain; charset=ISO-8859-1', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseReplacesExistingContentTypeHeader(): void + { + $formatter = new PlainTextFormatter(); + + $response = $formatter->formatResponse( + (new Response())->withHeader(Header::CONTENT_TYPE, 'application/json') + ); + + $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Formatter/XmlFormatterTest.php b/tests/Formatter/XmlFormatterTest.php new file mode 100644 index 0000000..435ceaf --- /dev/null +++ b/tests/Formatter/XmlFormatterTest.php @@ -0,0 +1,240 @@ + [null]; + yield 'empty array' => [[]]; + yield 'empty string' => ['']; + yield 'zero' => [0]; + yield 'false' => [false]; + } + + #[DataProvider('dataFormatDataWithEmptyData')] + public function testFormatDataWithEmptyData(mixed $data): void + { + $formatter = new XmlFormatter(); + + $result = $formatter->formatData($data); + + $this->assertSame('', $result); + } + + public static function dataFormatData(): iterable + { + yield 'string' => [ + self::xml('test'), + 'test', + ]; + yield 'integer' => [ + self::xml('42'), + 42, + ]; + yield 'float' => [ + self::xml('3.14'), + 3.14, + ]; + yield 'bool true' => [ + self::xml('true'), + true, + ]; + yield 'simple array' => [ + self::xml('ab'), + ['a', 'b'], + ]; + yield 'associative array' => [ + self::xml('value'), + ['key' => 'value'], + ]; + yield 'nested array' => [ + self::xml('value'), + ['parent' => ['child' => 'value']], + ]; + yield 'nested empty array' => [ + self::xml(''), + ['parent' => []], + ]; + yield 'mixed array' => [ + self::xml('test5true'), + ['name' => 'test', 'count' => 5, 'active' => true], + ]; + yield 'bool false in array' => [ + self::xml('false'), + ['enabled' => false], + ]; + yield 'invalid xml tag name' => [ + self::xml('value'), + ['1invalid' => 'value'], + ]; + yield 'object' => [ + self::xml('test42'), + (object) ['name' => 'test', 'value' => 42], + ]; + yield 'traversable' => [ + self::xml('abc'), + new ArrayIterator(['a', 'b', 'c']), + ]; + yield 'traversable in array' => [ + self::xml('ab'), + ['items' => new ArrayIterator(['a', 'b'])], + ]; + yield 'XmlDataInterface' => [ + self::xml('value'), + new class () implements XmlDataInterface { + public function xmlTagName(): string + { + return 'custom'; + } + + public function xmlTagAttributes(): array + { + return ['id' => '1', 'type' => 'test']; + } + + public function xmlData(): array + { + return ['name' => 'value']; + } + }, + ]; + yield 'nested XmlDataInterface' => [ + self::xml('nested'), + [ + 'items' => [ + new class () implements XmlDataInterface { + public function xmlTagName(): string + { + return 'inner'; + } + + public function xmlTagAttributes(): array + { + return []; + } + + public function xmlData(): array + { + return ['value' => 'nested']; + } + }, + ], + ], + ]; + } + + #[DataProvider('dataFormatData')] + public function testFormatData(string $expected, mixed $data): void + { + $formatter = new XmlFormatter(); + + $result = $formatter->formatData($data); + + $this->assertSame($expected, $result); + } + + public function testFormatDataWithCustomRootTag(): void + { + $formatter = new XmlFormatter(rootTag: 'data'); + + $result = $formatter->formatData(['key' => 'value']); + + $this->assertSame( + self::xml('value'), + $result + ); + } + + public function testFormatDataWithEmptyRootTag(): void + { + $formatter = new XmlFormatter(rootTag: ''); + + $result = $formatter->formatData(['key' => 'value']); + + $this->assertSame( + self::xml('value'), + $result + ); + } + + public function testFormatDataWithCustomVersion(): void + { + $formatter = new XmlFormatter(version: '1.1'); + + $result = $formatter->formatData(['key' => 'value']); + + $this->assertSame( + self::xml('value', '1.1'), + $result + ); + } + + public function testFormatDataWithCustomEncoding(): void + { + $formatter = new XmlFormatter(encoding: 'ISO-8859-1'); + + $result = $formatter->formatData(['key' => 'value']); + + $this->assertSame( + self::xml('value', encoding: 'ISO-8859-1'), + $result + ); + } + + public function testFormatResponse(): void + { + $formatter = new XmlFormatter(); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomContentType(): void + { + $formatter = new XmlFormatter(contentType: 'text/xml'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('text/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseWithCustomEncoding(): void + { + $formatter = new XmlFormatter(encoding: 'ISO-8859-1'); + + $response = $formatter->formatResponse(new Response()); + + $this->assertSame('application/xml; charset=ISO-8859-1', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testFormatResponseReplacesExistingContentTypeHeader(): void + { + $formatter = new XmlFormatter(); + + $response = $formatter->formatResponse( + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + ); + + $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + private static function xml(string $content, string $version = '1.0', string $encoding = 'UTF-8'): string + { + return sprintf('%s', $version, $encoding, "\n" . $content . "\n"); + } +} From ea76d7d1fc727ccd1c791da81317ea3b6fae3cc3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 3 Feb 2026 17:52:35 +0300 Subject: [PATCH 36/64] tests --- ...ntNegotiatorDataResponseMiddlewareTest.php | 149 ++++++++++++++++++ .../Middleware/DataResponseMiddlewareTest.php | 64 ++++++++ .../HtmlDataResponseMiddlewareTest.php | 31 ++++ .../JsonDataResponseMiddlewareTest.php | 31 ++++ .../PlainTextDataResponseMiddlewareTest.php | 31 ++++ .../XmlDataResponseMiddlewareTest.php | 38 +++++ tests/Support/StubRequestHandler.php | 22 +++ 7 files changed, 366 insertions(+) create mode 100644 tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php create mode 100644 tests/Middleware/DataResponseMiddlewareTest.php create mode 100644 tests/Middleware/HtmlDataResponseMiddlewareTest.php create mode 100644 tests/Middleware/JsonDataResponseMiddlewareTest.php create mode 100644 tests/Middleware/PlainTextDataResponseMiddlewareTest.php create mode 100644 tests/Middleware/XmlDataResponseMiddlewareTest.php create mode 100644 tests/Support/StubRequestHandler.php diff --git a/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php new file mode 100644 index 0000000..af75f20 --- /dev/null +++ b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php @@ -0,0 +1,149 @@ + 'value']); + $response = (new Response())->withBody($dataStream); + $middleware = new ContentNegotiatorDataResponseMiddleware([ + 'application/json' => new JsonFormatter(), + 'application/xml' => new XmlFormatter(), + ]); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'application/json'), + new StubRequestHandler($response), + ); + + $this->assertSame('{"key":"value"}', (string) $result->getBody()); + $this->assertSame('application/json; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testProcessWithMultipleAcceptHeaders(): void + { + $dataStream = new DataStream(['key' => 'value']); + $response = (new Response())->withBody($dataStream); + $middleware = new ContentNegotiatorDataResponseMiddleware([ + 'application/json' => new JsonFormatter(), + 'application/xml' => new XmlFormatter(), + ]); + + $result = $middleware->process( + (new ServerRequest())->withHeader( + Header::ACCEPT, + 'text/html, application/xml;q=0.9, application/json;q=0.8', + ), + new StubRequestHandler($response), + ); + + $this->assertSame('application/xml; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testProcessWithNoMatchingAcceptHeaderUsesFallback(): void + { + $dataStream = new DataStream('test content'); + $response = (new Response())->withBody($dataStream); + $middleware = new ContentNegotiatorDataResponseMiddleware( + ['application/json' => new JsonFormatter()], + new PlainTextFormatter(), + ); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'text/html'), + new StubRequestHandler($response), + ); + + $this->assertSame('test content', (string) $result->getBody()); + $this->assertSame('text/plain; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testProcessWithNoMatchingAcceptHeaderAndNoFallback(): void + { + $dataStream = new DataStream(['key' => 'value']); + $response = (new Response())->withBody($dataStream); + $middleware = new ContentNegotiatorDataResponseMiddleware([ + 'application/json' => new JsonFormatter(), + ]); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'text/html'), + new StubRequestHandler($response), + ); + + $this->assertSame($response, $result); + } + + public function testProcessWithNonDataStreamBody(): void + { + $stream = (new StreamFactory())->createStream('plain content'); + $response = (new Response())->withBody($stream); + $middleware = new ContentNegotiatorDataResponseMiddleware([ + 'application/json' => new JsonFormatter(), + ]); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'application/json'), + new StubRequestHandler($response), + ); + + $this->assertSame($response, $result); + } + + public function testProcessWithDataStreamWithFormatter(): void + { + $dataStream = new DataStream(['key' => 'value'], new HtmlFormatter()); + $response = (new Response())->withBody($dataStream); + $middleware = new ContentNegotiatorDataResponseMiddleware([ + 'application/json' => new JsonFormatter(), + ]); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'application/json'), + new StubRequestHandler($response), + ); + + $this->assertSame($response, $result); + } + + public function testConstructorWithInvalidContentType(): void + { + $formatters = [new JsonFormatter()]; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid formatter content type. A string is expected, "integer" is received.'); + new ContentNegotiatorDataResponseMiddleware($formatters); + } + + public function testConstructorWithInvalidFormatter(): void + { + $formatters = [ + 'application/json' => new stdClass(), + ]; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Invalid formatter. A "Yiisoft\DataResponse\Formatter\FormatterInterface" instance is expected, "stdClass" is received.' + ); + new ContentNegotiatorDataResponseMiddleware($formatters); + } +} diff --git a/tests/Middleware/DataResponseMiddlewareTest.php b/tests/Middleware/DataResponseMiddlewareTest.php new file mode 100644 index 0000000..8b4f664 --- /dev/null +++ b/tests/Middleware/DataResponseMiddlewareTest.php @@ -0,0 +1,64 @@ + 'value']; + $dataStream = new DataStream($data); + $response = (new Response())->withBody($dataStream); + $middleware = new DataResponseMiddleware(new JsonFormatter()); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame('{"key":"value"}', (string) $result->getBody()); + $this->assertSame('application/json; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testProcessWithDataStreamWithFormatter(): void + { + $data = ['key' => 'value']; + $dataStream = new DataStream($data, new PlainTextFormatter()); + $response = (new Response())->withBody($dataStream); + $middleware = new DataResponseMiddleware(new JsonFormatter()); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame($response, $result); + } + + public function testProcessWithNonDataStreamBody(): void + { + $stream = (new StreamFactory())->createStream('plain content'); + $response = (new Response())->withBody($stream); + $middleware = new DataResponseMiddleware(new JsonFormatter()); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame($response, $result); + } +} diff --git a/tests/Middleware/HtmlDataResponseMiddlewareTest.php b/tests/Middleware/HtmlDataResponseMiddlewareTest.php new file mode 100644 index 0000000..7ba1621 --- /dev/null +++ b/tests/Middleware/HtmlDataResponseMiddlewareTest.php @@ -0,0 +1,31 @@ +withBody($dataStream); + $middleware = new HtmlDataResponseMiddleware(); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame('test content', (string) $result->getBody()); + $this->assertSame('text/html; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Middleware/JsonDataResponseMiddlewareTest.php b/tests/Middleware/JsonDataResponseMiddlewareTest.php new file mode 100644 index 0000000..0815402 --- /dev/null +++ b/tests/Middleware/JsonDataResponseMiddlewareTest.php @@ -0,0 +1,31 @@ + 'value']); + $response = (new Response())->withBody($dataStream); + $middleware = new JsonDataResponseMiddleware(); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame('{"key":"value"}', (string) $result->getBody()); + $this->assertSame('application/json; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Middleware/PlainTextDataResponseMiddlewareTest.php b/tests/Middleware/PlainTextDataResponseMiddlewareTest.php new file mode 100644 index 0000000..243c9c9 --- /dev/null +++ b/tests/Middleware/PlainTextDataResponseMiddlewareTest.php @@ -0,0 +1,31 @@ +withBody($dataStream); + $middleware = new PlainTextDataResponseMiddleware(); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame('test content', (string) $result->getBody()); + $this->assertSame('text/plain; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Middleware/XmlDataResponseMiddlewareTest.php b/tests/Middleware/XmlDataResponseMiddlewareTest.php new file mode 100644 index 0000000..6251c0f --- /dev/null +++ b/tests/Middleware/XmlDataResponseMiddlewareTest.php @@ -0,0 +1,38 @@ + 'value']); + $response = (new Response())->withBody($dataStream); + $middleware = new XmlDataResponseMiddleware(); + + $result = $middleware->process( + new ServerRequest(), + new StubRequestHandler($response), + ); + + $this->assertSame( + << + value + + XML, + (string) $result->getBody(), + ); + $this->assertSame('application/xml; charset=UTF-8', $result->getHeaderLine(Header::CONTENT_TYPE)); + } +} diff --git a/tests/Support/StubRequestHandler.php b/tests/Support/StubRequestHandler.php new file mode 100644 index 0000000..7581d8e --- /dev/null +++ b/tests/Support/StubRequestHandler.php @@ -0,0 +1,22 @@ +response; + } +} From 2ef792aa0280e03cf21b1c71e7bbfcb8f4b3fa24 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 3 Feb 2026 18:13:40 +0300 Subject: [PATCH 37/64] tests --- .../ContentNegotiatorResponseFactoryTest.php | 121 ++++++++++++++++++ .../DataResponseFactoryTest.php | 65 ++++++++++ .../HtmlResponseFactoryTest.php | 36 ++++++ .../JsonResponseFactoryTest.php | 36 ++++++ .../PlainTextResponseFactoryTest.php | 36 ++++++ .../XmlResponseFactoryTest.php | 41 ++++++ 6 files changed, 335 insertions(+) create mode 100644 tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php create mode 100644 tests/ResponseFactory/DataResponseFactoryTest.php create mode 100644 tests/ResponseFactory/HtmlResponseFactoryTest.php create mode 100644 tests/ResponseFactory/JsonResponseFactoryTest.php create mode 100644 tests/ResponseFactory/PlainTextResponseFactoryTest.php create mode 100644 tests/ResponseFactory/XmlResponseFactoryTest.php diff --git a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php new file mode 100644 index 0000000..801589c --- /dev/null +++ b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php @@ -0,0 +1,121 @@ + new JsonResponseFactory($responseFactory, new JsonFormatter()), + 'application/xml' => new XmlResponseFactory($responseFactory, new XmlFormatter()), + ], + new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()), + ); + + $request = (new ServerRequest())->withHeader(Header::ACCEPT, 'application/json'); + $response = $factory->createResponse($request, ['key' => 'value']); + + $this->assertSame('{"key":"value"}', (string) $response->getBody()); + $this->assertSame('application/json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testCreateResponseWithMultipleAcceptHeaders(): void + { + $responseFactory = new ResponseFactory(); + $factory = new ContentNegotiatorResponseFactory( + [ + 'application/json' => new JsonResponseFactory($responseFactory, new JsonFormatter()), + 'application/xml' => new XmlResponseFactory($responseFactory, new XmlFormatter()), + ], + new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()), + ); + + $request = (new ServerRequest())->withHeader( + Header::ACCEPT, + 'text/html, application/xml;q=0.9, application/json;q=0.8', + ); + $response = $factory->createResponse($request, ['key' => 'value']); + + $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testCreateResponseWithNoMatchingAcceptHeaderUsesFallback(): void + { + $responseFactory = new ResponseFactory(); + $factory = new ContentNegotiatorResponseFactory( + [ + 'application/json' => new JsonResponseFactory($responseFactory, new JsonFormatter()), + ], + new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()), + ); + + $request = (new ServerRequest())->withHeader(Header::ACCEPT, 'text/html'); + $response = $factory->createResponse($request, 'test content'); + + $this->assertSame('test content', (string) $response->getBody()); + $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $responseFactory = new ResponseFactory(); + $factory = new ContentNegotiatorResponseFactory( + [ + 'application/json' => new JsonResponseFactory($responseFactory, new JsonFormatter()), + ], + new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()), + ); + + $request = (new ServerRequest())->withHeader(Header::ACCEPT, 'application/json'); + $response = $factory->createResponse($request, ['error' => 'not found'], Status::NOT_FOUND, 'Not Found'); + + $this->assertSame(Status::NOT_FOUND, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); + } + + public function testConstructorWithInvalidContentType(): void + { + $responseFactory = new ResponseFactory(); + $factories = [new JsonResponseFactory($responseFactory, new JsonFormatter())]; + $fallbackFactory = new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid formatter content type. A string is expected, "integer" is received.'); + new ContentNegotiatorResponseFactory($factories, $fallbackFactory); + } + + public function testConstructorWithInvalidFactory(): void + { + $responseFactory = new ResponseFactory(); + $factories = [ + 'application/json' => new stdClass(), + ]; + $fallbackFactory = new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Invalid formatter. A "Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface" instance is expected, "stdClass" is received.' + ); + new ContentNegotiatorResponseFactory($factories, $fallbackFactory); + } +} diff --git a/tests/ResponseFactory/DataResponseFactoryTest.php b/tests/ResponseFactory/DataResponseFactoryTest.php new file mode 100644 index 0000000..ab39a40 --- /dev/null +++ b/tests/ResponseFactory/DataResponseFactoryTest.php @@ -0,0 +1,65 @@ +createResponse(); + + $this->assertInstanceOf(DataStream::class, $response->getBody()); + $this->assertSame(Status::OK, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + } + + public function testCreateResponseWithData(): void + { + $factory = new DataResponseFactory(new ResponseFactory()); + + $response = $factory->createResponse('hello'); + + $body = $response->getBody(); + $this->assertInstanceOf(DataStream::class, $body); + $this->assertSame('hello', (string) $body); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $factory = new DataResponseFactory(new ResponseFactory()); + + $response = $factory->createResponse(null, Status::CREATED); + + $this->assertSame(Status::CREATED, $response->getStatusCode()); + } + + public function testCreateResponseWithReasonPhrase(): void + { + $factory = new DataResponseFactory(new ResponseFactory()); + + $response = $factory->createResponse(null, Status::BAD_REQUEST, 'Custom Reason'); + + $this->assertSame(Status::BAD_REQUEST, $response->getStatusCode()); + $this->assertSame('Custom Reason', $response->getReasonPhrase()); + } + + public function testCreateResponseWithCustomFallbackFormatter(): void + { + $factory = new DataResponseFactory(new ResponseFactory(), new JsonFormatter()); + + $response = $factory->createResponse(['key' => 'value']); + + $this->assertSame('{"key":"value"}', (string) $response->getBody()); + } +} diff --git a/tests/ResponseFactory/HtmlResponseFactoryTest.php b/tests/ResponseFactory/HtmlResponseFactoryTest.php new file mode 100644 index 0000000..cea40ba --- /dev/null +++ b/tests/ResponseFactory/HtmlResponseFactoryTest.php @@ -0,0 +1,36 @@ +createResponse('test content'); + + $this->assertSame('test content', (string) $response->getBody()); + $this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + $this->assertSame(Status::OK, $response->getStatusCode()); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $factory = new HtmlResponseFactory(new ResponseFactory(), new HtmlFormatter()); + + $response = $factory->createResponse('error', Status::NOT_FOUND, 'Not Found'); + + $this->assertSame(Status::NOT_FOUND, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); + } +} diff --git a/tests/ResponseFactory/JsonResponseFactoryTest.php b/tests/ResponseFactory/JsonResponseFactoryTest.php new file mode 100644 index 0000000..9ba08a9 --- /dev/null +++ b/tests/ResponseFactory/JsonResponseFactoryTest.php @@ -0,0 +1,36 @@ +createResponse(['key' => 'value']); + + $this->assertSame('{"key":"value"}', (string) $response->getBody()); + $this->assertSame('application/json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + $this->assertSame(Status::OK, $response->getStatusCode()); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $factory = new JsonResponseFactory(new ResponseFactory(), new JsonFormatter()); + + $response = $factory->createResponse(['error' => 'not found'], Status::NOT_FOUND, 'Not Found'); + + $this->assertSame(Status::NOT_FOUND, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); + } +} diff --git a/tests/ResponseFactory/PlainTextResponseFactoryTest.php b/tests/ResponseFactory/PlainTextResponseFactoryTest.php new file mode 100644 index 0000000..572f68c --- /dev/null +++ b/tests/ResponseFactory/PlainTextResponseFactoryTest.php @@ -0,0 +1,36 @@ +createResponse('test content'); + + $this->assertSame('test content', (string) $response->getBody()); + $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + $this->assertSame(Status::OK, $response->getStatusCode()); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $factory = new PlainTextResponseFactory(new ResponseFactory(), new PlainTextFormatter()); + + $response = $factory->createResponse('error', Status::NOT_FOUND, 'Not Found'); + + $this->assertSame(Status::NOT_FOUND, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); + } +} diff --git a/tests/ResponseFactory/XmlResponseFactoryTest.php b/tests/ResponseFactory/XmlResponseFactoryTest.php new file mode 100644 index 0000000..795546f --- /dev/null +++ b/tests/ResponseFactory/XmlResponseFactoryTest.php @@ -0,0 +1,41 @@ +createResponse(['key' => 'value']); + + $expected = << + value + + XML; + $this->assertSame($expected, (string) $response->getBody()); + $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); + $this->assertSame(Status::OK, $response->getStatusCode()); + } + + public function testCreateResponseWithCustomStatusCode(): void + { + $factory = new XmlResponseFactory(new ResponseFactory(), new XmlFormatter()); + + $response = $factory->createResponse(['error' => 'not found'], Status::NOT_FOUND, 'Not Found'); + + $this->assertSame(Status::NOT_FOUND, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); + } +} From 5d66a9c39948a459edcfd902b59425051c7786cd Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:14:52 +0000 Subject: [PATCH 38/64] Apply PHP CS Fixer and Rector changes (CI) --- tests/DataStream/DataStreamTest.php | 5 ++++- tests/Formatter/HtmlFormatterTest.php | 4 ++-- tests/Formatter/JsonFormatterTest.php | 3 +-- tests/Formatter/PlainTextFormatterTest.php | 4 ++-- tests/Formatter/XmlFormatterTest.php | 14 +++++++------- ...ContentNegotiatorDataResponseMiddlewareTest.php | 2 +- .../ContentNegotiatorResponseFactoryTest.php | 2 +- tests/Support/StubFormatter.php | 3 +-- tests/Support/StubRequestHandler.php | 3 +-- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/DataStream/DataStreamTest.php b/tests/DataStream/DataStreamTest.php index 37e6e46..507afb2 100644 --- a/tests/DataStream/DataStreamTest.php +++ b/tests/DataStream/DataStreamTest.php @@ -12,6 +12,9 @@ use Yiisoft\DataResponse\Tests\Support\StubFormatter; use Yiisoft\Test\Support\HttpMessage\StringStream; +use const SEEK_CUR; +use const SEEK_END; + final class DataStreamTest extends TestCase { public function testBase(): void @@ -34,7 +37,7 @@ public function testFallbackFormatter(): void { $stream = new DataStream( 'test', - fallbackFormatter: new JsonFormatter() + fallbackFormatter: new JsonFormatter(), ); $this->assertFalse($stream->hasFormatter()); diff --git a/tests/Formatter/HtmlFormatterTest.php b/tests/Formatter/HtmlFormatterTest.php index 1791d1e..5521ee1 100644 --- a/tests/Formatter/HtmlFormatterTest.php +++ b/tests/Formatter/HtmlFormatterTest.php @@ -26,7 +26,7 @@ public static function dataFormatData(): iterable yield 'bool false' => ['', false]; yield 'stringable object' => [ 'stringable content', - new class () implements Stringable { + new class implements Stringable { public function __toString(): string { return 'stringable content'; @@ -94,7 +94,7 @@ public function testFormatResponseReplacesExistingContentTypeHeader(): void $formatter = new HtmlFormatter(); $response = $formatter->formatResponse( - (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain'), ); $this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); diff --git a/tests/Formatter/JsonFormatterTest.php b/tests/Formatter/JsonFormatterTest.php index 44ecf97..83b36c2 100644 --- a/tests/Formatter/JsonFormatterTest.php +++ b/tests/Formatter/JsonFormatterTest.php @@ -8,7 +8,6 @@ use JsonException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use stdClass; use Yiisoft\DataResponse\Formatter\JsonFormatter; use Yiisoft\Http\Header; @@ -102,7 +101,7 @@ public function testFormatResponseReplacesExistingContentTypeHeader(): void $formatter = new JsonFormatter(); $response = $formatter->formatResponse( - (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain'), ); $this->assertSame('application/json; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); diff --git a/tests/Formatter/PlainTextFormatterTest.php b/tests/Formatter/PlainTextFormatterTest.php index 0596b33..8a2d010 100644 --- a/tests/Formatter/PlainTextFormatterTest.php +++ b/tests/Formatter/PlainTextFormatterTest.php @@ -26,7 +26,7 @@ public static function dataFormatData(): iterable yield 'bool false' => ['', false]; yield 'stringable object' => [ 'stringable content', - new class () implements Stringable { + new class implements Stringable { public function __toString(): string { return 'stringable content'; @@ -94,7 +94,7 @@ public function testFormatResponseReplacesExistingContentTypeHeader(): void $formatter = new PlainTextFormatter(); $response = $formatter->formatResponse( - (new Response())->withHeader(Header::CONTENT_TYPE, 'application/json') + (new Response())->withHeader(Header::CONTENT_TYPE, 'application/json'), ); $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); diff --git a/tests/Formatter/XmlFormatterTest.php b/tests/Formatter/XmlFormatterTest.php index 435ceaf..34812bf 100644 --- a/tests/Formatter/XmlFormatterTest.php +++ b/tests/Formatter/XmlFormatterTest.php @@ -95,7 +95,7 @@ public static function dataFormatData(): iterable ]; yield 'XmlDataInterface' => [ self::xml('value'), - new class () implements XmlDataInterface { + new class implements XmlDataInterface { public function xmlTagName(): string { return 'custom'; @@ -116,7 +116,7 @@ public function xmlData(): array self::xml('nested'), [ 'items' => [ - new class () implements XmlDataInterface { + new class implements XmlDataInterface { public function xmlTagName(): string { return 'inner'; @@ -155,7 +155,7 @@ public function testFormatDataWithCustomRootTag(): void $this->assertSame( self::xml('value'), - $result + $result, ); } @@ -167,7 +167,7 @@ public function testFormatDataWithEmptyRootTag(): void $this->assertSame( self::xml('value'), - $result + $result, ); } @@ -179,7 +179,7 @@ public function testFormatDataWithCustomVersion(): void $this->assertSame( self::xml('value', '1.1'), - $result + $result, ); } @@ -191,7 +191,7 @@ public function testFormatDataWithCustomEncoding(): void $this->assertSame( self::xml('value', encoding: 'ISO-8859-1'), - $result + $result, ); } @@ -227,7 +227,7 @@ public function testFormatResponseReplacesExistingContentTypeHeader(): void $formatter = new XmlFormatter(); $response = $formatter->formatResponse( - (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain') + (new Response())->withHeader(Header::CONTENT_TYPE, 'text/plain'), ); $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine(Header::CONTENT_TYPE)); diff --git a/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php index af75f20..7af9880 100644 --- a/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php +++ b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php @@ -142,7 +142,7 @@ public function testConstructorWithInvalidFormatter(): void $this->expectException(RuntimeException::class); $this->expectExceptionMessage( - 'Invalid formatter. A "Yiisoft\DataResponse\Formatter\FormatterInterface" instance is expected, "stdClass" is received.' + 'Invalid formatter. A "Yiisoft\DataResponse\Formatter\FormatterInterface" instance is expected, "stdClass" is received.', ); new ContentNegotiatorDataResponseMiddleware($formatters); } diff --git a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php index 801589c..302917a 100644 --- a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php +++ b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php @@ -114,7 +114,7 @@ public function testConstructorWithInvalidFactory(): void $this->expectException(RuntimeException::class); $this->expectExceptionMessage( - 'Invalid formatter. A "Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface" instance is expected, "stdClass" is received.' + 'Invalid formatter. A "Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface" instance is expected, "stdClass" is received.', ); new ContentNegotiatorResponseFactory($factories, $fallbackFactory); } diff --git a/tests/Support/StubFormatter.php b/tests/Support/StubFormatter.php index d0281f5..56ef19a 100644 --- a/tests/Support/StubFormatter.php +++ b/tests/Support/StubFormatter.php @@ -12,8 +12,7 @@ final class StubFormatter implements FormatterInterface { public function __construct( private readonly StreamInterface|string $formattedData = '', - ) { - } + ) {} public function formatData(mixed $data): StreamInterface { diff --git a/tests/Support/StubRequestHandler.php b/tests/Support/StubRequestHandler.php index 7581d8e..1fa800a 100644 --- a/tests/Support/StubRequestHandler.php +++ b/tests/Support/StubRequestHandler.php @@ -12,8 +12,7 @@ final class StubRequestHandler implements RequestHandlerInterface { public function __construct( private readonly ResponseInterface $response, - ) { - } + ) {} public function handle(ServerRequestInterface $request): ResponseInterface { From 0369bf124683c200c6f88837a8c3a7cf67cc981b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 3 Feb 2026 18:27:17 +0300 Subject: [PATCH 39/64] deprecations --- src/DataResponse.php | 3 +++ src/DataResponseFactory.php | 2 ++ src/DataResponseFactoryInterface.php | 2 ++ src/DataResponseFormatterInterface.php | 2 ++ src/Formatter/HtmlDataResponseFormatter.php | 3 +++ src/Formatter/JsonDataResponseFormatter.php | 2 ++ src/Formatter/PlainTextDataResponseFormatter.php | 2 ++ src/Formatter/XmlDataResponseFormatter.php | 2 ++ src/Middleware/ContentNegotiator.php | 2 ++ src/Middleware/FormatDataResponse.php | 2 ++ src/Middleware/FormatDataResponseAsHtml.php | 2 ++ src/Middleware/FormatDataResponseAsJson.php | 2 ++ src/Middleware/FormatDataResponseAsPlainText.php | 2 ++ src/Middleware/FormatDataResponseAsXml.php | 2 ++ src/ResponseContentTrait.php | 2 ++ 15 files changed, 32 insertions(+) diff --git a/src/DataResponse.php b/src/DataResponse.php index e9b7e3e..f714eba 100644 --- a/src/DataResponse.php +++ b/src/DataResponse.php @@ -9,6 +9,7 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use RuntimeException; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; use function ftruncate; @@ -24,6 +25,8 @@ * * For example, `['name' => 'Dmitriy']` to be formatted as JSON using * {@see JsonDataResponseFormatter} when {@see DataResponse::getBody()} is called. + * + * @deprecated Use {@see DataStream} instead. */ final class DataResponse implements ResponseInterface { diff --git a/src/DataResponseFactory.php b/src/DataResponseFactory.php index fb9268a..f865a67 100644 --- a/src/DataResponseFactory.php +++ b/src/DataResponseFactory.php @@ -10,6 +10,8 @@ /** * DataResponseFactory creates an instance of the data response {@see DataResponse}. + * + * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactory} instead. */ final class DataResponseFactory implements DataResponseFactoryInterface { diff --git a/src/DataResponseFactoryInterface.php b/src/DataResponseFactoryInterface.php index 9cefd3b..82d4176 100644 --- a/src/DataResponseFactoryInterface.php +++ b/src/DataResponseFactoryInterface.php @@ -8,6 +8,8 @@ /** * `DataResponseFactoryInterface` is the interface that should be implemented by data response factory classes. + * + * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface} instead. */ interface DataResponseFactoryInterface { diff --git a/src/DataResponseFormatterInterface.php b/src/DataResponseFormatterInterface.php index 127ab69..04194f3 100644 --- a/src/DataResponseFormatterInterface.php +++ b/src/DataResponseFormatterInterface.php @@ -8,6 +8,8 @@ /** * DataResponseFormatterInterface is the interface that should be implemented by data response formatters. + * + * @deprecated Use {@see \Yiisoft\DataResponse\Formatter\FormatterInterface} instead. */ interface DataResponseFormatterInterface { diff --git a/src/Formatter/HtmlDataResponseFormatter.php b/src/Formatter/HtmlDataResponseFormatter.php index 2478494..1520d08 100644 --- a/src/Formatter/HtmlDataResponseFormatter.php +++ b/src/Formatter/HtmlDataResponseFormatter.php @@ -9,6 +9,7 @@ use Stringable; use Yiisoft\DataResponse\DataResponse; use Yiisoft\DataResponse\DataResponseFormatterInterface; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\ResponseContentTrait; use function is_scalar; @@ -16,6 +17,8 @@ /** * `HtmlDataResponseFormatter` formats the response data as HTML. + * + * @deprecated Use {@see DataStream} with {@see HtmlFormatter} instead. */ final class HtmlDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/JsonDataResponseFormatter.php b/src/Formatter/JsonDataResponseFormatter.php index 4268ec4..3efa8b5 100644 --- a/src/Formatter/JsonDataResponseFormatter.php +++ b/src/Formatter/JsonDataResponseFormatter.php @@ -16,6 +16,8 @@ /** * `JsonDataResponseFormatter` formats the response data as JSON. + * + * @deprecated Use {@see DataStream} with {@see JsonFormatter} instead. */ final class JsonDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/PlainTextDataResponseFormatter.php b/src/Formatter/PlainTextDataResponseFormatter.php index d2cac40..f776626 100644 --- a/src/Formatter/PlainTextDataResponseFormatter.php +++ b/src/Formatter/PlainTextDataResponseFormatter.php @@ -16,6 +16,8 @@ /** * `PlainTextDataResponseFormatter` formats the response data as plain text. + * + * @deprecated Use {@see DataStream} with {@see PlainTextFormatter} instead. */ final class PlainTextDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/XmlDataResponseFormatter.php b/src/Formatter/XmlDataResponseFormatter.php index 3b68baf..576b2bf 100644 --- a/src/Formatter/XmlDataResponseFormatter.php +++ b/src/Formatter/XmlDataResponseFormatter.php @@ -22,6 +22,8 @@ /** * `XmlDataResponseFormatter` formats the response data as XML. + * + * @deprecated Use {@see DataStream} with {@see XmlFormatter} instead. */ final class XmlDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Middleware/ContentNegotiator.php b/src/Middleware/ContentNegotiator.php index c03973e..674418b 100644 --- a/src/Middleware/ContentNegotiator.php +++ b/src/Middleware/ContentNegotiator.php @@ -21,6 +21,8 @@ * ContentNegotiator supports response format negotiation. * * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation + * + * @deprecated Use {@see ContentNegotiatorDataResponseMiddleware} instead. */ final class ContentNegotiator implements MiddlewareInterface { diff --git a/src/Middleware/FormatDataResponse.php b/src/Middleware/FormatDataResponse.php index d6ed13e..301ee94 100644 --- a/src/Middleware/FormatDataResponse.php +++ b/src/Middleware/FormatDataResponse.php @@ -14,6 +14,8 @@ /** * FormatDataResponse adds a formatter {@see DataResponseFormatterInterface} instance to the * instance of the data response {@see DataResponse}, if the formatter was not added earlier. + * + * @deprecated Use {@see DataResponseMiddleware} instead. */ class FormatDataResponse implements MiddlewareInterface { diff --git a/src/Middleware/FormatDataResponseAsHtml.php b/src/Middleware/FormatDataResponseAsHtml.php index 29b9f3d..d11845c 100644 --- a/src/Middleware/FormatDataResponseAsHtml.php +++ b/src/Middleware/FormatDataResponseAsHtml.php @@ -9,6 +9,8 @@ /** * FormatDataResponseAsHtml adds an HTML formatter {@see HtmlDataResponseFormatter} instance to the * instance of the data response {@see DataResponse}, if the formatter was not added earlier. + * + * @deprecated Use {@see HtmlDataResponseMiddleware} instead. */ final class FormatDataResponseAsHtml extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsJson.php b/src/Middleware/FormatDataResponseAsJson.php index a518302..9b20a5a 100644 --- a/src/Middleware/FormatDataResponseAsJson.php +++ b/src/Middleware/FormatDataResponseAsJson.php @@ -9,6 +9,8 @@ /** * FormatDataResponseAsJson adds a JSON formatter {@see JsonDataResponseFormatter} instance to the * instance of the data response {@see DataResponse}, if the formatter was not added earlier. + * + * @deprecated Use {@see JsonDataResponseMiddleware} instead. */ final class FormatDataResponseAsJson extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsPlainText.php b/src/Middleware/FormatDataResponseAsPlainText.php index 576d508..8125f3a 100644 --- a/src/Middleware/FormatDataResponseAsPlainText.php +++ b/src/Middleware/FormatDataResponseAsPlainText.php @@ -9,6 +9,8 @@ /** * `FormatDataResponseAsPlainText` adds a plain text formatter {@see PlainTextDataResponseFormatter} instance to the * instance of the data response {@see DataResponse}, if the formatter was not added earlier. + * + * @deprecated Use {@see PlainTextDataResponseMiddleware} instead. */ final class FormatDataResponseAsPlainText extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsXml.php b/src/Middleware/FormatDataResponseAsXml.php index 6e504a3..d4b84b2 100644 --- a/src/Middleware/FormatDataResponseAsXml.php +++ b/src/Middleware/FormatDataResponseAsXml.php @@ -9,6 +9,8 @@ /** * FormatDataResponseAsXml adds an XML formatter {@see XmlDataResponseFormatter} instance to the * instance of the data response {@see DataResponse}, if the formatter was not added earlier. + * + * @deprecated Use {@see XmlDataResponseMiddleware} instead. */ final class FormatDataResponseAsXml extends FormatDataResponse { diff --git a/src/ResponseContentTrait.php b/src/ResponseContentTrait.php index ce73b7d..e1fec6b 100644 --- a/src/ResponseContentTrait.php +++ b/src/ResponseContentTrait.php @@ -9,6 +9,8 @@ /** * ResponseContentTrait provides methods for manipulating the response content. + * + * @deprecated */ trait ResponseContentTrait { From dfab4fddda403de75f66f18c84dfc348d7d38cb3 Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:28:00 +0000 Subject: [PATCH 40/64] Apply PHP CS Fixer and Rector changes (CI) --- src/DataResponseFactory.php | 2 +- src/DataResponseFactoryInterface.php | 2 +- src/DataResponseFormatterInterface.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DataResponseFactory.php b/src/DataResponseFactory.php index f865a67..579ae9c 100644 --- a/src/DataResponseFactory.php +++ b/src/DataResponseFactory.php @@ -11,7 +11,7 @@ /** * DataResponseFactory creates an instance of the data response {@see DataResponse}. * - * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactory} instead. + * @deprecated Use {@see ResponseFactory\DataResponseFactory} instead. */ final class DataResponseFactory implements DataResponseFactoryInterface { diff --git a/src/DataResponseFactoryInterface.php b/src/DataResponseFactoryInterface.php index 82d4176..3889d00 100644 --- a/src/DataResponseFactoryInterface.php +++ b/src/DataResponseFactoryInterface.php @@ -9,7 +9,7 @@ /** * `DataResponseFactoryInterface` is the interface that should be implemented by data response factory classes. * - * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface} instead. + * @deprecated Use {@see ResponseFactory\DataResponseFactoryInterface} instead. */ interface DataResponseFactoryInterface { diff --git a/src/DataResponseFormatterInterface.php b/src/DataResponseFormatterInterface.php index 04194f3..a72644e 100644 --- a/src/DataResponseFormatterInterface.php +++ b/src/DataResponseFormatterInterface.php @@ -5,11 +5,12 @@ namespace Yiisoft\DataResponse; use Psr\Http\Message\ResponseInterface; +use Yiisoft\DataResponse\Formatter\FormatterInterface; /** * DataResponseFormatterInterface is the interface that should be implemented by data response formatters. * - * @deprecated Use {@see \Yiisoft\DataResponse\Formatter\FormatterInterface} instead. + * @deprecated Use {@see FormatterInterface} instead. */ interface DataResponseFormatterInterface { From 8f5b16737502f60fcc95dce7fea2d940d9caeb0c Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 11:31:16 +0300 Subject: [PATCH 41/64] Add `DataEncodingException` --- src/Formatter/DataEncodingException.php | 12 ++++++++++++ src/Formatter/FormatterInterface.php | 3 +++ src/Formatter/HtmlFormatter.php | 3 +-- src/Formatter/JsonFormatter.php | 9 +++++---- src/Formatter/PlainTextFormatter.php | 3 +-- tests/Formatter/HtmlFormatterTest.php | 4 ++-- tests/Formatter/JsonFormatterTest.php | 4 ++-- tests/Formatter/PlainTextFormatterTest.php | 4 ++-- 8 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 src/Formatter/DataEncodingException.php diff --git a/src/Formatter/DataEncodingException.php b/src/Formatter/DataEncodingException.php new file mode 100644 index 0000000..ec443fd --- /dev/null +++ b/src/Formatter/DataEncodingException.php @@ -0,0 +1,12 @@ +options); + try { + return Json::encode($data, $this->options); + } catch (JsonException $e) { + throw new DataEncodingException($e->getMessage(), previous: $e); + } } public function formatResponse(ResponseInterface $response): ResponseInterface diff --git a/src/Formatter/PlainTextFormatter.php b/src/Formatter/PlainTextFormatter.php index faadcdf..bb5c5bc 100644 --- a/src/Formatter/PlainTextFormatter.php +++ b/src/Formatter/PlainTextFormatter.php @@ -5,7 +5,6 @@ namespace Yiisoft\DataResponse\Formatter; use Psr\Http\Message\ResponseInterface; -use RuntimeException; use Stringable; use Yiisoft\Http\Header; @@ -26,7 +25,7 @@ public function formatData(mixed $data): string } if (!is_scalar($data) && !$data instanceof Stringable) { - throw new RuntimeException( + throw new DataEncodingException( sprintf( 'Data must be either a scalar value, null, or a stringable object. %s given.', get_debug_type($data), diff --git a/tests/Formatter/HtmlFormatterTest.php b/tests/Formatter/HtmlFormatterTest.php index 5521ee1..f842c94 100644 --- a/tests/Formatter/HtmlFormatterTest.php +++ b/tests/Formatter/HtmlFormatterTest.php @@ -7,9 +7,9 @@ use HttpSoft\Message\Response; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use RuntimeException; use stdClass; use Stringable; +use Yiisoft\DataResponse\Formatter\DataEncodingException; use Yiisoft\DataResponse\Formatter\HtmlFormatter; use Yiisoft\Http\Header; @@ -57,7 +57,7 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void { $formatter = new HtmlFormatter(); - $this->expectException(RuntimeException::class); + $this->expectException(DataEncodingException::class); $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); $formatter->formatData($data); } diff --git a/tests/Formatter/JsonFormatterTest.php b/tests/Formatter/JsonFormatterTest.php index 83b36c2..488bcb1 100644 --- a/tests/Formatter/JsonFormatterTest.php +++ b/tests/Formatter/JsonFormatterTest.php @@ -5,9 +5,9 @@ namespace Yiisoft\DataResponse\Tests\Formatter; use HttpSoft\Message\Response; -use JsonException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Yiisoft\DataResponse\Formatter\DataEncodingException; use Yiisoft\DataResponse\Formatter\JsonFormatter; use Yiisoft\Http\Header; @@ -47,7 +47,7 @@ public function testFormatDataWithUnsupportedValue(): void $formatter = new JsonFormatter(); $resource = fopen('php://memory', 'r'); - $this->expectException(JsonException::class); + $this->expectException(DataEncodingException::class); $formatter->formatData($resource); } diff --git a/tests/Formatter/PlainTextFormatterTest.php b/tests/Formatter/PlainTextFormatterTest.php index 8a2d010..c3fa7d0 100644 --- a/tests/Formatter/PlainTextFormatterTest.php +++ b/tests/Formatter/PlainTextFormatterTest.php @@ -7,9 +7,9 @@ use HttpSoft\Message\Response; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use RuntimeException; use stdClass; use Stringable; +use Yiisoft\DataResponse\Formatter\DataEncodingException; use Yiisoft\DataResponse\Formatter\PlainTextFormatter; use Yiisoft\Http\Header; @@ -57,7 +57,7 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void { $formatter = new PlainTextFormatter(); - $this->expectException(RuntimeException::class); + $this->expectException(DataEncodingException::class); $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); $formatter->formatData($data); } From a6fd488341391d7913fa3750ce62d738a05d5ec1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 11:38:55 +0300 Subject: [PATCH 42/64] fix psalm --- src/DataResponse.php | 2 ++ src/DataResponseFactory.php | 2 ++ src/DataResponseFactoryInterface.php | 2 ++ src/Formatter/HtmlDataResponseFormatter.php | 2 ++ src/Formatter/JsonDataResponseFormatter.php | 2 ++ src/Formatter/PlainTextDataResponseFormatter.php | 2 ++ src/Formatter/XmlDataResponseFormatter.php | 2 ++ src/Middleware/ContentNegotiator.php | 2 ++ src/Middleware/FormatDataResponse.php | 2 ++ src/Middleware/FormatDataResponseAsHtml.php | 2 ++ src/Middleware/FormatDataResponseAsJson.php | 2 ++ src/Middleware/FormatDataResponseAsPlainText.php | 2 ++ src/Middleware/FormatDataResponseAsXml.php | 2 ++ src/ResponseContentTrait.php | 4 ++++ 14 files changed, 30 insertions(+) diff --git a/src/DataResponse.php b/src/DataResponse.php index f714eba..6d75d69 100644 --- a/src/DataResponse.php +++ b/src/DataResponse.php @@ -27,6 +27,8 @@ * {@see JsonDataResponseFormatter} when {@see DataResponse::getBody()} is called. * * @deprecated Use {@see DataStream} instead. + * + * @psalm-suppress DeprecatedInterface */ final class DataResponse implements ResponseInterface { diff --git a/src/DataResponseFactory.php b/src/DataResponseFactory.php index f865a67..ea5d724 100644 --- a/src/DataResponseFactory.php +++ b/src/DataResponseFactory.php @@ -12,6 +12,8 @@ * DataResponseFactory creates an instance of the data response {@see DataResponse}. * * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactory} instead. + * + * @psalm-suppress DeprecatedInterface, DeprecatedClass */ final class DataResponseFactory implements DataResponseFactoryInterface { diff --git a/src/DataResponseFactoryInterface.php b/src/DataResponseFactoryInterface.php index 82d4176..9b4a80f 100644 --- a/src/DataResponseFactoryInterface.php +++ b/src/DataResponseFactoryInterface.php @@ -10,6 +10,8 @@ * `DataResponseFactoryInterface` is the interface that should be implemented by data response factory classes. * * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface} instead. + * + * @psalm-suppress DeprecatedClass */ interface DataResponseFactoryInterface { diff --git a/src/Formatter/HtmlDataResponseFormatter.php b/src/Formatter/HtmlDataResponseFormatter.php index 1520d08..f1e0d3a 100644 --- a/src/Formatter/HtmlDataResponseFormatter.php +++ b/src/Formatter/HtmlDataResponseFormatter.php @@ -19,6 +19,8 @@ * `HtmlDataResponseFormatter` formats the response data as HTML. * * @deprecated Use {@see DataStream} with {@see HtmlFormatter} instead. + * + * @psalm-suppress DeprecatedTrait, DeprecatedInterface */ final class HtmlDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/JsonDataResponseFormatter.php b/src/Formatter/JsonDataResponseFormatter.php index 3efa8b5..fc37106 100644 --- a/src/Formatter/JsonDataResponseFormatter.php +++ b/src/Formatter/JsonDataResponseFormatter.php @@ -18,6 +18,8 @@ * `JsonDataResponseFormatter` formats the response data as JSON. * * @deprecated Use {@see DataStream} with {@see JsonFormatter} instead. + * + * @psalm-suppress DeprecatedTrait, DeprecatedInterface */ final class JsonDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/PlainTextDataResponseFormatter.php b/src/Formatter/PlainTextDataResponseFormatter.php index f776626..9e5e617 100644 --- a/src/Formatter/PlainTextDataResponseFormatter.php +++ b/src/Formatter/PlainTextDataResponseFormatter.php @@ -18,6 +18,8 @@ * `PlainTextDataResponseFormatter` formats the response data as plain text. * * @deprecated Use {@see DataStream} with {@see PlainTextFormatter} instead. + * + * @psalm-suppress DeprecatedTrait, DeprecatedInterface */ final class PlainTextDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Formatter/XmlDataResponseFormatter.php b/src/Formatter/XmlDataResponseFormatter.php index 576b2bf..d7a1c33 100644 --- a/src/Formatter/XmlDataResponseFormatter.php +++ b/src/Formatter/XmlDataResponseFormatter.php @@ -24,6 +24,8 @@ * `XmlDataResponseFormatter` formats the response data as XML. * * @deprecated Use {@see DataStream} with {@see XmlFormatter} instead. + * + * @psalm-suppress DeprecatedTrait, DeprecatedInterface */ final class XmlDataResponseFormatter implements DataResponseFormatterInterface { diff --git a/src/Middleware/ContentNegotiator.php b/src/Middleware/ContentNegotiator.php index 674418b..ea1e187 100644 --- a/src/Middleware/ContentNegotiator.php +++ b/src/Middleware/ContentNegotiator.php @@ -23,6 +23,8 @@ * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation * * @deprecated Use {@see ContentNegotiatorDataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedInterface */ final class ContentNegotiator implements MiddlewareInterface { diff --git a/src/Middleware/FormatDataResponse.php b/src/Middleware/FormatDataResponse.php index 301ee94..c27d561 100644 --- a/src/Middleware/FormatDataResponse.php +++ b/src/Middleware/FormatDataResponse.php @@ -16,6 +16,8 @@ * instance of the data response {@see DataResponse}, if the formatter was not added earlier. * * @deprecated Use {@see DataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedInterface */ class FormatDataResponse implements MiddlewareInterface { diff --git a/src/Middleware/FormatDataResponseAsHtml.php b/src/Middleware/FormatDataResponseAsHtml.php index d11845c..8f8da70 100644 --- a/src/Middleware/FormatDataResponseAsHtml.php +++ b/src/Middleware/FormatDataResponseAsHtml.php @@ -11,6 +11,8 @@ * instance of the data response {@see DataResponse}, if the formatter was not added earlier. * * @deprecated Use {@see HtmlDataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedClass */ final class FormatDataResponseAsHtml extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsJson.php b/src/Middleware/FormatDataResponseAsJson.php index 9b20a5a..792abb7 100644 --- a/src/Middleware/FormatDataResponseAsJson.php +++ b/src/Middleware/FormatDataResponseAsJson.php @@ -11,6 +11,8 @@ * instance of the data response {@see DataResponse}, if the formatter was not added earlier. * * @deprecated Use {@see JsonDataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedClass */ final class FormatDataResponseAsJson extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsPlainText.php b/src/Middleware/FormatDataResponseAsPlainText.php index 8125f3a..57713e3 100644 --- a/src/Middleware/FormatDataResponseAsPlainText.php +++ b/src/Middleware/FormatDataResponseAsPlainText.php @@ -11,6 +11,8 @@ * instance of the data response {@see DataResponse}, if the formatter was not added earlier. * * @deprecated Use {@see PlainTextDataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedClass */ final class FormatDataResponseAsPlainText extends FormatDataResponse { diff --git a/src/Middleware/FormatDataResponseAsXml.php b/src/Middleware/FormatDataResponseAsXml.php index d4b84b2..271ce0a 100644 --- a/src/Middleware/FormatDataResponseAsXml.php +++ b/src/Middleware/FormatDataResponseAsXml.php @@ -11,6 +11,8 @@ * instance of the data response {@see DataResponse}, if the formatter was not added earlier. * * @deprecated Use {@see XmlDataResponseMiddleware} instead. + * + * @psalm-suppress DeprecatedClass */ final class FormatDataResponseAsXml extends FormatDataResponse { diff --git a/src/ResponseContentTrait.php b/src/ResponseContentTrait.php index e1fec6b..964ed36 100644 --- a/src/ResponseContentTrait.php +++ b/src/ResponseContentTrait.php @@ -18,6 +18,8 @@ trait ResponseContentTrait * Returns a new instance with the specified content type. * * @param string $contentType The content type. For example, "text/html". + * + * @psalm-suppress DeprecatedClass */ public function withContentType(string $contentType): self { @@ -30,6 +32,8 @@ public function withContentType(string $contentType): self * Returns a new instance with the specified encoding. * * @param string $encoding The encoding. For example, "UTF-8". + * + * @psalm-suppress DeprecatedClass */ public function withEncoding(string $encoding): self { From c56c1e683924d29163444d520b2dacc69775ae13 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 11:42:59 +0300 Subject: [PATCH 43/64] fix --- src/DataResponseFormatterInterface.php | 2 ++ src/Formatter/DataEncodingException.php | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DataResponseFormatterInterface.php b/src/DataResponseFormatterInterface.php index a72644e..47d73fe 100644 --- a/src/DataResponseFormatterInterface.php +++ b/src/DataResponseFormatterInterface.php @@ -11,6 +11,8 @@ * DataResponseFormatterInterface is the interface that should be implemented by data response formatters. * * @deprecated Use {@see FormatterInterface} instead. + * + * @psalm-suppress DeprecatedClass */ interface DataResponseFormatterInterface { diff --git a/src/Formatter/DataEncodingException.php b/src/Formatter/DataEncodingException.php index ec443fd..2f071a4 100644 --- a/src/Formatter/DataEncodingException.php +++ b/src/Formatter/DataEncodingException.php @@ -5,7 +5,6 @@ namespace Yiisoft\DataResponse\Formatter; use LogicException; -use Throwable; final class DataEncodingException extends LogicException { From 1bf531f1761e9d7c7cb5e5d631a83cccaadb9834 Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:43:30 +0000 Subject: [PATCH 44/64] Apply PHP CS Fixer and Rector changes (CI) --- src/DataResponseFactory.php | 2 +- src/DataResponseFactoryInterface.php | 2 +- src/Formatter/DataEncodingException.php | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/DataResponseFactory.php b/src/DataResponseFactory.php index ea5d724..d943804 100644 --- a/src/DataResponseFactory.php +++ b/src/DataResponseFactory.php @@ -11,7 +11,7 @@ /** * DataResponseFactory creates an instance of the data response {@see DataResponse}. * - * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactory} instead. + * @deprecated Use {@see ResponseFactory\DataResponseFactory} instead. * * @psalm-suppress DeprecatedInterface, DeprecatedClass */ diff --git a/src/DataResponseFactoryInterface.php b/src/DataResponseFactoryInterface.php index 9b4a80f..5dda38f 100644 --- a/src/DataResponseFactoryInterface.php +++ b/src/DataResponseFactoryInterface.php @@ -9,7 +9,7 @@ /** * `DataResponseFactoryInterface` is the interface that should be implemented by data response factory classes. * - * @deprecated Use {@see \Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface} instead. + * @deprecated Use {@see ResponseFactory\DataResponseFactoryInterface} instead. * * @psalm-suppress DeprecatedClass */ diff --git a/src/Formatter/DataEncodingException.php b/src/Formatter/DataEncodingException.php index 2f071a4..ae16dc9 100644 --- a/src/Formatter/DataEncodingException.php +++ b/src/Formatter/DataEncodingException.php @@ -6,6 +6,4 @@ use LogicException; -final class DataEncodingException extends LogicException -{ -} +final class DataEncodingException extends LogicException {} From bb61efd29da39375028d9bf838afdc19dc5da86d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 11:54:50 +0300 Subject: [PATCH 45/64] phpdoc --- src/DataStream/DataStream.php | 15 ++++++++---- src/Formatter/DataEncodingException.php | 6 +++++ src/Formatter/FormatterInterface.php | 23 ++++++++++++++++++- src/Formatter/HtmlFormatter.php | 9 ++++++++ src/Formatter/JsonFormatter.php | 8 +++++++ src/Formatter/PlainTextFormatter.php | 9 ++++++++ src/Formatter/XmlFormatter.php | 11 +++++++++ .../AbstractDataResponseMiddleware.php | 9 ++++++++ ...ontentNegotiatorDataResponseMiddleware.php | 9 ++++++-- src/Middleware/DataResponseMiddleware.php | 4 ++++ src/Middleware/HtmlDataResponseMiddleware.php | 6 +++++ src/Middleware/JsonDataResponseMiddleware.php | 6 +++++ .../PlainTextDataResponseMiddleware.php | 6 +++++ src/Middleware/XmlDataResponseMiddleware.php | 6 +++++ .../AbstractFormattedResponseFactory.php | 10 ++++++++ .../ContentNegotiatorResponseFactory.php | 18 +++++++++++++-- src/ResponseFactory/DataResponseFactory.php | 10 ++++++++ .../DataResponseFactoryInterface.php | 13 +++++++++++ .../FormattedResponseFactory.php | 4 ++++ src/ResponseFactory/HtmlResponseFactory.php | 7 ++++++ src/ResponseFactory/JsonResponseFactory.php | 7 ++++++ .../PlainTextResponseFactory.php | 7 ++++++ src/ResponseFactory/XmlResponseFactory.php | 7 ++++++ 23 files changed, 201 insertions(+), 9 deletions(-) diff --git a/src/DataStream/DataStream.php b/src/DataStream/DataStream.php index 2370160..a6a0b81 100644 --- a/src/DataStream/DataStream.php +++ b/src/DataStream/DataStream.php @@ -36,11 +36,21 @@ public function __toString(): string return (string) $this->getFormatted(); } + /** + * Checks whether a formatter has been set. + * + * @return bool Whether a formatter is set. + */ public function hasFormatter(): bool { return $this->formatter !== null; } + /** + * Changes the formatter. + * + * @param FormatterInterface $formatter The new formatter. + */ public function changeFormatter(FormatterInterface $formatter): void { $this->formatter = $formatter; @@ -48,7 +58,7 @@ public function changeFormatter(FormatterInterface $formatter): void } /** - * Changes the data and resets the stream state. + * Changes the data. * * @param mixed $data The new data. */ @@ -144,9 +154,6 @@ private function getFormatted(): StreamInterface return $this->formatted; } - /** - * Resets the stream state. - */ private function resetState(): void { if ($this->formatted !== null) { diff --git a/src/Formatter/DataEncodingException.php b/src/Formatter/DataEncodingException.php index ae16dc9..e51ecf7 100644 --- a/src/Formatter/DataEncodingException.php +++ b/src/Formatter/DataEncodingException.php @@ -6,4 +6,10 @@ use LogicException; +/** + * Exception thrown when data encoding fails during response formatting. + * + * This exception is typically thrown by formatters when they fail to encode + * data into a specific format (e.g., JSON, XML). + */ final class DataEncodingException extends LogicException {} diff --git a/src/Formatter/FormatterInterface.php b/src/Formatter/FormatterInterface.php index fde23df..cf2ad46 100644 --- a/src/Formatter/FormatterInterface.php +++ b/src/Formatter/FormatterInterface.php @@ -7,12 +7,33 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; +/** + * Interface that should be implemented by data formatters. + * + * Data formatters are responsible for converting data into a specific format (e.g., JSON, XML, HTML) + * and applying the formatted content to HTTP responses. + */ interface FormatterInterface { /** - * @throws DataEncodingException + * Formats the given data into a stream or string representation. + * + * @param mixed $data The data to be formatted. + * + * @throws DataEncodingException If the data cannot be encoded into the target format. + * + * @return StreamInterface|string The formatted data as a stream or string. */ public function formatData(mixed $data): StreamInterface|string; + /** + * Applies the formatter's headers to the response. + * + * This method does not format the response body data. Use {@see formatData()} to format the data. + * + * @param ResponseInterface $response The response to apply headers to. + * + * @return ResponseInterface The response with the applied headers. + */ public function formatResponse(ResponseInterface $response): ResponseInterface; } diff --git a/src/Formatter/HtmlFormatter.php b/src/Formatter/HtmlFormatter.php index fa0f53e..3b407e4 100644 --- a/src/Formatter/HtmlFormatter.php +++ b/src/Formatter/HtmlFormatter.php @@ -11,8 +11,17 @@ use function is_scalar; use function sprintf; +/** + * Formatter that converts data to HTML and sets appropriate response headers. + * + * Supports scalar values, null, and objects implementing {@see Stringable}. + */ final class HtmlFormatter implements FormatterInterface { + /** + * @param string $contentType The content type for the response. + * @param string $encoding The character encoding for the response. + */ public function __construct( private readonly string $contentType = 'text/html', private readonly string $encoding = 'UTF-8', diff --git a/src/Formatter/JsonFormatter.php b/src/Formatter/JsonFormatter.php index 1ba9938..c8858e5 100644 --- a/src/Formatter/JsonFormatter.php +++ b/src/Formatter/JsonFormatter.php @@ -12,8 +12,16 @@ use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; +/** + * Formatter that encodes data as JSON and sets appropriate response headers. + */ final class JsonFormatter implements FormatterInterface { + /** + * @param string $contentType The content type for the response. + * @param string $encoding The character encoding for the response. + * @param int $options The JSON encoding options. + */ public function __construct( private readonly string $contentType = 'application/json', private readonly string $encoding = 'UTF-8', diff --git a/src/Formatter/PlainTextFormatter.php b/src/Formatter/PlainTextFormatter.php index bb5c5bc..72e54cb 100644 --- a/src/Formatter/PlainTextFormatter.php +++ b/src/Formatter/PlainTextFormatter.php @@ -11,8 +11,17 @@ use function is_scalar; use function sprintf; +/** + * Formatter that converts data to plain text and sets appropriate response headers. + * + * Supports scalar values, null, and objects implementing {@see Stringable}. + */ final class PlainTextFormatter implements FormatterInterface { + /** + * @param string $contentType The content type for the response. + * @param string $encoding The character encoding for the response. + */ public function __construct( private readonly string $contentType = 'text/plain', private readonly string $encoding = 'UTF-8', diff --git a/src/Formatter/XmlFormatter.php b/src/Formatter/XmlFormatter.php index 662803b..d757ba0 100644 --- a/src/Formatter/XmlFormatter.php +++ b/src/Formatter/XmlFormatter.php @@ -18,10 +18,21 @@ use function is_int; use function is_object; +/** + * Formatter that encodes data as XML and sets appropriate response headers. + * + * Supports arrays, traversable objects, and objects implementing {@see XmlDataInterface}. + */ final class XmlFormatter implements FormatterInterface { private const DEFAULT_ITEM_TAG_NAME = 'item'; + /** + * @param string $encoding The character encoding for the XML document and response. + * @param string $version The XML version. + * @param string $rootTag The root element tag name. If empty, no root element is added. + * @param string $contentType The content type for the response. + */ public function __construct( private readonly string $encoding = 'UTF-8', private readonly string $version = '1.0', diff --git a/src/Middleware/AbstractDataResponseMiddleware.php b/src/Middleware/AbstractDataResponseMiddleware.php index d8efd60..e1f77dd 100644 --- a/src/Middleware/AbstractDataResponseMiddleware.php +++ b/src/Middleware/AbstractDataResponseMiddleware.php @@ -11,8 +11,17 @@ use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\FormatterInterface; +/** + * Abstract middleware class that applies a formatter to {@see DataStream} responses + * and sets appropriate response headers. + * + * The middleware only formats responses whose body is a {@see DataStream} without a formatter set. + */ abstract class AbstractDataResponseMiddleware implements MiddlewareInterface { + /** + * @param FormatterInterface $formatter The formatter to apply to the response. + */ public function __construct( private readonly FormatterInterface $formatter, ) {} diff --git a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php index b1906a0..a5edfc1 100644 --- a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -17,11 +17,16 @@ use function is_string; use function sprintf; +/** + * Middleware that selects a formatter for {@see DataStream} responses based on the request's `Accept` header + * and sets appropriate response headers. + */ final class ContentNegotiatorDataResponseMiddleware implements MiddlewareInterface { /** - * @param array $formatters - * @param FormatterInterface|null $fallbackFormatter + * @param array $formatters Map of content types to formatters. + * For example: `['application/json' => new JsonFormatter(), 'application/xml' => new XmlFormatter()]`. + * @param FormatterInterface|null $fallbackFormatter Formatter to use when no match is found. */ public function __construct( private readonly array $formatters = [], diff --git a/src/Middleware/DataResponseMiddleware.php b/src/Middleware/DataResponseMiddleware.php index efc4e33..49bb0f0 100644 --- a/src/Middleware/DataResponseMiddleware.php +++ b/src/Middleware/DataResponseMiddleware.php @@ -4,4 +4,8 @@ namespace Yiisoft\DataResponse\Middleware; +/** + * Middleware that applies a custom formatter to {@see DataStream} responses + * and sets appropriate response headers. + */ final class DataResponseMiddleware extends AbstractDataResponseMiddleware {} diff --git a/src/Middleware/HtmlDataResponseMiddleware.php b/src/Middleware/HtmlDataResponseMiddleware.php index 9a9bdf5..2878b17 100644 --- a/src/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Middleware/HtmlDataResponseMiddleware.php @@ -6,8 +6,14 @@ use Yiisoft\DataResponse\Formatter\HtmlFormatter; +/** + * Middleware that formats {@see DataStream} responses as HTML and sets appropriate response headers. + */ final class HtmlDataResponseMiddleware extends AbstractDataResponseMiddleware { + /** + * @param HtmlFormatter $formatter The HTML formatter to use. + */ public function __construct(HtmlFormatter $formatter = new HtmlFormatter()) { parent::__construct($formatter); diff --git a/src/Middleware/JsonDataResponseMiddleware.php b/src/Middleware/JsonDataResponseMiddleware.php index 0a43e75..a617ac8 100644 --- a/src/Middleware/JsonDataResponseMiddleware.php +++ b/src/Middleware/JsonDataResponseMiddleware.php @@ -6,8 +6,14 @@ use Yiisoft\DataResponse\Formatter\JsonFormatter; +/** + * Middleware that formats {@see DataStream} responses as JSON and sets appropriate response headers. + */ final class JsonDataResponseMiddleware extends AbstractDataResponseMiddleware { + /** + * @param JsonFormatter $formatter The JSON formatter to use. + */ public function __construct(JsonFormatter $formatter = new JsonFormatter()) { parent::__construct($formatter); diff --git a/src/Middleware/PlainTextDataResponseMiddleware.php b/src/Middleware/PlainTextDataResponseMiddleware.php index 2bf5e2f..34dc8c3 100644 --- a/src/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Middleware/PlainTextDataResponseMiddleware.php @@ -6,8 +6,14 @@ use Yiisoft\DataResponse\Formatter\PlainTextFormatter; +/** + * Middleware that formats {@see DataStream} responses as plain text and sets appropriate response headers. + */ final class PlainTextDataResponseMiddleware extends AbstractDataResponseMiddleware { + /** + * @param PlainTextFormatter $formatter The plain text formatter to use. + */ public function __construct(PlainTextFormatter $formatter = new PlainTextFormatter()) { parent::__construct($formatter); diff --git a/src/Middleware/XmlDataResponseMiddleware.php b/src/Middleware/XmlDataResponseMiddleware.php index e84c75c..5522307 100644 --- a/src/Middleware/XmlDataResponseMiddleware.php +++ b/src/Middleware/XmlDataResponseMiddleware.php @@ -6,8 +6,14 @@ use Yiisoft\DataResponse\Formatter\XmlFormatter; +/** + * Middleware that formats {@see DataStream} responses as XML and sets appropriate response headers. + */ final class XmlDataResponseMiddleware extends AbstractDataResponseMiddleware { + /** + * @param XmlFormatter $formatter The XML formatter to use. + */ public function __construct(XmlFormatter $formatter = new XmlFormatter()) { parent::__construct($formatter); diff --git a/src/ResponseFactory/AbstractFormattedResponseFactory.php b/src/ResponseFactory/AbstractFormattedResponseFactory.php index 5c1fb52..327169a 100644 --- a/src/ResponseFactory/AbstractFormattedResponseFactory.php +++ b/src/ResponseFactory/AbstractFormattedResponseFactory.php @@ -10,8 +10,18 @@ use Yiisoft\DataResponse\Formatter\FormatterInterface; use Yiisoft\Http\Status; +/** + * Abstract factory class that creates responses with pre-formatted {@see DataStream} body + * and appropriate response headers. + * + * The formatter is applied immediately when creating the response. + */ abstract class AbstractFormattedResponseFactory implements DataResponseFactoryInterface { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param FormatterInterface $formatter The formatter to apply to the response. + */ public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly FormatterInterface $formatter, diff --git a/src/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/ResponseFactory/ContentNegotiatorResponseFactory.php index c7d3c68..705fcf1 100644 --- a/src/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -14,11 +14,15 @@ use function is_string; use function sprintf; +/** + * Factory that selects a response factory based on the request's `Accept` header. + */ final class ContentNegotiatorResponseFactory { /** - * @param array $factories - * @param DataResponseFactoryInterface $fallbackFactory + * @param array $factories Map of content types to factories. + * For example: `['application/json' => $jsonFactory, 'application/xml' => $xmlFactory]`. + * @param DataResponseFactoryInterface $fallbackFactory Factory to use when no match is found. */ public function __construct( private readonly array $factories, @@ -27,6 +31,16 @@ public function __construct( $this->checkFormatters($factories); } + /** + * Creates an HTTP response using a factory selected based on the request's `Accept` header. + * + * @param RequestInterface $request The request to extract the `Accept` header from. + * @param mixed $data The response data to be included in the response body. + * @param int $code The HTTP status code for the response. + * @param string $reasonPhrase The reason phrase associated with the status code. + * + * @return ResponseInterface The created HTTP response. + */ public function createResponse( RequestInterface $request, mixed $data = null, diff --git a/src/ResponseFactory/DataResponseFactory.php b/src/ResponseFactory/DataResponseFactory.php index d19505f..0beb09e 100644 --- a/src/ResponseFactory/DataResponseFactory.php +++ b/src/ResponseFactory/DataResponseFactory.php @@ -11,8 +11,18 @@ use Yiisoft\DataResponse\Formatter\HtmlFormatter; use Yiisoft\Http\Status; +/** + * Factory that creates responses with {@see DataStream} body without applying a formatter. + * + * The formatter can be applied later using middleware. If no formatter is applied before reading the response body, + * the fallback formatter is used. + */ final class DataResponseFactory implements DataResponseFactoryInterface { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param FormatterInterface $fallbackFormatter The formatter to use when no formatter is applied. + */ public function __construct( private readonly ResponseFactoryInterface $responseFactory, private readonly FormatterInterface $fallbackFormatter = new HtmlFormatter(), diff --git a/src/ResponseFactory/DataResponseFactoryInterface.php b/src/ResponseFactory/DataResponseFactoryInterface.php index 142510e..1724c99 100644 --- a/src/ResponseFactory/DataResponseFactoryInterface.php +++ b/src/ResponseFactory/DataResponseFactoryInterface.php @@ -6,9 +6,22 @@ use Psr\Http\Message\ResponseInterface; use Yiisoft\Http\Status; +use Yiisoft\DataResponse\DataStream\DataStream; +/** + * Interface for factories that create HTTP responses with {@see DataStream} body. + */ interface DataResponseFactoryInterface { + /** + * Creates an HTTP response with the given data wrapped in a {@see DataStream}. + * + * @param mixed $data The data to include in the response body. + * @param int $code The HTTP status code. + * @param string $reasonPhrase The reason phrase. If empty, a default phrase for the status code will be used. + * + * @return ResponseInterface The created response with {@see DataStream} body. + */ public function createResponse( mixed $data = null, int $code = Status::OK, diff --git a/src/ResponseFactory/FormattedResponseFactory.php b/src/ResponseFactory/FormattedResponseFactory.php index 3c75957..112a7da 100644 --- a/src/ResponseFactory/FormattedResponseFactory.php +++ b/src/ResponseFactory/FormattedResponseFactory.php @@ -4,4 +4,8 @@ namespace Yiisoft\DataResponse\ResponseFactory; +/** + * Factory that creates responses with a custom formatter applied to the {@see DataStream} body + * and appropriate response headers. + */ final class FormattedResponseFactory extends AbstractFormattedResponseFactory {} diff --git a/src/ResponseFactory/HtmlResponseFactory.php b/src/ResponseFactory/HtmlResponseFactory.php index 0b9ef40..97675ed 100644 --- a/src/ResponseFactory/HtmlResponseFactory.php +++ b/src/ResponseFactory/HtmlResponseFactory.php @@ -7,8 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Yiisoft\DataResponse\Formatter\HtmlFormatter; +/** + * Factory that creates responses with HTML-formatted {@see DataStream} body and appropriate response headers. + */ final class HtmlResponseFactory extends AbstractFormattedResponseFactory { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param HtmlFormatter $formatter The HTML formatter to use. + */ public function __construct( ResponseFactoryInterface $responseFactory, HtmlFormatter $formatter, diff --git a/src/ResponseFactory/JsonResponseFactory.php b/src/ResponseFactory/JsonResponseFactory.php index 17b999b..ce2a1eb 100644 --- a/src/ResponseFactory/JsonResponseFactory.php +++ b/src/ResponseFactory/JsonResponseFactory.php @@ -7,8 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Yiisoft\DataResponse\Formatter\JsonFormatter; +/** + * Factory that creates responses with JSON-formatted {@see DataStream} body and appropriate response headers. + */ final class JsonResponseFactory extends AbstractFormattedResponseFactory { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param JsonFormatter $formatter The JSON formatter to use. + */ public function __construct( ResponseFactoryInterface $responseFactory, JsonFormatter $formatter, diff --git a/src/ResponseFactory/PlainTextResponseFactory.php b/src/ResponseFactory/PlainTextResponseFactory.php index c2127cc..bb84826 100644 --- a/src/ResponseFactory/PlainTextResponseFactory.php +++ b/src/ResponseFactory/PlainTextResponseFactory.php @@ -7,8 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Yiisoft\DataResponse\Formatter\PlainTextFormatter; +/** + * Factory that creates responses with plain text formatted {@see DataStream} body and appropriate response headers. + */ final class PlainTextResponseFactory extends AbstractFormattedResponseFactory { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param PlainTextFormatter $formatter The plain text formatter to use. + */ public function __construct( ResponseFactoryInterface $responseFactory, PlainTextFormatter $formatter, diff --git a/src/ResponseFactory/XmlResponseFactory.php b/src/ResponseFactory/XmlResponseFactory.php index e6fa704..f251856 100644 --- a/src/ResponseFactory/XmlResponseFactory.php +++ b/src/ResponseFactory/XmlResponseFactory.php @@ -7,8 +7,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Yiisoft\DataResponse\Formatter\XmlFormatter; +/** + * Factory that creates responses with XML-formatted {@see DataStream} body and appropriate response headers. + */ final class XmlResponseFactory extends AbstractFormattedResponseFactory { + /** + * @param ResponseFactoryInterface $responseFactory The PSR-17 response factory. + * @param XmlFormatter $formatter The XML formatter to use. + */ public function __construct( ResponseFactoryInterface $responseFactory, XmlFormatter $formatter, From a6d2c0303c8b75a416c7432c079a81303268a09f Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 17:47:06 +0300 Subject: [PATCH 46/64] config --- config/di-web.php | 39 +++++++++++++++++-- ...ontentNegotiatorDataResponseMiddleware.php | 4 +- .../ContentNegotiatorResponseFactory.php | 4 +- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/config/di-web.php b/config/di-web.php index 4721d77..7ba4eab 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -2,21 +2,54 @@ declare(strict_types=1); -use Yiisoft\DataResponse\DataResponseFactory; -use Yiisoft\DataResponse\DataResponseFactoryInterface; +use Psr\Container\ContainerInterface; +use Yiisoft\DataResponse\DataResponseFactory as DeprecatedDataResponseFactory; +use Yiisoft\DataResponse\DataResponseFactoryInterface as DeprecatedDataResponseFactoryInterface; use Yiisoft\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Formatter\HtmlDataResponseFormatter; +use Yiisoft\DataResponse\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\Formatter\JsonFormatter; +use Yiisoft\DataResponse\Formatter\XmlFormatter; use Yiisoft\DataResponse\Middleware\ContentNegotiator; +use Yiisoft\DataResponse\Middleware\ContentNegotiatorDataResponseMiddleware; +use Yiisoft\DataResponse\ResponseFactory\ContentNegotiatorResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\DataResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface; +use Yiisoft\DataResponse\ResponseFactory\HtmlResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\JsonResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\XmlResponseFactory; use Yiisoft\Definitions\DynamicReferencesArray; /* @var $params array */ return [ DataResponseFormatterInterface::class => HtmlDataResponseFormatter::class, - DataResponseFactoryInterface::class => DataResponseFactory::class, + DeprecatedDataResponseFactoryInterface::class => DeprecatedDataResponseFactory::class, ContentNegotiator::class => [ '__construct()' => [ 'contentFormatters' => DynamicReferencesArray::from($params['yiisoft/data-response']['contentFormatters']), ], ], + DataResponseFactoryInterface::class => DataResponseFactory::class, + ContentNegotiatorDataResponseMiddleware::class => + static function (ContainerInterface $container): ContentNegotiatorDataResponseMiddleware { + return new ContentNegotiatorDataResponseMiddleware([ + 'text/html' => $container->get(HtmlFormatter::class), + 'application/xml' => $container->get(XmlFormatter::class), + 'application/json' => $container->get(JsonFormatter::class), + ]); + }, + ContentNegotiatorResponseFactory::class => + static function (ContainerInterface $container): ContentNegotiatorResponseFactory { + /** @var HtmlResponseFactory $html */ + $html = $container->get(HtmlResponseFactory::class); + return new ContentNegotiatorResponseFactory( + [ + 'text/html' => $html, + 'application/xml' => $container->get(XmlResponseFactory::class), + 'application/json' => $container->get(JsonResponseFactory::class), + ], + $html, + ); + }, ]; diff --git a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php index a5edfc1..486245a 100644 --- a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -24,9 +24,11 @@ final class ContentNegotiatorDataResponseMiddleware implements MiddlewareInterface { /** - * @param array $formatters Map of content types to formatters. + * @param FormatterInterface[] $formatters Map of content types to formatters. * For example: `['application/json' => new JsonFormatter(), 'application/xml' => new XmlFormatter()]`. * @param FormatterInterface|null $fallbackFormatter Formatter to use when no match is found. + * + * @psalm-param array $formatters */ public function __construct( private readonly array $formatters = [], diff --git a/src/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/ResponseFactory/ContentNegotiatorResponseFactory.php index 705fcf1..177a553 100644 --- a/src/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -20,9 +20,11 @@ final class ContentNegotiatorResponseFactory { /** - * @param array $factories Map of content types to factories. + * @param DataResponseFactoryInterface[] $factories Map of content types to factories. * For example: `['application/json' => $jsonFactory, 'application/xml' => $xmlFactory]`. * @param DataResponseFactoryInterface $fallbackFactory Factory to use when no match is found. + * + * @psalm-param array $factories */ public function __construct( private readonly array $factories, From c98c5823c06761039e2b37de1befa9595647c26f Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:47:43 +0000 Subject: [PATCH 47/64] Apply PHP CS Fixer and Rector changes (CI) --- config/di-web.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/di-web.php b/config/di-web.php index 7ba4eab..9999bc4 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -31,16 +31,16 @@ ], ], DataResponseFactoryInterface::class => DataResponseFactory::class, - ContentNegotiatorDataResponseMiddleware::class => - static function (ContainerInterface $container): ContentNegotiatorDataResponseMiddleware { + ContentNegotiatorDataResponseMiddleware::class + => static function (ContainerInterface $container): ContentNegotiatorDataResponseMiddleware { return new ContentNegotiatorDataResponseMiddleware([ 'text/html' => $container->get(HtmlFormatter::class), 'application/xml' => $container->get(XmlFormatter::class), 'application/json' => $container->get(JsonFormatter::class), ]); }, - ContentNegotiatorResponseFactory::class => - static function (ContainerInterface $container): ContentNegotiatorResponseFactory { + ContentNegotiatorResponseFactory::class + => static function (ContainerInterface $container): ContentNegotiatorResponseFactory { /** @var HtmlResponseFactory $html */ $html = $container->get(HtmlResponseFactory::class); return new ContentNegotiatorResponseFactory( From 3e4ae118d148624953edf996b58dd88ad9b2851d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 18:03:42 +0300 Subject: [PATCH 48/64] docs --- README.md | 144 +++++++++++++++++++++++++++------------------ docs/deprecated.md | 97 ++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 58 deletions(-) create mode 100644 docs/deprecated.md diff --git a/README.md b/README.md index fcd1bb4..ae6e65a 100644 --- a/README.md +++ b/README.md @@ -32,99 +32,127 @@ composer require yiisoft/data-response ## General usage -The package provides `DataResponseFactory` class that, given a [PSR-17](https://www.php-fig.org/psr/psr-17/) -response factory, is able to create data response. +### Response Factories -Data response contains raw data to be processed later. +The package provides response factories that create [PSR-7](https://www.php-fig.org/psr/psr-7/) responses +with `DataStream` body. The data is formatted lazily when the response body is read. ```php -use Yiisoft\DataResponse\DataResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\JsonResponseFactory; +use Yiisoft\DataResponse\Formatter\JsonFormatter; /** * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory */ -$factory = new DataResponseFactory($responseFactory); -$dataResponse = $factory->createResponse('test'); -$dataResponse - ->getBody() - ->rewind(); +$factory = new JsonResponseFactory($responseFactory, new JsonFormatter()); +$response = $factory->createResponse(['key' => 'value']); -echo $dataResponse - ->getBody() - ->getContents(); // "test" +$response->getBody()->rewind(); +echo $response->getBody()->getContents(); // {"key":"value"} +echo $response->getHeaderLine('Content-Type'); // application/json; charset=UTF-8 ``` -### Formatters +The following response factories are available: -Formatter purpose is to format a data response. In the following example we format data as JSON. +- `JsonResponseFactory` — creates responses with JSON-formatted body; +- `XmlResponseFactory` — creates responses with XML-formatted body; +- `HtmlResponseFactory` — creates responses with HTML-formatted body; +- `PlainTextResponseFactory` — creates responses with plain text body; +- `DataResponseFactory` — creates responses without a predefined formatter, use middleware to format. -```php -use Yiisoft\DataResponse\DataResponseFactory; -use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; +### Middleware -/** - * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory - */ +The package provides [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware that formats `DataStream` responses +without a predefined formatter. -$factory = new DataResponseFactory($responseFactory); -$dataResponse = $factory->createResponse('test'); -$dataResponse = $dataResponse->withResponseFormatter(new JsonDataResponseFormatter()); -$dataResponse - ->getBody() - ->rewind(); - -echo $dataResponse->getHeader('Content-Type'); // ["application/json; charset=UTF-8"] -echo $dataResponse - ->getBody() - ->getContents(); // "test" +```php +use Yiisoft\DataResponse\Middleware\JsonDataResponseMiddleware; +use Yiisoft\DataResponse\Formatter\JsonFormatter; + +$middleware = new JsonDataResponseMiddleware(new JsonFormatter()); ``` -The following formatters are available: +The following middleware are available: -- `HtmlDataResponseFormatter` -- `JsonDataResponseFormatter` -- `XmlDataResponseFormatter` -- `PlainTextDataResponseFormatter` +- `HtmlDataResponseMiddleware` +- `JsonDataResponseMiddleware` +- `XmlDataResponseMiddleware` +- `PlainTextDataResponseMiddleware` -### Middleware +### Content Negotiation + +The package provides content negotiation via middleware and response factory. + +#### Middleware + +`ContentNegotiatorDataResponseMiddleware` selects a formatter based on the request's `Accept` header: -The package provides a [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware that is able to format a data response. +```php +use Yiisoft\DataResponse\Formatter\HtmlFormatter; +use Yiisoft\DataResponse\Formatter\XmlFormatter; +use Yiisoft\DataResponse\Formatter\JsonFormatter; +use Yiisoft\DataResponse\Middleware\ContentNegotiatorDataResponseMiddleware; + +$middleware = new ContentNegotiatorDataResponseMiddleware( + formatters: [ + 'text/html' => new HtmlFormatter(), + 'application/xml' => new XmlFormatter(), + 'application/json' => new JsonFormatter(), + ], + fallbackFormatter: new JsonFormatter(), +); +``` + +#### Response Factory + +`ContentNegotiatorResponseFactory` selects a response factory based on the request's `Accept` header: ```php -use Yiisoft\DataResponse\Middleware\FormatDataResponse; -use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; +use Yiisoft\DataResponse\ResponseFactory\ContentNegotiatorResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\JsonResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\XmlResponseFactory; -$middleware = (new FormatDataResponse(new JsonDataResponseFormatter())); -//$middleware->process($request, $handler); +/** + * @var JsonResponseFactory $jsonResponseFactory + * @var XmlResponseFactory $xmlResponseFactory + */ + +$factory = new ContentNegotiatorResponseFactory( + factories: [ + 'application/json' => $jsonResponseFactory, + 'application/xml' => $xmlResponseFactory, + ], + fallbackFactory: $jsonResponseFactory, +); + +$response = $factory->createResponse($request, ['key' => 'value']); ``` -Also, the package provides [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware for content negotiation: +### DataStream + +`DataStream` is a [PSR-7](https://www.php-fig.org/psr/psr-7/) stream that lazily formats data. +It wraps raw data and a formatter, and performs formatting only when the stream is read. ```php -use Yiisoft\DataResponse\Formatter\HtmlDataResponseFormatter; -use Yiisoft\DataResponse\Formatter\XmlDataResponseFormatter; -use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; -use Yiisoft\DataResponse\Middleware\ContentNegotiator; - -$middleware = new ContentNegotiator([ - 'text/html' => new HtmlDataResponseFormatter(), - 'application/xml' => new XmlDataResponseFormatter(), - 'application/json' => new JsonDataResponseFormatter(), -]); +use Yiisoft\DataResponse\DataStream\DataStream; +use Yiisoft\DataResponse\Formatter\JsonFormatter; + +$stream = new DataStream(['key' => 'value'], new JsonFormatter()); + +echo (string) $stream; // {"key":"value"} ``` -You can override middlewares with method `withContentFormatters()`: +You can change the data or formatter dynamically: ```php -$middleware->withContentFormatters([ - 'application/xml' => new XmlDataResponseFormatter(), - 'application/json' => new JsonDataResponseFormatter(), -]); +$stream->changeData(['new' => 'data']); +$stream->changeFormatter(new XmlFormatter()); ``` ## Documentation +- [Deprecated classes](docs/deprecated.md) - [Internals](docs/internals.md) If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that. diff --git a/docs/deprecated.md b/docs/deprecated.md new file mode 100644 index 0000000..753353e --- /dev/null +++ b/docs/deprecated.md @@ -0,0 +1,97 @@ +# Deprecated + +> [!WARNING] +> The classes described in this document are deprecated and will be removed in a future version. + +## General usage + +The package provides `DataResponseFactory` class that, given a [PSR-17](https://www.php-fig.org/psr/psr-17/) +response factory, is able to create data response. + +Data response contains raw data to be processed later. + +```php +use Yiisoft\DataResponse\DataResponseFactory; + +/** + * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory + */ + +$factory = new DataResponseFactory($responseFactory); +$dataResponse = $factory->createResponse('test'); +$dataResponse + ->getBody() + ->rewind(); + +echo $dataResponse + ->getBody() + ->getContents(); // "test" +``` + +## Formatters + +Formatter purpose is to format a data response. In the following example we format data as JSON. + +```php +use Yiisoft\DataResponse\DataResponseFactory; +use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; + +/** + * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory + */ + +$factory = new DataResponseFactory($responseFactory); +$dataResponse = $factory->createResponse('test'); +$dataResponse = $dataResponse->withResponseFormatter(new JsonDataResponseFormatter()); +$dataResponse + ->getBody() + ->rewind(); + +echo $dataResponse->getHeader('Content-Type'); // ["application/json; charset=UTF-8"] +echo $dataResponse + ->getBody() + ->getContents(); // "test" +``` + +The following formatters are available: + +- `HtmlDataResponseFormatter` +- `JsonDataResponseFormatter` +- `XmlDataResponseFormatter` +- `PlainTextDataResponseFormatter` + +## Middleware + +The package provides a [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware that is able to format a data response. + +```php +use Yiisoft\DataResponse\Middleware\FormatDataResponse; +use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; + +$middleware = (new FormatDataResponse(new JsonDataResponseFormatter())); +//$middleware->process($request, $handler); +``` + +Also, the package provides [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware for content negotiation: + +```php +use Yiisoft\DataResponse\Formatter\HtmlDataResponseFormatter; +use Yiisoft\DataResponse\Formatter\XmlDataResponseFormatter; +use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; +use Yiisoft\DataResponse\Middleware\ContentNegotiator; + +$middleware = new ContentNegotiator([ + 'text/html' => new HtmlDataResponseFormatter(), + 'application/xml' => new XmlDataResponseFormatter(), + 'application/json' => new JsonDataResponseFormatter(), +]); +``` + +You can override middlewares with method `withContentFormatters()`: + +```php +$middleware->withContentFormatters([ + 'application/xml' => new XmlDataResponseFormatter(), + 'application/json' => new JsonDataResponseFormatter(), +]); +``` From 93846b62afceb6a52084977945753e6e2fdd363a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 4 Feb 2026 18:10:50 +0300 Subject: [PATCH 49/64] changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b901594..dd0e6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## 2.1.3 under development +- New #107: Add `DataStream` (@vjik) +- New #107: Add `FormatterInterface` and implementations: `HtmlFormatter`, `JsonFormatter`, `PlainTextFormatter`, + `XmlFormatter` (@vjik) +- New #107: Add `DataResponseFactoryInterface` and implementations: `DataResponseFactory`, `FormattedResponseFactory`, + `HtmlResponseFactory`, `JsonResponseFactory`, `PlainTextResponseFactory`, `XmlResponseFactory` (@vjik) +- New #107: Add middlewares: `XmlDataResponseMiddleware`, `HtmlDataResponseMiddleware`, `JsonDataResponseMiddleware`, + `PlainTextDataResponseMiddleware` and `DataResponseMiddleware` (@vjik) +- New #107: Add `ContentNegotiatorResponseFactory` and `ContentNegotiatorDataResponseMiddleware` (@vjik) +- Chg #107: Deprecate `DataResponse`, `DataResponseFactory`, `DataResponseFactoryInterface`, + `DataResponseFormatterInterface`, `ResponseContentTrait`, `HtmlDataResponseFormatter`, + `JsonDataResponseFormatter`, `PlainTextDataResponseFormatter`, `XmlDataResponseFormatter`, `ContentNegotiator`, + `FormatDataResponse`, `FormatDataResponseAsHtml`, `FormatDataResponseAsJson`, `FormatDataResponseAsPlainText`, + `FormatDataResponseAsXml` (@vjik) - Enh #106: Explicitly import classes, functions, and constants in "use" section (@mspirkov) - Enh #108: Remove unnecessary files from Composer package (@mspirkov) From 7081609ca96e07aa6a3d2d813e84b32d01d20612 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 10:50:27 +0300 Subject: [PATCH 50/64] Allow use `RequestHandlerInterface` as fallback --- ...ontentNegotiatorDataResponseMiddleware.php | 15 ++++++++++----- .../ContentNegotiatorResponseFactory.php | 18 ++++++++++++------ ...ntNegotiatorDataResponseMiddlewareTest.php | 18 ++++++++++++++++++ .../ContentNegotiatorResponseFactoryTest.php | 19 +++++++++++++++++++ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php index 486245a..32259ee 100644 --- a/src/Middleware/ContentNegotiatorDataResponseMiddleware.php +++ b/src/Middleware/ContentNegotiatorDataResponseMiddleware.php @@ -26,13 +26,14 @@ final class ContentNegotiatorDataResponseMiddleware implements MiddlewareInterfa /** * @param FormatterInterface[] $formatters Map of content types to formatters. * For example: `['application/json' => new JsonFormatter(), 'application/xml' => new XmlFormatter()]`. - * @param FormatterInterface|null $fallbackFormatter Formatter to use when no match is found. + * @param FormatterInterface|RequestHandlerInterface|null $fallback Formatter or request handler + * to use when no match is found. If `null`, the response is returned unmodified. * * @psalm-param array $formatters */ public function __construct( private readonly array $formatters = [], - private readonly ?FormatterInterface $fallbackFormatter = null, + private readonly FormatterInterface|RequestHandlerInterface|null $fallback = null, ) { $this->checkFormatters($formatters); } @@ -58,12 +59,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } } - if ($this->fallbackFormatter === null) { + if ($this->fallback === null) { return $response; } - $body->changeFormatter($this->fallbackFormatter); - return $this->fallbackFormatter->formatResponse($response); + if ($this->fallback instanceof RequestHandlerInterface) { + return $this->fallback->handle($request); + } + + $body->changeFormatter($this->fallback); + return $this->fallback->formatResponse($response); } private function checkFormatters(array $formatters): void diff --git a/src/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/ResponseFactory/ContentNegotiatorResponseFactory.php index 177a553..088974f 100644 --- a/src/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -4,8 +4,9 @@ namespace Yiisoft\DataResponse\ResponseFactory; -use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Yiisoft\Http\HeaderValueHelper; use Yiisoft\Http\Status; @@ -22,13 +23,14 @@ final class ContentNegotiatorResponseFactory /** * @param DataResponseFactoryInterface[] $factories Map of content types to factories. * For example: `['application/json' => $jsonFactory, 'application/xml' => $xmlFactory]`. - * @param DataResponseFactoryInterface $fallbackFactory Factory to use when no match is found. + * @param DataResponseFactoryInterface|RequestHandlerInterface $fallback Factory or request handler + * to use when no match is found. * * @psalm-param array $factories */ public function __construct( private readonly array $factories, - private readonly DataResponseFactoryInterface $fallbackFactory, + private readonly DataResponseFactoryInterface|RequestHandlerInterface $fallback, ) { $this->checkFormatters($factories); } @@ -36,7 +38,7 @@ public function __construct( /** * Creates an HTTP response using a factory selected based on the request's `Accept` header. * - * @param RequestInterface $request The request to extract the `Accept` header from. + * @param ServerRequestInterface $request The request to extract the `Accept` header from. * @param mixed $data The response data to be included in the response body. * @param int $code The HTTP status code for the response. * @param string $reasonPhrase The reason phrase associated with the status code. @@ -44,7 +46,7 @@ public function __construct( * @return ResponseInterface The created HTTP response. */ public function createResponse( - RequestInterface $request, + ServerRequestInterface $request, mixed $data = null, int $code = Status::OK, string $reasonPhrase = '', @@ -61,7 +63,11 @@ public function createResponse( } } - return $this->fallbackFactory->createResponse($data, $code, $reasonPhrase); + if ($this->fallback instanceof RequestHandlerInterface) { + return $this->fallback->handle($request); + } + + return $this->fallback->createResponse($data, $code, $reasonPhrase); } private function checkFormatters(array $formatters): void diff --git a/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php index 7af9880..c1f9440 100644 --- a/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php +++ b/tests/Middleware/ContentNegotiatorDataResponseMiddlewareTest.php @@ -146,4 +146,22 @@ public function testConstructorWithInvalidFormatter(): void ); new ContentNegotiatorDataResponseMiddleware($formatters); } + + public function testProcessWithNoMatchingAcceptHeaderUsesRequestHandlerFallback(): void + { + $dataStream = new DataStream(['key' => 'value']); + $response = (new Response())->withBody($dataStream); + $fallbackResponse = (new Response())->withStatus(406); + $middleware = new ContentNegotiatorDataResponseMiddleware( + ['application/json' => new JsonFormatter()], + new StubRequestHandler($fallbackResponse), + ); + + $result = $middleware->process( + (new ServerRequest())->withHeader(Header::ACCEPT, 'text/html'), + new StubRequestHandler($response), + ); + + $this->assertSame($fallbackResponse, $result); + } } diff --git a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php index 302917a..7841948 100644 --- a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php +++ b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php @@ -4,11 +4,13 @@ namespace Yiisoft\DataResponse\Tests\ResponseFactory; +use HttpSoft\Message\Response; use HttpSoft\Message\ResponseFactory; use HttpSoft\Message\ServerRequest; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; +use Yiisoft\DataResponse\Tests\Support\StubRequestHandler; use Yiisoft\DataResponse\Formatter\JsonFormatter; use Yiisoft\DataResponse\Formatter\PlainTextFormatter; use Yiisoft\DataResponse\Formatter\XmlFormatter; @@ -118,4 +120,21 @@ public function testConstructorWithInvalidFactory(): void ); new ContentNegotiatorResponseFactory($factories, $fallbackFactory); } + + public function testCreateResponseWithNoMatchingAcceptHeaderUsesRequestHandlerFallback(): void + { + $responseFactory = new ResponseFactory(); + $fallbackResponse = (new Response())->withStatus(406); + $factory = new ContentNegotiatorResponseFactory( + [ + 'application/json' => new JsonResponseFactory($responseFactory, new JsonFormatter()), + ], + new StubRequestHandler($fallbackResponse), + ); + + $request = (new ServerRequest())->withHeader(Header::ACCEPT, 'text/html'); + $response = $factory->createResponse($request, 'test content'); + + $this->assertSame($fallbackResponse, $response); + } } From 4c6bafefe77f3eb0bdfe2b6193d8bfebbc2a90c4 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 10:56:46 +0300 Subject: [PATCH 51/64] NotAcceptableRequestHandler --- README.md | 10 ++++++-- config/di-web.php | 20 ++++++++-------- src/NotAcceptableRequestHandler.php | 28 +++++++++++++++++++++++ tests/NotAcceptableRequestHandlerTest.php | 21 +++++++++++++++++ 4 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 src/NotAcceptableRequestHandler.php create mode 100644 tests/NotAcceptableRequestHandlerTest.php diff --git a/README.md b/README.md index ae6e65a..eeca652 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,13 @@ $middleware = new ContentNegotiatorDataResponseMiddleware( 'application/xml' => new XmlFormatter(), 'application/json' => new JsonFormatter(), ], - fallbackFormatter: new JsonFormatter(), + fallback: new JsonFormatter(), ); ``` +The `fallback` parameter also accepts a `RequestHandlerInterface`, for example `NotAcceptableRequestHandler` +to return a 406 response when no formatter matches. + #### Response Factory `ContentNegotiatorResponseFactory` selects a response factory based on the request's `Accept` header: @@ -123,12 +126,15 @@ $factory = new ContentNegotiatorResponseFactory( 'application/json' => $jsonResponseFactory, 'application/xml' => $xmlResponseFactory, ], - fallbackFactory: $jsonResponseFactory, + fallback: $jsonResponseFactory, ); $response = $factory->createResponse($request, ['key' => 'value']); ``` +The `fallback` parameter also accepts a `RequestHandlerInterface`, for example `NotAcceptableRequestHandler` +to return a 406 response when no factory matches. + ### DataStream `DataStream` is a [PSR-7](https://www.php-fig.org/psr/psr-7/) stream that lazily formats data. diff --git a/config/di-web.php b/config/di-web.php index 9999bc4..ca7df8a 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -12,6 +12,7 @@ use Yiisoft\DataResponse\Formatter\XmlFormatter; use Yiisoft\DataResponse\Middleware\ContentNegotiator; use Yiisoft\DataResponse\Middleware\ContentNegotiatorDataResponseMiddleware; +use Yiisoft\DataResponse\NotAcceptableRequestHandler; use Yiisoft\DataResponse\ResponseFactory\ContentNegotiatorResponseFactory; use Yiisoft\DataResponse\ResponseFactory\DataResponseFactory; use Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface; @@ -33,23 +34,24 @@ DataResponseFactoryInterface::class => DataResponseFactory::class, ContentNegotiatorDataResponseMiddleware::class => static function (ContainerInterface $container): ContentNegotiatorDataResponseMiddleware { - return new ContentNegotiatorDataResponseMiddleware([ - 'text/html' => $container->get(HtmlFormatter::class), - 'application/xml' => $container->get(XmlFormatter::class), - 'application/json' => $container->get(JsonFormatter::class), - ]); + return new ContentNegotiatorDataResponseMiddleware( + [ + 'text/html' => $container->get(HtmlFormatter::class), + 'application/xml' => $container->get(XmlFormatter::class), + 'application/json' => $container->get(JsonFormatter::class), + ], + fallback: $container->get(NotAcceptableRequestHandler::class), + ); }, ContentNegotiatorResponseFactory::class => static function (ContainerInterface $container): ContentNegotiatorResponseFactory { - /** @var HtmlResponseFactory $html */ - $html = $container->get(HtmlResponseFactory::class); return new ContentNegotiatorResponseFactory( [ - 'text/html' => $html, + 'text/html' => $container->get(HtmlResponseFactory::class), 'application/xml' => $container->get(XmlResponseFactory::class), 'application/json' => $container->get(JsonResponseFactory::class), ], - $html, + $container->get(NotAcceptableRequestHandler::class), ); }, ]; diff --git a/src/NotAcceptableRequestHandler.php b/src/NotAcceptableRequestHandler.php new file mode 100644 index 0000000..14fe646 --- /dev/null +++ b/src/NotAcceptableRequestHandler.php @@ -0,0 +1,28 @@ +responseFactory->createResponse(Status::NOT_ACCEPTABLE); + } +} diff --git a/tests/NotAcceptableRequestHandlerTest.php b/tests/NotAcceptableRequestHandlerTest.php new file mode 100644 index 0000000..05ae8d2 --- /dev/null +++ b/tests/NotAcceptableRequestHandlerTest.php @@ -0,0 +1,21 @@ +handle($this->createRequest()); + + $this->assertSame(Status::NOT_ACCEPTABLE, $response->getStatusCode()); + } +} From 96e556565613a1964a26f4ffcab544ecd49db9e5 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:10:18 +0300 Subject: [PATCH 52/64] rename --- .../ContentNegotiatorResponseFactory.php | 14 +++++++------- .../ContentNegotiatorResponseFactoryTest.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ResponseFactory/ContentNegotiatorResponseFactory.php b/src/ResponseFactory/ContentNegotiatorResponseFactory.php index 088974f..b5a832b 100644 --- a/src/ResponseFactory/ContentNegotiatorResponseFactory.php +++ b/src/ResponseFactory/ContentNegotiatorResponseFactory.php @@ -32,7 +32,7 @@ public function __construct( private readonly array $factories, private readonly DataResponseFactoryInterface|RequestHandlerInterface $fallback, ) { - $this->checkFormatters($factories); + $this->checkFactories($factories); } /** @@ -70,24 +70,24 @@ public function createResponse( return $this->fallback->createResponse($data, $code, $reasonPhrase); } - private function checkFormatters(array $formatters): void + private function checkFactories(array $factories): void { - foreach ($formatters as $contentType => $formatter) { + foreach ($factories as $contentType => $factory) { if (!is_string($contentType)) { throw new RuntimeException( sprintf( - 'Invalid formatter content type. A string is expected, "%s" is received.', + 'Invalid factory content type. A string is expected, "%s" is received.', gettype($contentType), ), ); } - if (!($formatter instanceof DataResponseFactoryInterface)) { + if (!($factory instanceof DataResponseFactoryInterface)) { throw new RuntimeException( sprintf( - 'Invalid formatter. A "%s" instance is expected, "%s" is received.', + 'Invalid factory. A "%s" instance is expected, "%s" is received.', DataResponseFactoryInterface::class, - get_debug_type($formatter), + get_debug_type($factory), ), ); } diff --git a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php index 7841948..566bd9a 100644 --- a/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php +++ b/tests/ResponseFactory/ContentNegotiatorResponseFactoryTest.php @@ -102,7 +102,7 @@ public function testConstructorWithInvalidContentType(): void $fallbackFactory = new PlainTextResponseFactory($responseFactory, new PlainTextFormatter()); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Invalid formatter content type. A string is expected, "integer" is received.'); + $this->expectExceptionMessage('Invalid factory content type. A string is expected, "integer" is received.'); new ContentNegotiatorResponseFactory($factories, $fallbackFactory); } @@ -116,7 +116,7 @@ public function testConstructorWithInvalidFactory(): void $this->expectException(RuntimeException::class); $this->expectExceptionMessage( - 'Invalid formatter. A "Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface" instance is expected, "stdClass" is received.', + 'Invalid factory. A "Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface" instance is expected, "stdClass" is received.', ); new ContentNegotiatorResponseFactory($factories, $fallbackFactory); } From 1ae5cd8cfe28054844b4a6edfd40f9945da33b9d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:12:04 +0300 Subject: [PATCH 53/64] fix --- tests/Support/StubFormatter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Support/StubFormatter.php b/tests/Support/StubFormatter.php index 56ef19a..a5fe002 100644 --- a/tests/Support/StubFormatter.php +++ b/tests/Support/StubFormatter.php @@ -11,7 +11,7 @@ final class StubFormatter implements FormatterInterface { public function __construct( - private readonly StreamInterface|string $formattedData = '', + private readonly StreamInterface $formattedData, ) {} public function formatData(mixed $data): StreamInterface From d20ecec928823897226fb23055b782323a693359 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:12:37 +0300 Subject: [PATCH 54/64] fix --- tests/DataStream/DataStreamTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DataStream/DataStreamTest.php b/tests/DataStream/DataStreamTest.php index 507afb2..ec682f4 100644 --- a/tests/DataStream/DataStreamTest.php +++ b/tests/DataStream/DataStreamTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace DataStream; +namespace Yiisoft\DataResponse\Tests\DataStream; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; From 06f61989a3c287d4c4469bc01a92c9573a1e2f90 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:14:35 +0300 Subject: [PATCH 55/64] improve test --- tests/Formatter/HtmlFormatterTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Formatter/HtmlFormatterTest.php b/tests/Formatter/HtmlFormatterTest.php index f842c94..3169dc8 100644 --- a/tests/Formatter/HtmlFormatterTest.php +++ b/tests/Formatter/HtmlFormatterTest.php @@ -58,7 +58,9 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void $formatter = new HtmlFormatter(); $this->expectException(DataEncodingException::class); - $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); + $this->expectExceptionMessage( + 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.' + ); $formatter->formatData($data); } From 0f6a5a39d2bd943b29d49b42bbbb11795ebbbe3e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:15:56 +0300 Subject: [PATCH 56/64] fix --- src/Formatter/XmlDataInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Formatter/XmlDataInterface.php b/src/Formatter/XmlDataInterface.php index 4b67043..c399773 100644 --- a/src/Formatter/XmlDataInterface.php +++ b/src/Formatter/XmlDataInterface.php @@ -5,7 +5,7 @@ namespace Yiisoft\DataResponse\Formatter; /** - * XmlFormatDataInterface provides methods used when formatting objects {@see XmlFormatter} as XML data. + * Interface provides methods used when formatting objects {@see XmlFormatter} as XML data. */ interface XmlDataInterface { From 8abcb19e5f6007c5e8a7796c16b31d02dd69b938 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:16:54 +0300 Subject: [PATCH 57/64] improve test --- tests/Formatter/PlainTextFormatterTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Formatter/PlainTextFormatterTest.php b/tests/Formatter/PlainTextFormatterTest.php index c3fa7d0..f81b8e8 100644 --- a/tests/Formatter/PlainTextFormatterTest.php +++ b/tests/Formatter/PlainTextFormatterTest.php @@ -58,7 +58,9 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void $formatter = new PlainTextFormatter(); $this->expectException(DataEncodingException::class); - $this->expectExceptionMessage('Data must be either a scalar value, null, or a stringable object.'); + $this->expectExceptionMessage( + 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.' + ); $formatter->formatData($data); } From b2889081b5802283a51836a9649a1df2c8554691 Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Thu, 5 Feb 2026 08:18:30 +0000 Subject: [PATCH 58/64] Apply PHP CS Fixer and Rector changes (CI) --- tests/Formatter/HtmlFormatterTest.php | 2 +- tests/Formatter/PlainTextFormatterTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Formatter/HtmlFormatterTest.php b/tests/Formatter/HtmlFormatterTest.php index 3169dc8..8c605b0 100644 --- a/tests/Formatter/HtmlFormatterTest.php +++ b/tests/Formatter/HtmlFormatterTest.php @@ -59,7 +59,7 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void $this->expectException(DataEncodingException::class); $this->expectExceptionMessage( - 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.' + 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.', ); $formatter->formatData($data); } diff --git a/tests/Formatter/PlainTextFormatterTest.php b/tests/Formatter/PlainTextFormatterTest.php index f81b8e8..46bd94a 100644 --- a/tests/Formatter/PlainTextFormatterTest.php +++ b/tests/Formatter/PlainTextFormatterTest.php @@ -59,7 +59,7 @@ public function testFormatDataWithUnsupportedValue(mixed $data): void $this->expectException(DataEncodingException::class); $this->expectExceptionMessage( - 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.' + 'Data must be either a scalar value, null, or a stringable object. ' . get_debug_type($data) . ' given.', ); $formatter->formatData($data); } From b085961f5ca12f79219b62e49fb7e1a11eb695cf Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:29:30 +0300 Subject: [PATCH 59/64] fix --- src/Middleware/HtmlDataResponseMiddleware.php | 1 + src/Middleware/JsonDataResponseMiddleware.php | 1 + src/Middleware/PlainTextDataResponseMiddleware.php | 1 + src/Middleware/XmlDataResponseMiddleware.php | 1 + src/ResponseFactory/FormattedResponseFactory.php | 2 ++ src/ResponseFactory/HtmlResponseFactory.php | 1 + src/ResponseFactory/JsonResponseFactory.php | 1 + src/ResponseFactory/PlainTextResponseFactory.php | 1 + src/ResponseFactory/XmlResponseFactory.php | 1 + 9 files changed, 10 insertions(+) diff --git a/src/Middleware/HtmlDataResponseMiddleware.php b/src/Middleware/HtmlDataResponseMiddleware.php index 2878b17..9623b93 100644 --- a/src/Middleware/HtmlDataResponseMiddleware.php +++ b/src/Middleware/HtmlDataResponseMiddleware.php @@ -4,6 +4,7 @@ namespace Yiisoft\DataResponse\Middleware; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\HtmlFormatter; /** diff --git a/src/Middleware/JsonDataResponseMiddleware.php b/src/Middleware/JsonDataResponseMiddleware.php index a617ac8..b2696db 100644 --- a/src/Middleware/JsonDataResponseMiddleware.php +++ b/src/Middleware/JsonDataResponseMiddleware.php @@ -4,6 +4,7 @@ namespace Yiisoft\DataResponse\Middleware; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\JsonFormatter; /** diff --git a/src/Middleware/PlainTextDataResponseMiddleware.php b/src/Middleware/PlainTextDataResponseMiddleware.php index 34dc8c3..21dc751 100644 --- a/src/Middleware/PlainTextDataResponseMiddleware.php +++ b/src/Middleware/PlainTextDataResponseMiddleware.php @@ -4,6 +4,7 @@ namespace Yiisoft\DataResponse\Middleware; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\PlainTextFormatter; /** diff --git a/src/Middleware/XmlDataResponseMiddleware.php b/src/Middleware/XmlDataResponseMiddleware.php index 5522307..f57d805 100644 --- a/src/Middleware/XmlDataResponseMiddleware.php +++ b/src/Middleware/XmlDataResponseMiddleware.php @@ -4,6 +4,7 @@ namespace Yiisoft\DataResponse\Middleware; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\XmlFormatter; /** diff --git a/src/ResponseFactory/FormattedResponseFactory.php b/src/ResponseFactory/FormattedResponseFactory.php index 112a7da..ac8271a 100644 --- a/src/ResponseFactory/FormattedResponseFactory.php +++ b/src/ResponseFactory/FormattedResponseFactory.php @@ -4,6 +4,8 @@ namespace Yiisoft\DataResponse\ResponseFactory; +use Yiisoft\DataResponse\DataStream\DataStream; + /** * Factory that creates responses with a custom formatter applied to the {@see DataStream} body * and appropriate response headers. diff --git a/src/ResponseFactory/HtmlResponseFactory.php b/src/ResponseFactory/HtmlResponseFactory.php index 97675ed..2f4b81e 100644 --- a/src/ResponseFactory/HtmlResponseFactory.php +++ b/src/ResponseFactory/HtmlResponseFactory.php @@ -5,6 +5,7 @@ namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\HtmlFormatter; /** diff --git a/src/ResponseFactory/JsonResponseFactory.php b/src/ResponseFactory/JsonResponseFactory.php index ce2a1eb..9785b4a 100644 --- a/src/ResponseFactory/JsonResponseFactory.php +++ b/src/ResponseFactory/JsonResponseFactory.php @@ -5,6 +5,7 @@ namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\JsonFormatter; /** diff --git a/src/ResponseFactory/PlainTextResponseFactory.php b/src/ResponseFactory/PlainTextResponseFactory.php index bb84826..ed6ecd5 100644 --- a/src/ResponseFactory/PlainTextResponseFactory.php +++ b/src/ResponseFactory/PlainTextResponseFactory.php @@ -5,6 +5,7 @@ namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\PlainTextFormatter; /** diff --git a/src/ResponseFactory/XmlResponseFactory.php b/src/ResponseFactory/XmlResponseFactory.php index f251856..32cf2e4 100644 --- a/src/ResponseFactory/XmlResponseFactory.php +++ b/src/ResponseFactory/XmlResponseFactory.php @@ -5,6 +5,7 @@ namespace Yiisoft\DataResponse\ResponseFactory; use Psr\Http\Message\ResponseFactoryInterface; +use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\XmlFormatter; /** From 9e144d246476f1b490dce6b9d9bb1d7b937c9a39 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:40:28 +0300 Subject: [PATCH 60/64] fix --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eeca652..b71f112 100644 --- a/README.md +++ b/README.md @@ -143,15 +143,13 @@ It wraps raw data and a formatter, and performs formatting only when the stream ```php use Yiisoft\DataResponse\DataStream\DataStream; use Yiisoft\DataResponse\Formatter\JsonFormatter; +use Yiisoft\DataResponse\Formatter\XmlFormatter; $stream = new DataStream(['key' => 'value'], new JsonFormatter()); echo (string) $stream; // {"key":"value"} -``` -You can change the data or formatter dynamically: - -```php +// You can change the data or formatter dynamically $stream->changeData(['new' => 'data']); $stream->changeFormatter(new XmlFormatter()); ``` From 7e539a8cf86c1bfd61a3eb6feced253d59ef81a0 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 11:43:57 +0300 Subject: [PATCH 61/64] fix --- src/Middleware/DataResponseMiddleware.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Middleware/DataResponseMiddleware.php b/src/Middleware/DataResponseMiddleware.php index 49bb0f0..c518fb0 100644 --- a/src/Middleware/DataResponseMiddleware.php +++ b/src/Middleware/DataResponseMiddleware.php @@ -4,6 +4,8 @@ namespace Yiisoft\DataResponse\Middleware; +use Yiisoft\DataResponse\DataStream\DataStream; + /** * Middleware that applies a custom formatter to {@see DataStream} responses * and sets appropriate response headers. From b5401f1ffae3c473b6d23cea70546fc2fdf5250e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 5 Feb 2026 20:03:57 +0300 Subject: [PATCH 62/64] use stable yiisoft/test-support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4eecd90..4e72518 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "roave/infection-static-analysis-plugin": "^1.35", "vimeo/psalm": "^5.26.1 || ^6.8.0", "yiisoft/di": "^1.4", - "yiisoft/test-support": "dev-stream" + "yiisoft/test-support": "^3.2" }, "autoload": { "psr-4": { From b0cb987914e2480e173def55e4dbce2cd24e1a99 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 6 Feb 2026 09:32:51 +0300 Subject: [PATCH 63/64] improve --- config/di-web.php | 44 ++++++++++++++++++++++---------------------- tests/ConfigTest.php | 25 +++++++++++++++++++++---- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/config/di-web.php b/config/di-web.php index ca7df8a..c008f2f 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -20,6 +20,8 @@ use Yiisoft\DataResponse\ResponseFactory\JsonResponseFactory; use Yiisoft\DataResponse\ResponseFactory\XmlResponseFactory; use Yiisoft\Definitions\DynamicReferencesArray; +use Yiisoft\Definitions\Reference; +use Yiisoft\Definitions\ReferencesArray; /* @var $params array */ @@ -32,26 +34,24 @@ ], ], DataResponseFactoryInterface::class => DataResponseFactory::class, - ContentNegotiatorDataResponseMiddleware::class - => static function (ContainerInterface $container): ContentNegotiatorDataResponseMiddleware { - return new ContentNegotiatorDataResponseMiddleware( - [ - 'text/html' => $container->get(HtmlFormatter::class), - 'application/xml' => $container->get(XmlFormatter::class), - 'application/json' => $container->get(JsonFormatter::class), - ], - fallback: $container->get(NotAcceptableRequestHandler::class), - ); - }, - ContentNegotiatorResponseFactory::class - => static function (ContainerInterface $container): ContentNegotiatorResponseFactory { - return new ContentNegotiatorResponseFactory( - [ - 'text/html' => $container->get(HtmlResponseFactory::class), - 'application/xml' => $container->get(XmlResponseFactory::class), - 'application/json' => $container->get(JsonResponseFactory::class), - ], - $container->get(NotAcceptableRequestHandler::class), - ); - }, + ContentNegotiatorDataResponseMiddleware::class => [ + '__construct()' => [ + 'formatters' => ReferencesArray::from([ + 'text/html' => HtmlFormatter::class, + 'application/xml' => XmlFormatter::class, + 'application/json' => JsonFormatter::class, + ]), + 'fallback' => Reference::to(NotAcceptableRequestHandler::class), + ], + ], + ContentNegotiatorResponseFactory::class => [ + '__construct()' => [ + 'factories' => DynamicReferencesArray::from([ + 'text/html' => HtmlResponseFactory::class, + 'application/xml' => XmlResponseFactory::class, + 'application/json' => JsonResponseFactory::class, + ]), + 'fallback' => Reference::to(NotAcceptableRequestHandler::class), + ] + ], ]; diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index d89e7d9..40ca3b5 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -6,11 +6,15 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; -use Yiisoft\DataResponse\DataResponseFactory; -use Yiisoft\DataResponse\DataResponseFactoryInterface; +use Yiisoft\DataResponse\DataResponseFactory as DeprecatedDataResponseFactory; +use Yiisoft\DataResponse\DataResponseFactoryInterface as DeprecatedDataResponseFactoryInterface; use Yiisoft\DataResponse\DataResponseFormatterInterface; use Yiisoft\DataResponse\Formatter\HtmlDataResponseFormatter; use Yiisoft\DataResponse\Middleware\ContentNegotiator; +use Yiisoft\DataResponse\Middleware\ContentNegotiatorDataResponseMiddleware; +use Yiisoft\DataResponse\ResponseFactory\ContentNegotiatorResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\DataResponseFactory; +use Yiisoft\DataResponse\ResponseFactory\DataResponseFactoryInterface; use Yiisoft\Di\Container; use Yiisoft\Di\ContainerConfig; use PHPUnit\Framework\TestCase; @@ -23,12 +27,25 @@ public function testDiWeb(): void { $container = $this->createContainer('web'); - $dataResponseFormatter = $container->get(DataResponseFormatterInterface::class); $dataResponseFactory = $container->get(DataResponseFactoryInterface::class); + $contentNegotiatorDataResponseMiddleware = $container->get(ContentNegotiatorDataResponseMiddleware::class); + $contentNegotiatorResponseFactory = $container->get(ContentNegotiatorResponseFactory::class); + + $this->assertInstanceOf(DataResponseFactory::class, $dataResponseFactory); + $this->assertInstanceOf(ContentNegotiatorDataResponseMiddleware::class, $contentNegotiatorDataResponseMiddleware); + $this->assertInstanceOf(ContentNegotiatorResponseFactory::class, $contentNegotiatorResponseFactory); + } + + public function testDiWebDeprecated(): void + { + $container = $this->createContainer('web'); + + $dataResponseFormatter = $container->get(DataResponseFormatterInterface::class); + $dataResponseFactory = $container->get(DeprecatedDataResponseFactoryInterface::class); $contentNegotiator = $container->get(ContentNegotiator::class); $this->assertInstanceOf(HtmlDataResponseFormatter::class, $dataResponseFormatter); - $this->assertInstanceOf(DataResponseFactory::class, $dataResponseFactory); + $this->assertInstanceOf(DeprecatedDataResponseFactory::class, $dataResponseFactory); $this->assertInstanceOf(ContentNegotiator::class, $contentNegotiator); } From a5704a3a93cbc783a2ae747e6cc934a0b223e8a4 Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:33:35 +0000 Subject: [PATCH 64/64] Apply PHP CS Fixer and Rector changes (CI) --- config/di-web.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/di-web.php b/config/di-web.php index c008f2f..660f8b0 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use Psr\Container\ContainerInterface; use Yiisoft\DataResponse\DataResponseFactory as DeprecatedDataResponseFactory; use Yiisoft\DataResponse\DataResponseFactoryInterface as DeprecatedDataResponseFactoryInterface; use Yiisoft\DataResponse\DataResponseFormatterInterface; @@ -52,6 +51,6 @@ 'application/json' => JsonResponseFactory::class, ]), 'fallback' => Reference::to(NotAcceptableRequestHandler::class), - ] + ], ], ];