232 lines
7.1 KiB
PHP
232 lines
7.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core;
|
|
|
|
use App\Framework\Attributes\Route as RouteAttribute;
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Discovery\FileVisitor;
|
|
|
|
/**
|
|
* Visitor zum Auffinden von Routen
|
|
*/
|
|
final class RouteDiscoveryVisitor implements FileVisitor
|
|
{
|
|
private array $routes = [];
|
|
private array $routesByPath = [];
|
|
private array $routesByController = [];
|
|
private bool $routesSorted = false;
|
|
|
|
public function onScanStart(): void
|
|
{
|
|
$this->routes = [];
|
|
$this->routesByPath = [];
|
|
$this->routesByController = [];
|
|
$this->routesSorted = false;
|
|
}
|
|
|
|
public function onIncrementalScanStart(): void
|
|
{
|
|
// Bei inkrementellem Scan behalten wir bestehende Routen
|
|
// markieren sie aber als unsortiert
|
|
$this->routesSorted = false;
|
|
}
|
|
|
|
public function onIncrementalScanComplete(): void
|
|
{
|
|
// Nach inkrementellem Scan sortieren
|
|
$this->sortRoutes();
|
|
}
|
|
|
|
public function visitClass(string $className, string $filePath): void
|
|
{
|
|
if (!class_exists($className)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$reflection = new \ReflectionClass($className);
|
|
|
|
// Prüfe, ob die Klasse überhaupt Methoden mit Route-Attributen hat
|
|
$hasRouteMethods = false;
|
|
foreach ($reflection->getMethods() as $method) {
|
|
if (!empty($method->getAttributes(RouteAttribute::class))) {
|
|
$hasRouteMethods = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$hasRouteMethods) {
|
|
return;
|
|
}
|
|
|
|
// Nur Methoden mit Route-Attribut verarbeiten
|
|
foreach ($reflection->getMethods() as $method) {
|
|
$routeAttributes = $method->getAttributes(RouteAttribute::class);
|
|
|
|
foreach ($routeAttributes as $routeAttr) {
|
|
/** @var RouteAttribute $route */
|
|
$route = $routeAttr->newInstance();
|
|
|
|
$routeData = [
|
|
'path' => $route->path,
|
|
'method' => $route->method,
|
|
'controller' => $className,
|
|
'action' => $method->getName(),
|
|
'middleware' => $route->middleware ?? [],
|
|
'priority' => $route->priority ?? 0
|
|
];
|
|
|
|
$this->routes[] = $routeData;
|
|
|
|
// Erstelle Indizes für schnelleren Zugriff
|
|
$path = strtolower($route->path);
|
|
$httpMethod = strtoupper($route->method->value);
|
|
$key = "{$httpMethod}:{$path}";
|
|
|
|
if (!isset($this->routesByPath[$key])) {
|
|
$this->routesByPath[$key] = [];
|
|
}
|
|
$this->routesByPath[$key][] = $routeData;
|
|
|
|
if (!isset($this->routesByController[$className])) {
|
|
$this->routesByController[$className] = [];
|
|
}
|
|
$this->routesByController[$className][] = $routeData;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
error_log("Fehler beim Verarbeiten der Routen für {$className}: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function onScanComplete(): void
|
|
{
|
|
// Sortiere Routen nach Priorität
|
|
$this->sortRoutes();
|
|
}
|
|
|
|
/**
|
|
* Sortiert die Routen nach Priorität (absteigend)
|
|
*/
|
|
private function sortRoutes(): void
|
|
{
|
|
if ($this->routesSorted) {
|
|
return;
|
|
}
|
|
|
|
// Sortiere Hauptrouten-Array
|
|
usort($this->routes, function($a, $b) {
|
|
return $b['priority'] <=> $a['priority'];
|
|
});
|
|
|
|
// Sortiere Indizes
|
|
foreach ($this->routesByPath as $key => $routes) {
|
|
usort($this->routesByPath[$key], function($a, $b) {
|
|
return $b['priority'] <=> $a['priority'];
|
|
});
|
|
}
|
|
|
|
foreach ($this->routesByController as $className => $routes) {
|
|
usort($this->routesByController[$className], function($a, $b) {
|
|
return $b['priority'] <=> $a['priority'];
|
|
});
|
|
}
|
|
|
|
$this->routesSorted = true;
|
|
}
|
|
|
|
public function loadFromCache(Cache $cache): void
|
|
{
|
|
$cacheItem = $cache->get($this->getCacheKey());
|
|
if ($cacheItem->isHit) {
|
|
if (is_array($cacheItem->value) && isset($cacheItem->value['routes'], $cacheItem->value['byPath'], $cacheItem->value['byController'])) {
|
|
$this->routes = $cacheItem->value['routes'];
|
|
$this->routesByPath = $cacheItem->value['byPath'];
|
|
$this->routesByController = $cacheItem->value['byController'];
|
|
$this->routesSorted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getCacheKey(): string
|
|
{
|
|
return 'routes';
|
|
}
|
|
|
|
public function getCacheableData(): mixed
|
|
{
|
|
// Stelle sicher, dass die Routen sortiert sind, bevor wir sie cachen
|
|
if (!$this->routesSorted) {
|
|
$this->sortRoutes();
|
|
}
|
|
|
|
return [
|
|
'routes' => $this->routes,
|
|
'byPath' => $this->routesByPath,
|
|
'byController' => $this->routesByController
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Gibt alle gefundenen Routen zurück
|
|
*/
|
|
public function getRoutes(): array
|
|
{
|
|
if (!$this->routesSorted) {
|
|
$this->sortRoutes();
|
|
}
|
|
return $this->routes;
|
|
}
|
|
|
|
/**
|
|
* Findet eine Route für einen bestimmten Pfad und HTTP-Methode
|
|
*/
|
|
public function findRoute(string $path, string $httpMethod): ?array
|
|
{
|
|
$path = strtolower(rtrim($path, '/'));
|
|
$httpMethod = strtoupper($httpMethod);
|
|
$key = "{$httpMethod}:{$path}";
|
|
|
|
// Direkte Übereinstimmung
|
|
if (isset($this->routesByPath[$key]) && !empty($this->routesByPath[$key])) {
|
|
return $this->routesByPath[$key][0]; // Höchste Priorität zuerst
|
|
}
|
|
|
|
// Prüfe auf parametrisierte Routen, wenn keine direkte Übereinstimmung
|
|
foreach ($this->routesByPath as $routeKey => $routes) {
|
|
list($routeMethod, $routePath) = explode(':', $routeKey, 2);
|
|
|
|
if ($routeMethod !== $httpMethod) {
|
|
continue;
|
|
}
|
|
|
|
// Konvertiere Route-Pfad in ein reguläres Ausdrucksmuster
|
|
$pattern = $this->routePathToRegex($routePath);
|
|
if (preg_match($pattern, $path)) {
|
|
return $routes[0]; // Höchste Priorität zuerst
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Konvertiert einen Route-Pfad in ein reguläres Ausdrucksmuster
|
|
*/
|
|
private function routePathToRegex(string $routePath): string
|
|
{
|
|
// Ersetze Platzhalter wie {id} durch Regex-Gruppen
|
|
$pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $routePath);
|
|
return '/^' . str_replace('/', '\/', $pattern) . '$/';
|
|
}
|
|
|
|
/**
|
|
* Gibt alle Routen für einen bestimmten Controller zurück
|
|
*/
|
|
public function getRoutesForController(string $controllerClass): array
|
|
{
|
|
return $this->routesByController[$controllerClass] ?? [];
|
|
}
|
|
}
|