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,20 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
use App\Framework\Http\Status;
final readonly class ActionResult
{
public function __construct(
public ResultType $resultType,
public string $template,
public array $data = [],
public Status $status = Status::OK,
public string $layout = '',
public array $slots = [],
public ?string $controllerClass = null
) {}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
use App\Framework\Core\DynamicRoute;
use App\Framework\Http\HttpMethod;
final readonly class HttpRouter
{
public function __construct(
private RouteCollection $routes
) {}
/**
* Versucht, eine Route zu finden.
*
* @param string $method HTTP-Methode, zB. GET, POST
* @param string $path URI-Pfad, zB. /user/123
*/
public function match(string $method, string $path): RouteContext
{
$method = HttpMethod::tryFrom(strtoupper($method));
if ($method === null || !$this->routes->has($method->value)) {
return new RouteContext(
match: new NoRouteMatch(),
method: $method->value,
path: $path
);
}
$match = $this->matchStatic($method->value, $path)
?? $this->matchDynamic($method->value, $path)
?? new NoRouteMatch();
return new RouteContext(
match: $match,
method: $method->value,
path: $path
);
}
private function matchStatic(string $method, string $path): ?RouteMatch
{
if (isset($this->routes->getStatic($method)[$path])) {
$handler = $this->routes->getStatic($method)[$path];
return new RouteMatchSuccess($handler);
}
return null;
}
private function matchDynamic(string $method, string $path): ?RouteMatch
{
foreach ($this->routes->getDynamic($method) as $route) {
if (preg_match($route['regex'], $path, $matches)) {
array_shift($matches); // remove full match
$params = array_combine($route['params'], $matches);
$dynamicRoute = new DynamicRoute(
$route["regex"],
$route["params"],
$route['method']['class'],
$route['method']['method'],
$params
);
return new RouteMatchSuccess($route);
}
}
return null;
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
final readonly class NoRouteMatch implements RouteMatch
{
public function isMatch(): bool
{
return false;
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
enum ResultType
{
case Html;
case Json;
case Plain;
// ... weitere Typen wie Redirect, Stream usw. bei Bedarf
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Framework\Router;
final class RouteCollection
{
/** @var array<string, array{static: array<string, callable>, dynamic: array<array{regex: string, handler: callable}>}> */
private array $routes;
public function __construct(array $routes) {
$this->routes = $routes;
}
public function getStatic(string $method): array {
return $this->routes[$method]['static'] ?? [];
}
public function getDynamic(string $method): array {
return $this->routes[$method]['dynamic'] ?? [];
}
public function has(string $method): bool {
return isset($this->routes[$method]);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Framework\Router;
use Closure;
final class RouteContext
{
public null|Closure $handler {
get => $this->isSuccess() ? $this->match->route->method : null;
}
public array $params {
get => $this->isSuccess() ? $this->match->route->parameters : [];
}
public function __construct(
public readonly RouteMatch $match,
public readonly string $method,
public readonly string $path
) {}
public function isSuccess(): bool
{
return $this->match instanceof RouteMatchSuccess;
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Response;
use App\Framework\Http\Status;
class RouteDispatcher
{
public function dispatch(RouteContext $routeContext): ActionResult|Response
{
$routeMatch = $routeContext->match;
if ($routeMatch->isMatch()) {
$controller = $routeMatch->route->controller;
$action = $routeMatch->route->action;
$params = $routeMatch->route->params;
$params = $this->prepareParameters(...$params);
$obj = new $controller();
$result = $obj->$action(...$params);
// Hier könntest du z. B. Response-Objekte erwarten oder generieren:
if ($result instanceof Response || $result instanceof ActionResult) {
return $result;
}
}
// Fehlerbehandlung z.B. 404
return new HttpResponse(status: Status::NOT_FOUND, body: 'Nicht gefunden');
}
public function prepareParameters(...$params): mixed
{
$parameters = [];
foreach ($params as $param) {
if ($param['isBuiltin'] === true) {
$parameters[] = $param['default'];
} else {
#Container!
var_dump($param['isBuiltin']);
}
}
return $parameters;
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
interface RouteMatch
{
public function isMatch(): bool;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
use App\Framework\Core\DynamicRoute;
use App\Framework\Core\StaticRoute;
final readonly class RouteMatchSuccess implements RouteMatch
{
#public string $controller;
#public string $action;
#public array $params;
public function __construct(
public DynamicRoute|StaticRoute $route
) {
}
public function isMatch(): bool
{
return true;
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Framework\Router;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Response;
use App\Framework\View\Engine;
use App\Framework\View\RenderContext;
use App\Framework\View\TemplateRenderer;
readonly class RouteResponder
{
public function __construct(
private TemplateRenderer $templateRenderer = new Engine()
) {
}
public function respond(ActionResult $result): Response
{
$contentType = "text/html";
switch ($result->resultType) {
case ResultType::Html:
$body = $this->renderTemplate(
$result->template,
$result->data,
$result->layout ?? null,
$result->slots ?? [],
$result->controllerClass
);
$contentType = "text/html";
break;
case ResultType::Json:
$body = json_encode(
$result->data,
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
);
$contentType = "application/json";
break;
case ResultType::Plain:
$body = $result->data['text'] ?? '';
$contentType = "text/plain";
break;
default:
throw new \RuntimeException("Unknown result type: {$result->resultType}");
}
return new HttpResponse(
status: $result->status,
headers: new Headers()->with('Content-Type', $contentType), //['Content-Type' => $contentType],
body: $body
);
}
private function renderTemplate(string $template, array $data, ?string $layout, array $slots = [], ?string $controllerName = null): string
{
$context = new RenderContext(
template: $template,
data: $data,
layout: $layout,
slots: $slots,
controllerClass: $controllerName
);
return $this->templateRenderer->render($context);
}
}