Routing & pretty URLs
Tiger's routing has one liberating default: you rarely need to write a route at all. Every module surface is already reachable, and pretty URLs are declared, not hand-registered.
Convention first — the canonical path is free
Every controller/action is reachable at its canonical MVC path via ZF1's built-in :module/:controller/:action route, with zero registration:
Docs_IndexController::docsAction → /docs/index/docs
Billing_InvoiceController::viewAction → /billing/invoice/view/id/42
Trailing key/value pairs fold into params automatically (/billing/invoice/view/id/42 → id=42). Do not register a route for this — it exists for free, it's stable, and it's what everything else points at.
No query strings for navigation. Use path-style params —
/auth/login/out/1, not?out=1. A return-destination path lives in the session, never the URL (avoids%2F404s and history leakage).
Pretty routes are overrides, not addRoute
A vanity URL like /docs (instead of /docs/index/docs) is an optional alias. You declare it — you don't imperatively $router->addRoute() in a module Bootstrap. Declaring puts every alias through one ordering authority; hand-adding scatters precedence across bootstraps and makes you fight Zend's route stack.
class Docs_Bootstrap extends Zend_Application_Module_Bootstrap
{
protected function _initRouteOverride()
{
Tiger_Routing_Overrides::register('docs', [
'pattern' => 'docs', // public prefix; the remainder arrives as `slug`
'target' => 'docs/index/docs', // the canonical module/controller/action it maps to
'priority' => 100, // higher = checked first when prefixes overlap
]);
}
}
That's the whole contract: a public prefix maps to a canonical target, and the remaining path arrives as slug. /docs → the target with no slug; /docs/guides/deploy → the target with slug = guides/deploy.
The admin can retarget it live — no deploy
Overrides live in the config DB tier (tiger.routing.override.<name>.*), so any field is overridable at runtime, effective next request:
tiger.routing.override.docs.pattern = "help" ; serve the docs at /help instead
tiger.routing.override.docs.enabled = 0 ; turn the pretty route off (canonical path still works)
tiger.routing.override.docs.priority = 250 ; reorder against another module's alias
Same live-override pattern as everything else — a settings screen writes these keys via Tiger_Model_Config. No new table, no deploy.
How it resolves (and why order stops mattering)
A plugin runs at routeShutdown (after routing, before dispatch). For each URL:
- A real controller claims it? It wins — canonical MVC paths,
/api,/auth,/admin, and a module's own controllers. Overrides never shadow them. (This is why the reserved prefixes are safe, and why/docsnever hides/docs/admin/settings.) - A declared override prefix matches? It's rewritten to the canonical target (highest priority first).
- A published CMS page / redirect? Served (or 301'd).
- Nothing? A clean 404.
Because the plugin decides order itself, overrides are immune to Zend's last-in-first-out route matching — you never wrestle the route stack for a module alias.
The rules of thumb
- Reach features at
<module>/<controller>/<action>; don't register the canonical path. - Want a pretty URL? Declare an override; never
addRoutean alias in a module Bootstrap. - A module never touches web-server config. Pretty routes are PHP-layer overrides that work the instant a module activates — editing Apache/nginx to route a module would break 1-click install. (Changing your own deployment's web server is your prerogative — that's separate from installing a module.)
- Reserved prefixes
api,auth,admincan never be claimed by an override. - Priority is open — a module may declare any weight. Keep defaults sane (≈100) so an admin rarely has to reorder.