Implement JavaScript route generation and default route fallback system

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-22 01:34:31 +00:00
parent de4e0438f0
commit 7ebe35f207
3 changed files with 107 additions and 38 deletions

View File

@@ -2088,7 +2088,7 @@ JS;
console_debug('BUNDLE', "Found controller: {$class_name}");
// Process methods with Route attributes
foreach ($file_info['methods'] ?? [] as $method_name => $method_data) {
foreach ($file_info['public_static_methods'] ?? [] as $method_name => $method_data) {
foreach ($method_data['attributes'] ?? [] as $attr_name => $attr_instances) {
if ($attr_name === 'Route') {
// Get the first route pattern (index 0 is the first argument)
@@ -2143,7 +2143,7 @@ JS;
}
// Process methods with Route attributes
foreach ($file_info['methods'] ?? [] as $method_name => $method_data) {
foreach ($file_info['public_static_methods'] ?? [] as $method_name => $method_data) {
foreach ($method_data['attributes'] ?? [] as $attr_name => $attr_instances) {
if ($attr_name === 'Route') {
// Get the first route pattern (index 0 is the first argument)

View File

@@ -115,43 +115,108 @@ class Dispatcher
\Log::debug("Dispatcher: No route found for: $url");
console_debug('DISPATCH', 'No route found for:', $url);
// No route found - try Main pre_dispatch and unhandled_route hooks
$params = array_merge(request()->all(), $extra_params);
// Check if this matches the default route pattern: /_/{controller}/{action}
if (preg_match('#^/_/([A-Za-z_][A-Za-z0-9_]*)/([A-Za-z_][A-Za-z0-9_]*)/?$#', $url, $matches)) {
$controller_name = $matches[1];
$action_name = $matches[2];
// First try Main pre_dispatch
$main_classes = Manifest::php_get_extending('Main_Abstract');
foreach ($main_classes as $main_class) {
if (isset($main_class['fqcn']) && $main_class['fqcn']) {
$main_class_name = $main_class['fqcn'];
if (method_exists($main_class_name, 'pre_dispatch')) {
Debugger::console_debug('[DISPATCH]', 'Main::pre_dispatch');
$result = $main_class_name::pre_dispatch($request, $params);
if ($result !== null) {
$response = static::__build_response($result);
console_debug('DISPATCH', 'Matched default route pattern:', $controller_name, '::', $action_name);
return static::__transform_response($response, $original_method);
}
// Try to find the controller using manifest
try {
$metadata = Manifest::php_get_metadata_by_class($controller_name);
$controller_fqcn = $metadata['fqcn'];
// Verify it extends Rsx_Controller_Abstract
if (!Manifest::php_is_subclass_of($controller_name, 'Rsx_Controller_Abstract')) {
console_debug('DISPATCH', 'Class does not extend Rsx_Controller_Abstract:', $controller_name);
return null;
}
// Verify the method exists and has a Route attribute
if (!isset($metadata['public_static_methods'][$action_name])) {
console_debug('DISPATCH', 'Method not found:', $action_name);
return null;
}
$method_data = $metadata['public_static_methods'][$action_name];
if (!isset($method_data['attributes']['Route'])) {
console_debug('DISPATCH', 'Method does not have Route attribute:', $action_name);
return null;
}
// For POST requests: execute the action
if ($route_method === 'POST') {
// Collect parameters from POST data and query string
$params = array_merge($request->query->all(), $request->request->all(), $extra_params);
// Create synthetic route match
$route_match = [
'class' => $controller_fqcn,
'method' => $action_name,
'params' => $params,
'pattern' => "/_/{$controller_name}/{$action_name}",
'require' => $method_data['attributes']['Auth'] ?? []
];
// Continue with normal dispatch (will handle auth, pre_dispatch, etc.)
// Fall through to normal route handling below
} else {
// For GET requests: redirect to the proper route
$params = array_merge($request->query->all(), $extra_params);
// Generate proper URL using Rsx::Route
$proper_url = Rsx::Route($controller_name, $action_name)->url($params);
console_debug('DISPATCH', 'Redirecting GET to proper route:', $proper_url);
return redirect($proper_url, 302);
}
} catch (\RuntimeException $e) {
console_debug('DISPATCH', 'Controller not found in manifest:', $controller_name);
return null;
}
}
// Then try unhandled_route hook
foreach ($main_classes as $main_class) {
if (isset($main_class['fqcn']) && $main_class['fqcn']) {
$main_class_name = $main_class['fqcn'];
if (method_exists($main_class_name, 'unhandled_route')) {
$result = $main_class_name::unhandled_route($request, $params);
if ($result !== null) {
$response = static::__build_response($result);
if (!$route_match) {
// No route found - try Main pre_dispatch and unhandled_route hooks
$params = array_merge(request()->all(), $extra_params);
return static::__transform_response($response, $original_method);
// First try Main pre_dispatch
$main_classes = Manifest::php_get_extending('Main_Abstract');
foreach ($main_classes as $main_class) {
if (isset($main_class['fqcn']) && $main_class['fqcn']) {
$main_class_name = $main_class['fqcn'];
if (method_exists($main_class_name, 'pre_dispatch')) {
Debugger::console_debug('[DISPATCH]', 'Main::pre_dispatch');
$result = $main_class_name::pre_dispatch($request, $params);
if ($result !== null) {
$response = static::__build_response($result);
return static::__transform_response($response, $original_method);
}
}
}
}
}
// Default 404 - return null to let Laravel handle it
return null;
// Then try unhandled_route hook
foreach ($main_classes as $main_class) {
if (isset($main_class['fqcn']) && $main_class['fqcn']) {
$main_class_name = $main_class['fqcn'];
if (method_exists($main_class_name, 'unhandled_route')) {
$result = $main_class_name::unhandled_route($request, $params);
if ($result !== null) {
$response = static::__build_response($result);
return static::__transform_response($response, $original_method);
}
}
}
}
// Default 404 - return null to let Laravel handle it
return null;
}
}
// Extract route information

View File

@@ -156,6 +156,9 @@ class Rsx {
* The proxy ensures all required route parameters are provided and handles extra parameters
* as query string values.
*
* If the route is not found in the route definitions, a default pattern is used:
* `/_/{controller}/{action}` with all parameters appended as query strings.
*
* Usage examples:
* ```javascript
* // Simple route without parameters (defaults to 'index' action)
@@ -177,6 +180,10 @@ class Rsx {
* });
* // Returns: /clients/view/C001?tab=history
*
* // Route not found - uses default pattern
* const url = Rsx.Route('Unimplemented_Controller', 'some_action').url({foo: 'bar'});
* // Returns: /_/Unimplemented_Controller/some_action?foo=bar
*
* // Generate absolute URL
* const absolute = Rsx.Route('Frontend_Index_Controller').absolute_url();
* // Returns: https://example.com/dashboard
@@ -194,20 +201,17 @@ class Rsx {
* @param {string} class_name The controller class name (e.g., 'User_Controller')
* @param {string} [action_name='index'] The action/method name (defaults to 'index')
* @returns {Rsx_Route_Proxy} Route proxy instance for URL generation
* @throws {Error} If route not found
*/
static Route(class_name, action_name = 'index') {
// Check if route exists
if (!Rsx._routes[class_name]) {
throw new Error(`Class ${class_name} not found in routes`);
// Check if route exists in definitions
if (Rsx._routes[class_name] && Rsx._routes[class_name][action_name]) {
const pattern = Rsx._routes[class_name][action_name];
return new Rsx_Route_Proxy(class_name, action_name, pattern);
}
if (!Rsx._routes[class_name][action_name]) {
throw new Error(`Method ${action_name} not found in class ${class_name}`);
}
const pattern = Rsx._routes[class_name][action_name];
return new Rsx_Route_Proxy(class_name, action_name, pattern);
// Route not found - use default pattern /_/{controller}/{action}
const default_pattern = `/_/${class_name}/${action_name}`;
return new Rsx_Route_Proxy(class_name, action_name, default_pattern);
}
/**