chore: lots of changes
This commit is contained in:
20
src/Framework/Router/ActionResult.php
Normal file
20
src/Framework/Router/ActionResult.php
Normal 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
|
||||
) {}
|
||||
}
|
||||
75
src/Framework/Router/HttpRouter.php
Normal file
75
src/Framework/Router/HttpRouter.php
Normal 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;
|
||||
}
|
||||
}
|
||||
13
src/Framework/Router/NoRouteMatch.php
Normal file
13
src/Framework/Router/NoRouteMatch.php
Normal 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;
|
||||
}
|
||||
}
|
||||
13
src/Framework/Router/ResultType.php
Normal file
13
src/Framework/Router/ResultType.php
Normal 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
|
||||
}
|
||||
25
src/Framework/Router/RouteCollection.php
Normal file
25
src/Framework/Router/RouteCollection.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
27
src/Framework/Router/RouteContext.php
Normal file
27
src/Framework/Router/RouteContext.php
Normal 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;
|
||||
}
|
||||
}
|
||||
51
src/Framework/Router/RouteDispatcher.php
Normal file
51
src/Framework/Router/RouteDispatcher.php
Normal 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;
|
||||
}
|
||||
}
|
||||
10
src/Framework/Router/RouteMatch.php
Normal file
10
src/Framework/Router/RouteMatch.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Router;
|
||||
|
||||
interface RouteMatch
|
||||
{
|
||||
public function isMatch(): bool;
|
||||
}
|
||||
24
src/Framework/Router/RouteMatchSuccess.php
Normal file
24
src/Framework/Router/RouteMatchSuccess.php
Normal 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;
|
||||
}
|
||||
}
|
||||
73
src/Framework/Router/RouteResponder.php
Normal file
73
src/Framework/Router/RouteResponder.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user