Skip to content

Commit c7c71bd

Browse files
authored
Release/3.0.0 - (#22)
1 parent 1605548 commit c7c71bd

File tree

10 files changed

+141
-42
lines changed

10 files changed

+141
-42
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,16 @@
4141
}
4242
},
4343
"require": {
44-
"php": "^8.1||^8.2",
44+
"php": "^8.2",
4545
"tiny-blocks/serializer": "^2.0",
4646
"psr/http-message": "^1.1",
4747
"ext-mbstring": "*"
4848
},
4949
"require-dev": {
5050
"infection/infection": "^0.27",
51-
"phpmd/phpmd": "^2.13",
51+
"phpmd/phpmd": "^2.15",
5252
"phpunit/phpunit": "^9.6",
53-
"squizlabs/php_codesniffer": "^3.7"
53+
"squizlabs/php_codesniffer": "^3.8"
5454
},
5555
"suggest": {
5656
"ext-mbstring": "Provides multibyte-specific string functions that help us deal with multibyte encodings in PHP."

src/HttpHeaders.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,20 @@ final class HttpHeaders
1313
{
1414
private array $values = [];
1515

16-
private function __construct()
16+
public static function build(): HttpHeaders
1717
{
18+
return new HttpHeaders();
1819
}
1920

20-
public static function build(): HttpHeaders
21+
public function addFromCode(HttpCode $code): HttpHeaders
2122
{
22-
return new HttpHeaders();
23+
$template = 'HTTP/1.1 %s';
24+
$this->values['Status'][] = sprintf($template, $code->message());
25+
26+
return $this;
2327
}
2428

25-
public function add(Header $header): HttpHeaders
29+
public function addFromContentType(Header $header): HttpHeaders
2630
{
2731
$this->values[$header->key()][] = $header->value();
2832

@@ -34,9 +38,9 @@ public function getHeader(string $key): array
3438
return $this->values[$key] ?? [];
3539
}
3640

37-
public function hasHeaders(): bool
41+
public function hasNoHeaders(): bool
3842
{
39-
return !empty($this->values);
43+
return empty($this->values);
4044
}
4145

4246
public function hasHeader(string $key): bool

src/HttpResponse.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
*/
1313
final class HttpResponse
1414
{
15+
# Successful (200 – 299)
16+
1517
public static function ok(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
1618
{
1719
return Response::from(code: HttpCode::OK, data: $data, headers: $headers);
@@ -31,4 +33,28 @@ public static function noContent(?HttpHeaders $headers = null): ResponseInterfac
3133
{
3234
return Response::from(code: HttpCode::NO_CONTENT, data: null, headers: $headers);
3335
}
36+
37+
# Client error (400 – 499)
38+
39+
public static function badRequest(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
40+
{
41+
return Response::from(code: HttpCode::BAD_REQUEST, data: $data, headers: $headers);
42+
}
43+
44+
public static function notFound(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
45+
{
46+
return Response::from(code: HttpCode::NOT_FOUND, data: $data, headers: $headers);
47+
}
48+
49+
public static function conflict(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
50+
{
51+
return Response::from(code: HttpCode::CONFLICT, data: $data, headers: $headers);
52+
}
53+
54+
# Server error (500 – 599)
55+
56+
public static function internalServerError(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
57+
{
58+
return Response::from(code: HttpCode::INTERNAL_SERVER_ERROR, data: $data, headers: $headers);
59+
}
3460
}

src/Internal/Header.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@
44

55
interface Header
66
{
7+
/**
8+
* Get the key of the header.
9+
*
10+
* @return string The key of the header.
11+
*/
712
public function key(): string;
813

14+
/**
15+
* Get the value of the header.
16+
*
17+
* @return string The value of the header.
18+
*/
919
public function value(): string;
1020
}

src/Internal/Response.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@
1111
use TinyBlocks\Http\Internal\Exceptions\BadMethodCall;
1212
use TinyBlocks\Http\Internal\Stream\StreamFactory;
1313

14-
final class Response implements ResponseInterface
14+
final readonly class Response implements ResponseInterface
1515
{
16-
private function __construct(
17-
private readonly HttpCode $code,
18-
private readonly StreamInterface $body,
19-
private readonly HttpHeaders $headers
20-
) {
16+
private function __construct(private HttpCode $code, private StreamInterface $body, private HttpHeaders $headers)
17+
{
2118
}
2219

2320
public static function from(HttpCode $code, mixed $data, ?HttpHeaders $headers): ResponseInterface
2421
{
25-
if (is_null($headers) || !$headers->hasHeaders()) {
26-
$headers = HttpHeaders::build()->add(header: HttpContentType::APPLICATION_JSON);
22+
if (is_null($headers) || $headers->hasNoHeaders()) {
23+
$headers = HttpHeaders::build()
24+
->addFromCode(code: $code)
25+
->addFromContentType(header: HttpContentType::APPLICATION_JSON);
2726
}
2827

2928
return new Response(code: $code, body: StreamFactory::from(data: $data), headers: $headers);

src/Internal/Stream/StreamMetaData.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace TinyBlocks\Http\Internal\Stream;
44

5-
final class StreamMetaData
5+
final readonly class StreamMetaData
66
{
77
public function __construct(
8-
private readonly string $uri,
9-
private readonly string $mode,
10-
private readonly bool $seekable,
11-
private readonly string $streamType
8+
private string $uri,
9+
private string $mode,
10+
private bool $seekable,
11+
private string $streamType
1212
) {
1313
}
1414

tests/HttpResponseTest.php

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@
88

99
class HttpResponseTest extends TestCase
1010
{
11-
private array $defaultHeader;
12-
13-
protected function setUp(): void
14-
{
15-
$this->defaultHeader = ['Content-Type' => [HttpContentType::APPLICATION_JSON->value]];
16-
}
17-
1811
/**
1912
* @dataProvider providerData
2013
*/
@@ -26,7 +19,7 @@ public function testResponseOk(mixed $data, mixed $expected): void
2619
self::assertEquals($expected, $response->getBody()->getContents());
2720
self::assertEquals(HttpCode::OK->value, $response->getStatusCode());
2821
self::assertEquals(HttpCode::OK->message(), $response->getReasonPhrase());
29-
self::assertEquals($this->defaultHeader, $response->getHeaders());
22+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::OK), $response->getHeaders());
3023
}
3124

3225
/**
@@ -40,7 +33,7 @@ public function testResponseCreated(mixed $data, mixed $expected): void
4033
self::assertEquals($expected, $response->getBody()->getContents());
4134
self::assertEquals(HttpCode::CREATED->value, $response->getStatusCode());
4235
self::assertEquals(HttpCode::CREATED->message(), $response->getReasonPhrase());
43-
self::assertEquals($this->defaultHeader, $response->getHeaders());
36+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::CREATED), $response->getHeaders());
4437
}
4538

4639
/**
@@ -54,7 +47,7 @@ public function testResponseAccepted(mixed $data, mixed $expected): void
5447
self::assertEquals($expected, $response->getBody()->getContents());
5548
self::assertEquals(HttpCode::ACCEPTED->value, $response->getStatusCode());
5649
self::assertEquals(HttpCode::ACCEPTED->message(), $response->getReasonPhrase());
57-
self::assertEquals($this->defaultHeader, $response->getHeaders());
50+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::ACCEPTED), $response->getHeaders());
5851
}
5952

6053
public function testResponseNoContent(): void
@@ -65,7 +58,63 @@ public function testResponseNoContent(): void
6558
self::assertEquals('', $response->getBody()->getContents());
6659
self::assertEquals(HttpCode::NO_CONTENT->value, $response->getStatusCode());
6760
self::assertEquals(HttpCode::NO_CONTENT->message(), $response->getReasonPhrase());
68-
self::assertEquals($this->defaultHeader, $response->getHeaders());
61+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::NO_CONTENT), $response->getHeaders());
62+
}
63+
64+
/**
65+
* @dataProvider providerData
66+
*/
67+
public function testResponseBadRequest(mixed $data, mixed $expected): void
68+
{
69+
$response = HttpResponse::badRequest(data: $data);
70+
71+
self::assertEquals($expected, $response->getBody()->__toString());
72+
self::assertEquals($expected, $response->getBody()->getContents());
73+
self::assertEquals(HttpCode::BAD_REQUEST->value, $response->getStatusCode());
74+
self::assertEquals(HttpCode::BAD_REQUEST->message(), $response->getReasonPhrase());
75+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::BAD_REQUEST), $response->getHeaders());
76+
}
77+
78+
/**
79+
* @dataProvider providerData
80+
*/
81+
public function testResponseNotFound(mixed $data, mixed $expected): void
82+
{
83+
$response = HttpResponse::notFound(data: $data);
84+
85+
self::assertEquals($expected, $response->getBody()->__toString());
86+
self::assertEquals($expected, $response->getBody()->getContents());
87+
self::assertEquals(HttpCode::NOT_FOUND->value, $response->getStatusCode());
88+
self::assertEquals(HttpCode::NOT_FOUND->message(), $response->getReasonPhrase());
89+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::NOT_FOUND), $response->getHeaders());
90+
}
91+
92+
/**
93+
* @dataProvider providerData
94+
*/
95+
public function testResponseConflict(mixed $data, mixed $expected): void
96+
{
97+
$response = HttpResponse::conflict(data: $data);
98+
99+
self::assertEquals($expected, $response->getBody()->__toString());
100+
self::assertEquals($expected, $response->getBody()->getContents());
101+
self::assertEquals(HttpCode::CONFLICT->value, $response->getStatusCode());
102+
self::assertEquals(HttpCode::CONFLICT->message(), $response->getReasonPhrase());
103+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::CONFLICT), $response->getHeaders());
104+
}
105+
106+
/**
107+
* @dataProvider providerData
108+
*/
109+
public function testResponseInternalServerError(mixed $data, mixed $expected): void
110+
{
111+
$response = HttpResponse::internalServerError(data: $data);
112+
113+
self::assertEquals($expected, $response->getBody()->__toString());
114+
self::assertEquals($expected, $response->getBody()->getContents());
115+
self::assertEquals(HttpCode::INTERNAL_SERVER_ERROR->value, $response->getStatusCode());
116+
self::assertEquals(HttpCode::INTERNAL_SERVER_ERROR->message(), $response->getReasonPhrase());
117+
self::assertEquals($this->defaultHeaderFrom(code: HttpCode::INTERNAL_SERVER_ERROR), $response->getHeaders());
69118
}
70119

71120
public function providerData(): array
@@ -97,4 +146,12 @@ public function providerData(): array
97146
]
98147
];
99148
}
149+
150+
private function defaultHeaderFrom(HttpCode $code): array
151+
{
152+
return [
153+
'Status' => [sprintf('HTTP/1.1 %s', $code->message())],
154+
'Content-Type' => [HttpContentType::APPLICATION_JSON->value]
155+
];
156+
}
100157
}

tests/Internal/HeadersTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class HeadersTest extends TestCase
1010
{
1111
public function testAddAndGetValues(): void
1212
{
13-
$headers = HttpHeaders::build()->add(header: HttpContentType::APPLICATION_JSON);
13+
$headers = HttpHeaders::build()->addFromContentType(header: HttpContentType::APPLICATION_JSON);
1414
$expected = ['Content-Type' => [HttpContentType::APPLICATION_JSON->value]];
1515

1616
self::assertEquals($expected, $headers->toArray());
@@ -19,8 +19,8 @@ public function testAddAndGetValues(): void
1919
public function testAddAndGetUniqueValues(): void
2020
{
2121
$headers = HttpHeaders::build()
22-
->add(header: HttpContentType::TEXT_HTML)
23-
->add(header: HttpContentType::APPLICATION_PDF);
22+
->addFromContentType(header: HttpContentType::TEXT_HTML)
23+
->addFromContentType(header: HttpContentType::APPLICATION_PDF);
2424
$expected = ['Content-Type' => [HttpContentType::APPLICATION_PDF->value]];
2525

2626
self::assertEquals($expected, $headers->toArray());

tests/Internal/ResponseTest.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ class ResponseTest extends TestCase
1414
public function testDefaultHeaders(): void
1515
{
1616
$response = Response::from(code: HttpCode::OK, data: [], headers: null);
17-
$expected = ['Content-Type' => [HttpContentType::APPLICATION_JSON->value]];
17+
$expected = [
18+
'Status' => [sprintf('HTTP/1.1 %s', HttpCode::OK->message())],
19+
'Content-Type' => [HttpContentType::APPLICATION_JSON->value]
20+
];
1821

1922
self::assertEquals($expected, $response->getHeaders());
2023
}
@@ -28,7 +31,7 @@ public function testGetProtocolVersion(): void
2831

2932
public function testGetHeaders(): void
3033
{
31-
$headers = HttpHeaders::build()->add(header: HttpContentType::APPLICATION_JSON);
34+
$headers = HttpHeaders::build()->addFromContentType(header: HttpContentType::APPLICATION_JSON);
3235
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);
3336
$expected = [HttpContentType::APPLICATION_JSON->value];
3437

@@ -38,7 +41,7 @@ public function testGetHeaders(): void
3841

3942
public function testHasHeader(): void
4043
{
41-
$headers = HttpHeaders::build()->add(header: HttpContentType::TEXT_PLAIN);
44+
$headers = HttpHeaders::build()->addFromContentType(header: HttpContentType::TEXT_PLAIN);
4245
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);
4346
$expected = [HttpContentType::TEXT_PLAIN->value];
4447

@@ -48,7 +51,7 @@ public function testHasHeader(): void
4851

4952
public function testGetHeaderLine(): void
5053
{
51-
$headers = HttpHeaders::build()->add(header: HttpContentType::APPLICATION_JSON);
54+
$headers = HttpHeaders::build()->addFromContentType(header: HttpContentType::APPLICATION_JSON);
5255
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);
5356

5457
self::assertEquals(HttpContentType::APPLICATION_JSON->value, $response->getHeaderLine(name: 'Content-Type'));

tests/Mock/Xyz.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace TinyBlocks\Http\Mock;
44

5-
final class Xyz
5+
final readonly class Xyz
66
{
7-
public function __construct(public readonly int $value)
7+
public function __construct(public int $value)
88
{
99
}
1010
}

0 commit comments

Comments
 (0)