feat(cloud): Http Request/Response primitives
This commit is contained in:
parent
7246e12c4a
commit
a0db1e06ca
3 changed files with 82 additions and 0 deletions
32
cloud/src/Http/Request.php
Normal file
32
cloud/src/Http/Request.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Http;
|
||||
|
||||
final class Request
|
||||
{
|
||||
private array $headers;
|
||||
public function __construct(
|
||||
private string $method, private string $path, array $headers,
|
||||
private string $body, private array $query = [], private array $post = []
|
||||
) {
|
||||
$this->headers = [];
|
||||
foreach ($headers as $k => $v) $this->headers[strtolower($k)] = $v;
|
||||
}
|
||||
public function method(): string { return $this->method; }
|
||||
public function path(): string { return $this->path; }
|
||||
public function header(string $name): ?string { return $this->headers[strtolower($name)] ?? null; }
|
||||
public function body(): string { return $this->body; }
|
||||
public function json(): array { $d = json_decode($this->body, true); return is_array($d) ? $d : []; }
|
||||
public function query(string $k): ?string { return isset($this->query[$k]) ? (string)$this->query[$k] : null; }
|
||||
public function post(string $k): ?string { return isset($this->post[$k]) ? (string)$this->post[$k] : null; }
|
||||
|
||||
public static function fromGlobals(): self
|
||||
{
|
||||
$headers = function_exists('getallheaders') ? (getallheaders() ?: []) : [];
|
||||
return new self(
|
||||
$_SERVER['REQUEST_METHOD'] ?? 'GET',
|
||||
parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/',
|
||||
$headers, file_get_contents('php://input') ?: '', $_GET, $_POST
|
||||
);
|
||||
}
|
||||
}
|
||||
29
cloud/src/Http/Response.php
Normal file
29
cloud/src/Http/Response.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Http;
|
||||
|
||||
final class Response
|
||||
{
|
||||
private function __construct(private int $status, private array $headers, private string $body) {}
|
||||
|
||||
public static function json(array $data, int $status = 200): self {
|
||||
return new self($status, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
public static function text(string $s, int $status = 200): self {
|
||||
return new self($status, ['Content-Type' => 'text/html; charset=utf-8'], $s);
|
||||
}
|
||||
public static function bytes(string $b, string $contentType, int $status = 200): self {
|
||||
return new self($status, ['Content-Type' => $contentType], $b);
|
||||
}
|
||||
public static function redirect(string $to): self {
|
||||
return new self(302, ['Location' => $to], '');
|
||||
}
|
||||
public function status(): int { return $this->status; }
|
||||
public function headers(): array { return $this->headers; }
|
||||
public function body(): string { return $this->body; }
|
||||
public function send(): void {
|
||||
http_response_code($this->status);
|
||||
foreach ($this->headers as $k => $v) header("$k: $v");
|
||||
echo $this->body;
|
||||
}
|
||||
}
|
||||
21
cloud/tests/HttpTest.php
Normal file
21
cloud/tests/HttpTest.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Tests;
|
||||
use App\Http\{Request, Response};
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class HttpTest extends TestCase
|
||||
{
|
||||
public function testRequestJsonAndHeaderCaseInsensitive(): void {
|
||||
$r = new Request('POST', '/api/events', ['X-Nonce' => 'n1'], '{"a":1}', [], []);
|
||||
$this->assertSame('POST', $r->method());
|
||||
$this->assertSame('n1', $r->header('x-nonce'));
|
||||
$this->assertSame(['a' => 1], $r->json());
|
||||
}
|
||||
public function testResponseJson(): void {
|
||||
$res = Response::json(['ok' => true], 201);
|
||||
$this->assertSame(201, $res->status());
|
||||
$this->assertStringContainsString('application/json', $res->headers()['Content-Type']);
|
||||
$this->assertSame('{"ok":true}', $res->body());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue