PHP自定义MVC框架URL路由适配:书籍控制器省略action
Hey Leo, great question! Handling this kind of custom routing in your PHP MVC framework is totally manageable—you just need to add a conditional check in your router before falling back to the standard URL parsing logic. Let me break down a straightforward implementation for you:
Step 1: Set Up Your .htaccess (If You Haven’t Already)
First, make sure all incoming requests are routed to your front-controller (usually index.php) so your PHP code can handle the routing. Add this to your .htaccess file:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [L]
Step 2: Parse the URL Segments
In your index.php (or dedicated router class), start by extracting and cleaning up the URL segments:
// Get the request URI and strip out the query string $uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/'); // Split the URI into individual segments $segments = explode('/', $uri); // Set default values for controller, action, and ID $controller = 'Home'; // Fallback to a default controller $action = 'index'; // Fallback to the index action $id = null;
Step 3: Handle the Custom "Books" Route
Add a conditional check to catch requests targeting the books controller first. This lets you override the standard routing logic for this specific case:
// Check if the first segment is "books" if (!empty($segments[0]) && strtolower($segments[0]) === 'books') { $controller = 'Books'; // Map to your Books controller class // If a second segment exists and looks like a valid ID (adjust validation as needed) if (!empty($segments[1]) && is_numeric($segments[1])) { $action = 'index'; // Force the action to "index" $id = $segments[1]; // Reject any extra segments beyond the ID (e.g., localhost/books/1/extra) if (count($segments) > 2) { http_response_code(404); include __DIR__ . '/views/404.php'; // Use your custom 404 view exit; } } elseif (!empty($segments[1])) { // If there's a second segment that isn't an ID, treat it as invalid (per your requirement) http_response_code(404); include __DIR__ . '/views/404.php'; exit; } // If no second segment, keep action as "index" with no ID } else { // Standard routing logic for all other controllers if (!empty($segments[0])) { $controller = ucfirst(strtolower($segments[0])); // Capitalize controller name (e.g., games → Games) } if (!empty($segments[1])) { $action = strtolower($segments[1]); // Use the second segment as action } if (!empty($segments[2])) { $id = $segments[2]; // Use the third segment as ID } // Reject extra segments for standard routes (e.g., localhost/games/puzzle/1/extra) if (count($segments) > 3) { http_response_code(404); include __DIR__ . '/views/404.php'; exit; } }
Step 4: Validate and Execute the Controller/Action
Finally, verify that the controller class and action method exist, then run the action:
// Check if the controller class exists if (!class_exists($controller)) { http_response_code(404); include __DIR__ . '/views/404.php'; exit; } // Instantiate the controller $controllerInstance = new $controller(); // Check if the action method exists on the controller if (!method_exists($controllerInstance, $action)) { http_response_code(404); include __DIR__ . '/views/404.php'; exit; } // Call the action—pass the ID if it's set if ($id !== null) { $controllerInstance->$action($id); } else { $controllerInstance->$action(); }
Bonus: Make It Scalable
If you think you might add more controllers with custom routing later, you can abstract the special routes into a configuration array. This keeps your router clean and easy to update:
// Define custom routes: [controller_name => default_action] $customRoutes = [ 'books' => 'index', // Add more later, e.g., 'authors' => 'list' ]; // Check if the first segment matches a custom route if (!empty($segments[0]) && isset($customRoutes[strtolower($segments[0])])) { $controller = ucfirst($segments[0]); $action = $customRoutes[strtolower($segments[0])]; // Handle ID logic here (similar to the books example) // ... } else { // Standard routing // ... }
This setup will let you access localhost/books/1 to hit the Books::index(1) method, while still supporting standard URLs like localhost/games/puzzle/1 (which maps to Games::puzzle(1)).
内容的提问来源于stack exchange,提问作者Leo

