Skip to content

Commit 0162cb0

Browse files
committed
Add extra responses
1 parent eab1db7 commit 0162cb0

9 files changed

+279
-18
lines changed

src/EmptyResponse.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of php-fast-forward/http-message.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @link https://github.com/php-fast-forward/http-message
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Http\Message;
17+
18+
use Nyholm\Psr7\Response;
19+
20+
/**
21+
* Class EmptyResponse.
22+
*
23+
* Represents an HTTP 204 No Content response.
24+
*
25+
* This class MUST be used when generating responses that intentionally contain no body content,
26+
* in compliance with RFC 9110. It automatically sets the HTTP status code to 204 (No Content)
27+
* and applies an optional set of headers.
28+
*
29+
* @package FastForward\Http\Message
30+
*/
31+
final class EmptyResponse extends Response
32+
{
33+
/**
34+
* Constructs a new EmptyResponse instance with optional headers.
35+
*
36+
* This constructor SHALL initialize the response with HTTP status 204 and no body content.
37+
* The 'reason' phrase for status 204 is automatically included based on StatusCode enumeration.
38+
*
39+
* @param array<string, string|string[]> $headers optional headers to include in the response
40+
*/
41+
public function __construct(array $headers = [])
42+
{
43+
parent::__construct(
44+
status: StatusCode::NoContent->value,
45+
headers: $headers,
46+
reason: StatusCode::NoContent->getReasonPhrase(),
47+
);
48+
}
49+
}

src/JsonResponseInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
*
2626
* This interface SHALL be used to identify responses intended to transmit JSON-encoded payloads with proper headers.
2727
*/
28-
interface JsonResponseInterface extends ResponseInterface, PayloadAwareInterface {}
28+
interface JsonResponseInterface extends ResponseInterface, PayloadAwareInterface, PayloadImmutableInterface {}

src/JsonStream.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ final class JsonStream extends Stream implements JsonStreamInterface
4747
private mixed $payload = [];
4848

4949
/**
50-
* @var int The JSON encoding options to be applied. Defaults to self::ENCODING_OPTIONS.
50+
* @var int the JSON encoding options to be applied
5151
*/
52-
private int $encodingOptions = self::ENCODING_OPTIONS;
52+
private int $encodingOptions;
5353

5454
/**
5555
* Constructs a new JsonStream instance with the provided payload.
@@ -95,7 +95,7 @@ public function getPayload(): mixed
9595
*/
9696
public function withPayload(mixed $payload): self
9797
{
98-
return new self($payload);
98+
return new self($payload, $this->encodingOptions);
9999
}
100100

101101
/**

src/JsonStreamInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
*
2626
* @package FastForward\Http\Message
2727
*/
28-
interface JsonStreamInterface extends StreamInterface, PayloadAwareInterface {}
28+
interface JsonStreamInterface extends StreamInterface, PayloadAwareInterface, PayloadImmutableInterface {}

src/PayloadAwareInterface.php

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,4 @@ interface PayloadAwareInterface
3535
* @return mixed the payload, which MAY be of any type including array, object, scalar, or null
3636
*/
3737
public function getPayload(): mixed;
38-
39-
/**
40-
* Returns a new instance with the specified payload.
41-
*
42-
* This method MUST NOT modify the current instance. It SHALL return a new instance with the updated payload.
43-
* The payload MAY be of any type supported by the implementation. Implementations MAY throw exceptions if
44-
* constraints on the payload type are violated.
45-
*
46-
* @param mixed $payload the new payload to set in the instance
47-
*
48-
* @return self a new instance with the updated payload
49-
*/
50-
public function withPayload(mixed $payload): self;
5138
}

src/PayloadImmutableInterface.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of php-fast-forward/http-message.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @link https://github.com/php-fast-forward/http-message
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Http\Message;
17+
18+
/**
19+
* Interface PayloadAwareInterface.
20+
*
21+
* Defines functionality for objects that encapsulate and manage a payload.
22+
* Implementations of this interface MUST provide immutable methods for accessing and replacing the payload.
23+
* The payload MAY be of any type supported by the implementation, including arrays, objects, scalars, or null.
24+
*
25+
* @package FastForward\Http\Message
26+
*
27+
* @internal
28+
*/
29+
interface PayloadImmutableInterface
30+
{
31+
/**
32+
* Returns a new instance with the specified payload.
33+
*
34+
* This method MUST NOT modify the current instance. It SHALL return a new instance with the updated payload.
35+
* The payload MAY be of any type supported by the implementation. Implementations MAY throw exceptions if
36+
* constraints on the payload type are violated.
37+
*
38+
* @param mixed $payload the new payload to set in the instance
39+
*
40+
* @return self a new instance with the updated payload
41+
*/
42+
public function withPayload(mixed $payload): self;
43+
}

src/RedirectResponse.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of php-fast-forward/http-message.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @link https://github.com/php-fast-forward/http-message
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Http\Message;
17+
18+
use Nyholm\Psr7\Response;
19+
use Psr\Http\Message\UriInterface;
20+
21+
/**
22+
* Class RedirectResponse.
23+
*
24+
* Represents an HTTP redirect response with customizable status codes for temporary or permanent redirects.
25+
* This class MUST be used for generating HTTP responses that instruct clients to navigate to a different location,
26+
* by automatically setting the 'Location' header.
27+
*
28+
* @package FastForward\Http\Message
29+
*/
30+
final class RedirectResponse extends Response
31+
{
32+
/**
33+
* Constructs a new RedirectResponse instance.
34+
*
35+
* This constructor SHALL set the 'Location' header and apply the appropriate HTTP status code
36+
* for temporary (302 Found) or permanent (301 Moved Permanently) redirects.
37+
*
38+
* @param string|UriInterface $uri The target URI for redirection. MUST be absolute or relative according to context.
39+
* @param bool $permanent if true, the response status will be 301 (permanent redirect); otherwise, 302 (temporary redirect)
40+
* @param array<string, string|string[]> $headers optional additional headers to include in the response
41+
*/
42+
public function __construct(string|UriInterface $uri, bool $permanent = false, array $headers = [])
43+
{
44+
$headers['Location'] = (string) $uri;
45+
$status = $permanent ? StatusCode::MovedPermanently : StatusCode::Found;
46+
47+
parent::__construct(
48+
status: $status->value,
49+
headers: $headers,
50+
reason: $status->getReasonPhrase(),
51+
);
52+
}
53+
}

tests/EmptyResponseTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of php-fast-forward/http-message.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @link https://github.com/php-fast-forward/http-message
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Http\Message\Tests;
17+
18+
use FastForward\Http\Message\EmptyResponse;
19+
use FastForward\Http\Message\StatusCode;
20+
use PHPUnit\Framework\Attributes\CoversClass;
21+
use PHPUnit\Framework\Attributes\UsesClass;
22+
use PHPUnit\Framework\TestCase;
23+
24+
/**
25+
* @internal
26+
*/
27+
#[CoversClass(EmptyResponse::class)]
28+
#[UsesClass(StatusCode::class)]
29+
final class EmptyResponseTest extends TestCase
30+
{
31+
public function testConstructorWillSetNoContentStatusAndEmptyBody(): void
32+
{
33+
$response = new EmptyResponse();
34+
35+
self::assertSame(StatusCode::NoContent->value, $response->getStatusCode());
36+
self::assertSame(StatusCode::NoContent->getReasonPhrase(), $response->getReasonPhrase());
37+
self::assertSame('', (string) $response->getBody());
38+
}
39+
40+
public function testConstructorWillPreserveAdditionalHeaders(): void
41+
{
42+
$headers = [
43+
'X-Test-Header' => 'value',
44+
'X-Multiple' => ['one', 'two'],
45+
];
46+
47+
$response = new EmptyResponse($headers);
48+
49+
self::assertSame('value', $response->getHeaderLine('X-Test-Header'));
50+
self::assertSame('one, two', $response->getHeaderLine('X-Multiple'));
51+
self::assertSame(StatusCode::NoContent->value, $response->getStatusCode());
52+
self::assertSame('', (string) $response->getBody());
53+
}
54+
}

tests/RedirectResponseTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of php-fast-forward/http-message.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @link https://github.com/php-fast-forward/http-message
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Http\Message\Tests;
17+
18+
use FastForward\Http\Message\RedirectResponse;
19+
use FastForward\Http\Message\StatusCode;
20+
use Nyholm\Psr7\Uri;
21+
use PHPUnit\Framework\Attributes\CoversClass;
22+
use PHPUnit\Framework\Attributes\UsesClass;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* @internal
27+
*/
28+
#[CoversClass(RedirectResponse::class)]
29+
#[UsesClass(StatusCode::class)]
30+
final class RedirectResponseTest extends TestCase
31+
{
32+
public function testConstructorWillSetLocationHeaderAndTemporaryStatus(): void
33+
{
34+
$uri = 'https://example.com';
35+
36+
$response = new RedirectResponse($uri);
37+
38+
self::assertSame(StatusCode::Found->value, $response->getStatusCode());
39+
self::assertSame(StatusCode::Found->getReasonPhrase(), $response->getReasonPhrase());
40+
self::assertSame('https://example.com', $response->getHeaderLine('Location'));
41+
}
42+
43+
public function testConstructorWillSetLocationHeaderAndPermanentStatus(): void
44+
{
45+
$uri = 'https://example.com/redirect';
46+
47+
$response = new RedirectResponse($uri, permanent: true);
48+
49+
self::assertSame(StatusCode::MovedPermanently->value, $response->getStatusCode());
50+
self::assertSame(StatusCode::MovedPermanently->getReasonPhrase(), $response->getReasonPhrase());
51+
self::assertSame('https://example.com/redirect', $response->getHeaderLine('Location'));
52+
}
53+
54+
public function testConstructorAcceptsUriInterfaceInstance(): void
55+
{
56+
$uri = new Uri('/relative/path');
57+
58+
$response = new RedirectResponse($uri);
59+
60+
self::assertSame('/relative/path', $response->getHeaderLine('Location'));
61+
}
62+
63+
public function testConstructorWillPreserveAdditionalHeaders(): void
64+
{
65+
$uri = 'https://example.com';
66+
$headers = [
67+
'X-Custom-Header' => 'test-value',
68+
];
69+
70+
$response = new RedirectResponse($uri, permanent: false, headers: $headers);
71+
72+
self::assertSame('test-value', $response->getHeaderLine('X-Custom-Header'));
73+
self::assertSame('https://example.com', $response->getHeaderLine('Location'));
74+
}
75+
}

0 commit comments

Comments
 (0)