Files
michaelschiemer/src/Framework/Core/RouteDiscoveryVisitor.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] ?? [];
}
}