Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -122,7 +122,7 @@ class Dispatcher
|
||||
|
||||
console_debug('DISPATCH', 'Matched default route pattern:', $controller_name, '::', $action_name);
|
||||
|
||||
// Try to find the controller using manifest
|
||||
// First try to find as PHP controller
|
||||
try {
|
||||
$metadata = Manifest::php_get_metadata_by_class($controller_name);
|
||||
$controller_fqcn = $metadata['fqcn'];
|
||||
@@ -173,7 +173,56 @@ class Dispatcher
|
||||
return redirect($proper_url, 302);
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
console_debug('DISPATCH', 'Controller not found in manifest:', $controller_name);
|
||||
console_debug('DISPATCH', 'Not a PHP controller, checking if SPA action:', $controller_name);
|
||||
|
||||
// Not found as PHP controller - check if it's a SPA action
|
||||
try {
|
||||
$is_spa_action = Manifest::js_is_subclass_of($controller_name, 'Spa_Action');
|
||||
|
||||
if ($is_spa_action) {
|
||||
console_debug('DISPATCH', 'Found SPA action class:', $controller_name);
|
||||
|
||||
// Get the file path for this JS class
|
||||
$file_path = Manifest::js_find_class($controller_name);
|
||||
|
||||
// Get file metadata which contains decorator information
|
||||
$file_data = Manifest::get_file($file_path);
|
||||
|
||||
if (!$file_data) {
|
||||
console_debug('DISPATCH', 'SPA action metadata not found:', $controller_name);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract route pattern from @route() decorator
|
||||
// Format: [[0 => 'route', 1 => ['/contacts']], ...]
|
||||
$route_pattern = null;
|
||||
if (isset($file_data['decorators']) && is_array($file_data['decorators'])) {
|
||||
foreach ($file_data['decorators'] as $decorator) {
|
||||
if (isset($decorator[0]) && $decorator[0] === 'route') {
|
||||
if (isset($decorator[1][0])) {
|
||||
$route_pattern = $decorator[1][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($route_pattern) {
|
||||
// Generate proper URL for the SPA action
|
||||
$params = array_merge($extra_params, $request->query->all());
|
||||
$proper_url = Rsx::Route($controller_name, $action_name, $params);
|
||||
|
||||
console_debug('DISPATCH', 'Redirecting to SPA action route:', $proper_url);
|
||||
|
||||
return redirect($proper_url, 302);
|
||||
} else {
|
||||
console_debug('DISPATCH', 'SPA action missing @route() decorator:', $controller_name);
|
||||
}
|
||||
}
|
||||
} catch (\RuntimeException $spa_e) {
|
||||
console_debug('DISPATCH', 'Not a SPA action either:', $controller_name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -246,7 +295,8 @@ class Dispatcher
|
||||
Debugger::console_debug('DISPATCH', 'Matched route to ' . $handler_class . '::' . $handler_method . ' params: ' . json_encode($params));
|
||||
|
||||
// Set current controller and action in Rsx for tracking
|
||||
\App\RSpade\Core\Rsx::_set_current_controller_action($handler_class, $handler_method, $params);
|
||||
$route_type = $route_match['type'] ?? 'standard';
|
||||
\App\RSpade\Core\Rsx::_set_current_controller_action($handler_class, $handler_method, $params, $route_type);
|
||||
|
||||
// Load and validate handler class
|
||||
static::__load_handler_class($handler_class);
|
||||
@@ -339,93 +389,78 @@ class Dispatcher
|
||||
if (empty($routes)) {
|
||||
\Log::debug('Manifest::get_routes() returned empty array');
|
||||
console_debug('DISPATCH', 'Warning: got 0 routes from Manifest::get_routes()');
|
||||
} else {
|
||||
\Log::debug('Manifest has ' . count($routes) . ' route types');
|
||||
// Log details for debugging but don't output to console
|
||||
foreach ($routes as $type => $type_routes) {
|
||||
\Log::debug("Route type '$type' has " . count($type_routes) . ' patterns');
|
||||
// Show first few patterns for debugging in logs only
|
||||
$patterns = array_slice(array_keys($type_routes), 0, 5);
|
||||
\Log::debug(' First patterns: ' . implode(', ', $patterns));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort handler types by priority
|
||||
$sorted_types = array_keys(static::$handler_priorities);
|
||||
usort($sorted_types, function ($a, $b) {
|
||||
return static::$handler_priorities[$a] - static::$handler_priorities[$b];
|
||||
});
|
||||
\Log::debug('Manifest has ' . count($routes) . ' routes');
|
||||
|
||||
// Collect all matching routes
|
||||
$matches = [];
|
||||
// Get all patterns and sort by priority
|
||||
$patterns = array_keys($routes);
|
||||
$patterns = RouteResolver::sort_by_priority($patterns);
|
||||
|
||||
// Try each handler type in priority order
|
||||
foreach ($sorted_types as $type) {
|
||||
if (!isset($routes[$type])) {
|
||||
// Try to match each pattern
|
||||
foreach ($patterns as $pattern) {
|
||||
$route = $routes[$pattern];
|
||||
|
||||
// Check if HTTP method is supported
|
||||
if (!in_array($method, $route['methods'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type_routes = $routes[$type];
|
||||
// Try to match the URL
|
||||
$params = RouteResolver::match_with_query($url, $pattern);
|
||||
|
||||
// Get all patterns for this type
|
||||
$patterns = array_keys($type_routes);
|
||||
if ($params !== false) {
|
||||
// Found a match - verify the method has the required attribute
|
||||
$class_fqcn = $route['class'];
|
||||
$method_name = $route['method'];
|
||||
|
||||
// Sort patterns by priority
|
||||
$patterns = RouteResolver::sort_by_priority($patterns);
|
||||
// Get method metadata from manifest
|
||||
$class_metadata = Manifest::php_get_metadata_by_fqcn($class_fqcn);
|
||||
$method_metadata = $class_metadata['public_static_methods'][$method_name] ?? null;
|
||||
|
||||
// Try to match each pattern
|
||||
foreach ($patterns as $pattern) {
|
||||
$route_info = $type_routes[$pattern];
|
||||
if (!$method_metadata) {
|
||||
throw new \RuntimeException(
|
||||
"Route method not found in manifest: {$class_fqcn}::{$method_name}\n" .
|
||||
"Pattern: {$pattern}"
|
||||
);
|
||||
}
|
||||
|
||||
// Check if method is supported
|
||||
if (isset($route_info[$method])) {
|
||||
// Try to match the URL
|
||||
$params = RouteResolver::match_with_query($url, $pattern);
|
||||
// Check for Route or SPA attribute
|
||||
$attributes = $method_metadata['attributes'] ?? [];
|
||||
$has_route = false;
|
||||
|
||||
if ($params !== false) {
|
||||
// Handle new structure where each method can have multiple handlers
|
||||
$handlers = $route_info[$method];
|
||||
|
||||
// If it's not an array of handlers, convert it (backwards compatibility)
|
||||
if (!isset($handlers[0])) {
|
||||
$handlers = [$handlers];
|
||||
}
|
||||
|
||||
// Add all matching handlers
|
||||
foreach ($handlers as $handler) {
|
||||
$matches[] = [
|
||||
'type' => $type,
|
||||
'pattern' => $pattern,
|
||||
'class' => $handler['class'],
|
||||
'method' => $handler['method'],
|
||||
'params' => $params,
|
||||
'file' => $handler['file'] ?? null,
|
||||
'require' => $handler['require'] ?? [],
|
||||
];
|
||||
}
|
||||
foreach ($attributes as $attr_name => $attr_instances) {
|
||||
if (str_ends_with($attr_name, '\\Route') || $attr_name === 'Route' ||
|
||||
str_ends_with($attr_name, '\\SPA') || $attr_name === 'SPA') {
|
||||
$has_route = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate routes
|
||||
if (count($matches) > 1) {
|
||||
$error_msg = "Multiple routes match the request '{$method} {$url}':\n\n";
|
||||
foreach ($matches as $match) {
|
||||
$error_msg .= " - Pattern: {$match['pattern']}\n";
|
||||
$error_msg .= " Class: {$match['class']}::{$match['method']}\n";
|
||||
if (!empty($match['file'])) {
|
||||
$error_msg .= " File: {$match['file']}\n";
|
||||
if (!$has_route) {
|
||||
throw new \RuntimeException(
|
||||
"Route method {$class_fqcn}::{$method_name} is missing required #[Route] or #[SPA] attribute.\n" .
|
||||
"Pattern: {$pattern}\n" .
|
||||
"File: {$route['file']}"
|
||||
);
|
||||
}
|
||||
$error_msg .= " Type: {$match['type']}\n\n";
|
||||
}
|
||||
$error_msg .= 'Routes must be unique. Please remove duplicate route definitions.';
|
||||
|
||||
throw new RuntimeException($error_msg);
|
||||
// Return route with params
|
||||
return [
|
||||
'type' => $route['type'],
|
||||
'pattern' => $pattern,
|
||||
'class' => $route['class'],
|
||||
'method' => $route['method'],
|
||||
'params' => $params,
|
||||
'file' => $route['file'] ?? null,
|
||||
'require' => $route['require'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the single match or null
|
||||
return $matches[0] ?? null;
|
||||
// No match found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -572,8 +607,9 @@ class Dispatcher
|
||||
throw new Exception("Method not public: {$class_name}::{$method_name}");
|
||||
}
|
||||
|
||||
// Set current controller and action for tracking
|
||||
Rsx::_set_current_controller_action($class_name, $method_name, $params);
|
||||
// NOTE: Do NOT call _set_current_controller_action here - it's already been set
|
||||
// earlier in the dispatch flow with the correct route type. Calling it again
|
||||
// would overwrite the route type with null.
|
||||
|
||||
// Check if this is a controller (all methods are static)
|
||||
if (static::__is_controller($class_name)) {
|
||||
@@ -1096,7 +1132,11 @@ class Dispatcher
|
||||
"Auth redirect_to must be array ['Controller', 'action'] on {$handler_class}::{$handler_method}"
|
||||
);
|
||||
}
|
||||
$url = Rsx::Route($redirect_to[0], $redirect_to[1] ?? 'index');
|
||||
$action = $redirect_to[0];
|
||||
if (isset($redirect_to[1]) && $redirect_to[1] !== 'index') {
|
||||
$action .= '::' . $redirect_to[1];
|
||||
}
|
||||
$url = Rsx::Route($action);
|
||||
if ($message) {
|
||||
Rsx::flash_error($message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user