chore: lots of changes

This commit is contained in:
2025-05-24 07:09:22 +02:00
parent 77ee769d5e
commit 899227b0a4
178 changed files with 5145 additions and 53 deletions

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
final readonly class Cookie
{
public function __construct(
public string $name,
public string $value,
public ?int $expires = null,
public string $path = '/',
public ?string $domain = '',
public bool $secure = false,
public bool $httpOnly = false,
public ?string $sameSite = 'Lax'
) {
}
public function toHeaderString(): string
{
$cookie = urlencode($this->name) . '=' . urlencode($this->value);
if ($this->expires !== null) {
$cookie .= '; Expires=' . gmdate('D, d-M-Y H:i:s T', $this->expires);
}
if ($this->path) {
$cookie .= '; Path=' . $this->path;
}
if ($this->domain) {
$cookie .= '; Domain=' . $this->domain;
}
if ($this->secure) {
$cookie .= '; Secure';
}
if ($this->httpOnly) {
$cookie .= '; HttpOnly';
}
if ($this->sameSite) {
$cookie .= '; SameSite=' . $this->sameSite;
}
return $cookie;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
class Cookies
{
/** @var array<string, Cookie> */
private array $cookies = [];
public function __construct(array $rawCookies = [])
{
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Framework\Http;
final class Headers
{
/**
* Struktur:
* [
* 'content-type' => ['Content-Type', ['text/html']],
* 'set-cookie' => ['Set-Cookie', ['a=1', 'b=2']],
* ]
*/
/**
* @var array<string, array{string, string[]}>
* Struktur: 'normalized-lower-name' => [Original-Name, [Wert1, Wert2, ...]]
*/
public function __construct(
private readonly array $headers = []
) {}
public function with(string $name, string|array $value): self
{
$key = strtolower($name);
$original = $this->normalizeName($name);
$values = is_array($value) ? array_values($value) : [$value];
$new = $this->headers;
$new[$key] = [$original, $values];
return new self($new);
}
public function withAdded(string $name, string $value): self
{
$key = strtolower($name);
$original = $this->normalizeName($name);
$new = $this->headers;
if (!isset($new[$key])) {
$new[$key] = [$original, [$value]];
} else {
$new[$key][1][] = $value;
}
return new self($new);
}
public function without(string $name): self
{
$key = strtolower($name);
$new = $this->headers;
unset($new[$key]);
return new self($new);
}
public function get(string $name): ?array
{
return $this->headers[strtolower($name)][1] ?? null;
}
public function getFirst(string $name): ?string
{
return $this->get($name)[0] ?? null;
}
public function has(string $name): bool
{
return isset($this->headers[strtolower($name)]);
}
/**
* Gibt die Header im Format ['Original-Name' => [Wert1, Wert2]]
*/
public function all(): array
{
$output = [];
foreach ($this->headers as [$original, $values]) {
$output[$original] = $values;
}
return $output;
}
private function normalizeName(string $name): string
{
return preg_replace_callback('/(?:^|-)[a-z]/', fn($m) => strtoupper($m[0]), strtolower($name));
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
enum HttpMethod: string
{
case GET = 'GET';
case POST = 'POST';
case PUT = 'PUT';
case PATCH = 'PATCH';
case DELETE = 'DELETE';
case HEAD = 'HEAD';
case OPTIONS = 'OPTIONS';
case TRACE = 'TRACE';
case CONNECT = 'CONNECT';
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
final class HttpRequest implements Request
{
public function __construct(
public readonly HttpMethod $method = HttpMethod::GET,
readonly Headers $headers = new Headers(),
readonly string $body = '',
readonly string $path = '',
readonly array $files = [],
readonly Cookies $cookies = new Cookies()
) {
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
final readonly class HttpResponse implements Response
{
public function __construct(
public Status $status = Status::OK,
public Headers $headers = new Headers(),
public string $body = ''
/*public private(set) string $body = '' {
get => $this->body;
set => $value;
}*/
) {
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
interface Request
{
public Headers $headers{
get;
}
public string $body{
get;
}
public HttpMethod $method{
get;
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
interface Response
{
public string $body{
get;
}
public Status $status{
get;
}
public Headers $headers{
get;
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
final class ResponseEmitter
{
public function emit(Response $response): void
{
// Status-Code senden
http_response_code($response->status->value);
// Header senden
/*foreach ($response->headers->all() as $name => $values) {
#foreach ((array)$values as $value) {
header("$name: $values", false);
#}
}*/
// Header senden
foreach ($response->headers->all() as $name => $value) {
// Sicherheitsprüfung
if (!preg_match('/^[A-Za-z0-9\-]+$/', $name)) {
throw new \InvalidArgumentException("Invalid header name: '$name'");
}
// Bei Mehrfach-Headern: ggf. als Array zulassen
if (is_array($value)) {
foreach ($value as $single) {
header("$name: $single", false); // false = nicht ersetzen
}
} else {
header("$name: $value", true);
}
}
// Body ausgeben
echo $response->body;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Framework\Http;
final readonly class ResponseManipulator
{
public function withBody(Response $response, string $body): Response
{
return new HttpResponse(
status: $response->status,
headers: $response->headers,
body: $body
);
}
public function withHeader(Response $response, string $name, string $value): Response
{
$headers = clone $response->headers;
$headers->with($name, $value);
return new HttpResponse(
status: $response->status,
headers: $headers,
body: $response->body
);
}
public function withStatus(Response $response, Status $status): Response
{
return new HttpResponse(
status: $status,
headers: $response->headers,
body: $response->body
);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Framework\Http\Responses;
use App\Framework\Http\Headers;
use App\Framework\Http\Response;
use App\Framework\Http\Status;
class Redirect implements Response
{
public private(set) string $body = '';
public private(set) Status $status = Status::FOUND;
public \App\Framework\Http\Headers $headers {
get {
return new Headers()->with('Location', $this->body);
}
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http;
enum Status: int
{
case OK = 200;
case NOT_FOUND = 404;
case FOUND = 302;
case INTERNAL_SERVER_ERROR = 500;
}