Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds a wrapper that implements PSR-7 for HTTP responses. #7

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ echo $method->name; # GET
echo $method->value; # GET
```

### Using the HttpResponse

The library exposes a concrete implementation for HTTP responses via the `HttpResponse` class. Responses are of the
[ResponseInterface](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php) type, according to
the specifications defined in [PSR-7](https://www.php-fig.org/psr/psr-7).

```php
$data = new Xyz(value: 10);
$response = HttpResponse::ok(data: $data);

$response->getStatusCode(); # 200
$response->getReasonPhrase(); # 200 Ok
$response->getBody()->getContents(); # {"value":10}
```

## License

Math is licensed under [MIT](/LICENSE).
Expand Down
17 changes: 11 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
"keywords": [
"psr",
"psr-4",
"psr-7",
"psr-12",
"http",
"http-code",
"tiny-blocks",
"http-status",
"tiny-blocks"
"http-methods",
"http-response"
],
"authors": [
{
Expand All @@ -39,20 +42,22 @@
},
"require": {
"php": "^8.1||^8.2",
"tiny-blocks/serializer": "1.*",
"psr/http-message": "2.*",
"ext-mbstring": "*"
},
"require-dev": {
"infection/infection": "^0.26",
"phpmd/phpmd": "^2.12",
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.7"
"infection/infection": "0.*",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "9.*",
"squizlabs/php_codesniffer": "3.*"
},
"suggest": {
"ext-mbstring": "Provides multibyte-specific string functions that help us deal with multibyte encodings in PHP."
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --exclude /src/HttpCode.php --ignore-violations-on-exit",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --exclude /src/HttpCode.php --exclude /src/Internal/Response --ignore-violations-on-exit",
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
"test-no-coverage": "phpunit --no-coverage",
Expand Down
10 changes: 9 additions & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
"summary": "report/logs/infection-summary.log"
},
"mutators": {
"@default": true
"@default": true,
"ArrayItem": false,
"LogicalOr": false,
"IfNegation": false,
"InstanceOf_": false,
"ArrayItemRemoval": false,
"MethodCallRemoval": false,
"LogicalOrAllSubExprNegation": false,
"LogicalOrSingleSubExprNegation": false
},
"phpUnit": {
"configDir": "",
Expand Down
4 changes: 4 additions & 0 deletions src/HttpCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
* Responses are grouped in five classes:
*
* Informational (100 – 199)
*
* Successful (200 – 299)
*
* Redirection (300 – 399)
*
* Client error (400 – 499)
*
* Server error (500 – 599)
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#information_responses
Expand Down
34 changes: 34 additions & 0 deletions src/HttpResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace TinyBlocks\Http;

use Psr\Http\Message\ResponseInterface;
use TinyBlocks\Http\Internal\Response;

/**
* Define HTTP response following the PSR7 specification.
*
* @see https://www.php-fig.org/psr/psr-7
*/
final class HttpResponse
{
public static function ok(mixed $data, array $headers = []): ResponseInterface
{
return Response::from(code: HttpCode::OK, data: $data, headers: $headers);
}

public static function created(mixed $data, array $headers = []): ResponseInterface
{
return Response::from(code: HttpCode::CREATED, data: $data, headers: $headers);
}

public static function accepted(mixed $data, array $headers = []): ResponseInterface
{
return Response::from(code: HttpCode::ACCEPTED, data: $data, headers: $headers);
}

public static function noContent(array $headers = []): ResponseInterface
{
return Response::from(code: HttpCode::NO_CONTENT, data: null, headers: $headers);
}
}
14 changes: 14 additions & 0 deletions src/Internal/Exceptions/BadMethodCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace TinyBlocks\Http\Internal\Exceptions;

use BadMethodCallException;

final class BadMethodCall extends BadMethodCallException
{
public function __construct(private readonly string $method)
{
$template = 'Method <%s> cannot be used.';
parent::__construct(message: sprintf($template, $this->method));
}
}
13 changes: 13 additions & 0 deletions src/Internal/Exceptions/MissingResourceStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace TinyBlocks\Http\Internal\Exceptions;

use RuntimeException;

final class MissingResourceStream extends RuntimeException
{
public function __construct()
{
parent::__construct(message: 'No resource available.');
}
}
13 changes: 13 additions & 0 deletions src/Internal/Exceptions/NonReadableStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace TinyBlocks\Http\Internal\Exceptions;

use RuntimeException;

final class NonReadableStream extends RuntimeException
{
public function __construct()
{
parent::__construct(message: 'Stream is not readable.');
}
}
13 changes: 13 additions & 0 deletions src/Internal/Exceptions/NonSeekableStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace TinyBlocks\Http\Internal\Exceptions;

use RuntimeException;

final class NonSeekableStream extends RuntimeException
{
public function __construct()
{
parent::__construct(message: 'Stream is not seekable.');
}
}
13 changes: 13 additions & 0 deletions src/Internal/Exceptions/NonWritableStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace TinyBlocks\Http\Internal\Exceptions;

use RuntimeException;

final class NonWritableStream extends RuntimeException
{
public function __construct()
{
parent::__construct(message: 'Stream is not writable.');
}
}
97 changes: 97 additions & 0 deletions src/Internal/Response.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace TinyBlocks\Http\Internal;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use TinyBlocks\Http\HttpCode;
use TinyBlocks\Http\Internal\Exceptions\BadMethodCall;
use TinyBlocks\Http\Internal\Stream\StreamFactory;

final class Response implements ResponseInterface
{
private function __construct(
private readonly HttpCode $code,
private readonly StreamInterface $body,
private readonly array $headers
) {
}

public static function from(HttpCode $code, mixed $data, array $headers): ResponseInterface
{
$headers = empty($headers) ? ['Content-Type' => 'application/json'] : $headers;

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

public function withBody(StreamInterface $body): MessageInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function withHeader(string $name, mixed $value): MessageInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function withoutHeader(string $name): MessageInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function withAddedHeader(string $name, mixed $value): MessageInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function withProtocolVersion(string $version): MessageInterface
{
throw new BadMethodCall(method: __METHOD__);
}

public function getProtocolVersion(): string
{
return '1.1';
}

public function getHeaders(): array
{
return $this->headers;
}

public function hasHeader(string $name): bool
{
return isset($this->headers[$name]);
}

public function getHeader(string $name): array
{
return $this->headers[$name] ?? [];
}

public function getHeaderLine(string $name): string
{
return implode(', ', $this->getHeader(name: $name));
}

public function getBody(): StreamInterface
{
return $this->body;
}

public function getStatusCode(): int
{
return $this->code->value;
}

public function getReasonPhrase(): string
{
return $this->code->message();
}
}
Loading