Claude redid the router

This commit is contained in:
2026-06-30 01:35:55 -07:00
parent d5871ed8e7
commit 230476e8e5
2 changed files with 241 additions and 76 deletions

101
docs/Router.md Normal file
View File

@@ -0,0 +1,101 @@
# Router
`Novaconium\Router` resolves the current HTTP request to a controller file, based on a route table defined in PHP config files. It supports exact-match routes and simple parameterized routes (e.g. `/users/{id}`).
## How it works
On construction, the router runs through this pipeline:
1. **Load routes** — merges framework-level routes with app-level routes.
2. **Prepare path** — normalizes `REQUEST_URI` into a clean path string.
3. **Prepare query** — parses the query string into an array.
4. **Determine request type**`get` or `post`.
5. **Find controller** — matches the path against the route table.
6. **Resolve controller file** — turns the matched controller string into an actual file path, falling back to a 404 controller if needed.
## Properties
| Property | Type | Description |
|---|---|---|
| `$routes` | array | Combined route table (framework + app routes), keyed by path pattern. |
| `$query` | array | Parsed query string parameters. |
| `$path` | string | Normalized request path. |
| `$controller` | string | Resolved controller identifier (e.g. `Users/show` or `NOVACONIUM/Errors`). |
| `$controllerPath` | string | Absolute file path to the controller. |
| `$parameters` | array | Named parameters extracted from a parameterized route match. |
| `$requestType` | string | `'get'` or `'post'`. |
## Route table format
Routes are defined as an associative array, keyed by URL path. Each entry maps HTTP methods to a controller string:
```php
$routes = [
'/' => [
'get' => 'Home/index',
],
'/users/{id}' => [
'get' => 'Users/show',
],
'/login' => [
'get' => 'Auth/loginForm',
'post' => 'Auth/login',
],
];
```
Controller strings starting with `NOVACONIUM` are resolved against the framework's controller directory (e.g. `NOVACONIUM/Errors``FRAMEWORKPATH/controllers/Errors.php`); all other controller strings are resolved against the app's controller directory (`BASEPATH/App/controllers/`).
## Matching behavior
### 1. Exact match
The router first checks for an exact match between `$this->path` and a key in `$this->routes`. If found, and the current request type has a handler defined, that controller is returned immediately.
### 2. Parameterized match
If no exact match is found, the router scans the route table for patterns containing `{`. For each candidate:
- The **static prefix** of the pattern (everything before the first `{`) must prefix-match the current path.
- The pattern and the path must have the **same number of `/`-separated segments**.
The first route pattern satisfying both conditions is treated as the match — the router does not keep searching for a "better" match after this point, even if the matched route turns out to have no handler for the current request method (see [Known quirks](#known-quirks) below).
Once a candidate route is selected, the router walks its segments looking for the first `{param}` segment, extracts the corresponding path segment into `$this->parameters[paramName]`, and returns immediately with the controller for the current request type.
### 3. No match
If neither an exact nor a parameterized match is found, `$this->controller` is set to `'404'`, and `setRouteFile()` will fail to find a corresponding controller file, triggering the 404 fallback described below.
## Controller file resolution (`setRouteFile`)
Given the resolved `$this->controller` string:
1. If it starts with `NOVACONIUM`, look in `FRAMEWORKPATH/controllers/`.
2. Otherwise, look in `BASEPATH/App/controllers/`.
3. If the file exists, use it.
4. If not, fall back to `BASEPATH/App/controllers/404.php` if it exists.
5. Otherwise, fall back to the framework's built-in `FRAMEWORKPATH/controllers/404.php`.
## Known quirks
These behaviors exist in the current implementation and are preserved intentionally for backward compatibility — they're documented here so they aren't mistaken for bugs during future changes.
- **Only the first `{param}` in a route pattern is captured.** If a route pattern has multiple parameters (e.g. `/users/{id}/posts/{postId}`), only `id` is extracted into `$this->parameters`; `postId` is never processed, because the matching loop returns as soon as it finds the first parameter segment.
- **The first prefix+segment-count match wins, even without a method handler.** If a parameterized route's static prefix and segment count match the request, but that route has no handler defined for the current request type (e.g. the route only defines `get` but the request is `post`), the router still commits to that route and returns `null` as the controller (which then falls through to the 404 controller). It does **not** continue searching for another route that might have a valid handler.
- **No support for trailing wildcard or optional segments.** Segment counts must match exactly between the pattern and the path; there's no support for `*` or optional `{param?}` style segments.
## Debugging
Call `$router->debug()` to dump the resolved path, controller path, extracted parameters, and full route table as an HTML table, then halt execution via `die()`. Intended for development use only.
## Example
```php
$router = new \Novaconium\Router();
include $router->controllerPath;
// Inside the controller, access route parameters via:
$router->parameters['id'];
```