chore: complete update
This commit is contained in:
231
src/Framework/Core/RouteDiscoveryVisitor.php
Normal file
231
src/Framework/Core/RouteDiscoveryVisitor.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?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] ?? [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user