chore: lots of changes
This commit is contained in:
51
src/Framework/Http/Cookie.php
Normal file
51
src/Framework/Http/Cookie.php
Normal 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;
|
||||
}
|
||||
}
|
||||
15
src/Framework/Http/Cookies.php
Normal file
15
src/Framework/Http/Cookies.php
Normal 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 = [])
|
||||
{
|
||||
}
|
||||
}
|
||||
90
src/Framework/Http/Headers.php
Normal file
90
src/Framework/Http/Headers.php
Normal 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));
|
||||
}
|
||||
}
|
||||
19
src/Framework/Http/HttpMethod.php
Normal file
19
src/Framework/Http/HttpMethod.php
Normal 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';
|
||||
|
||||
}
|
||||
18
src/Framework/Http/HttpRequest.php
Normal file
18
src/Framework/Http/HttpRequest.php
Normal 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()
|
||||
) {
|
||||
}
|
||||
}
|
||||
20
src/Framework/Http/HttpResponse.php
Normal file
20
src/Framework/Http/HttpResponse.php
Normal 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;
|
||||
}*/
|
||||
) {
|
||||
}
|
||||
}
|
||||
22
src/Framework/Http/Request.php
Normal file
22
src/Framework/Http/Request.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
20
src/Framework/Http/Response.php
Normal file
20
src/Framework/Http/Response.php
Normal 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;
|
||||
}
|
||||
}
|
||||
41
src/Framework/Http/ResponseEmitter.php
Normal file
41
src/Framework/Http/ResponseEmitter.php
Normal 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;
|
||||
}
|
||||
}
|
||||
35
src/Framework/Http/ResponseManipulator.php
Normal file
35
src/Framework/Http/ResponseManipulator.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
18
src/Framework/Http/Responses/Redirect.php
Normal file
18
src/Framework/Http/Responses/Redirect.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Framework/Http/Status.php
Normal file
13
src/Framework/Http/Status.php
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user