Routing
Routing maps a URL to a controller. Each app registers its routes in Loader::init() through the router service, $this->router().
The router works with regular expressions. A route is a key-value pair: the key is a regular expression matched against the requested URL (without the project URL and without the query string), and the value is the controller class.
Routes of the Deals app (apps/Deals/Loader.php)
public function init(): void
{
parent::init();
// CRUD routes: deals, deals/123, deals/add
$this->router()->crud('deals', Controllers\Deals::class);
// Other routes
$this->router()->get([
'/^deals\/api\/log-activity\/?$/' => Controllers\Api\LogActivity::class,
'/^deals\/boards\/deal-warnings\/?$/' => Controllers\Boards\DealWarnings::class,
'/^deals\/tags\/?$/' => Controllers\Tags::class,
'/^deals\/plan\/?$/' => Controllers\Plan::class,
]);
}
Two methods
| Method | Use for |
|---|---|
$this->router()->crud(string $urlSlug, string $controllerClass) |
A page that lists, opens and creates records of a model. |
$this->router()->get(array $routes) |
Everything else: API controllers, boards, settings pages, custom pages. |
Router methods.
Pages in this chapter
| Page | Summary |
|---|---|
| Creating CRUD routes using $router->crud() | One line for list, detail and "add" URLs of a model. |
| Creating other routes using $router->get() | Regular expressions, route variables, fixed variables and redirects. |
Pages in the Routing chapter.
URL conventions
| URL | Controller | Purpose |
|---|---|---|
deals |
Controllers\Deals |
list of deals |
deals/5 |
Controllers\Deals (recordId=5) |
list with deal 5 opened in a form |
deals/add |
Controllers\Deals (recordId=-1) |
list with an empty form |
deals/tags |
Controllers\Tags |
a secondary page of the app |
deals/api/create-from-lead |
Controllers\Api\CreateFromLead |
JSON endpoint |
deals/boards/most-valuable-deals |
Controllers\Boards\MostValuableDeals |
dashboard board |
settings/myfirstapp |
Controllers\Settings |
app settings (generated by the CLI) |
URL conventions used by the community apps.
- Every URL of an app starts with its
rootUrlSlugfrom the manifest (settings pages generated by the CLI usesettings/<slug>). - URL parts are lower case kebab-case:
lost-reasons,leave-requests. - API endpoints are under
<slug>/api/, dashboard boards under<slug>/boards/.
How the router finds the controller
For every request, Hubleto\Erp\Renderer::render() asks the router to parse the route. Router::parseRoute() tests all registered routes. Every route that matches overwrites the previous result, so the last matching route wins. Named groups in the regular expression become route variables. The controller reads them with $this->router()->urlParamAsInteger('recordId') or gets them in $this->viewParams.
From Hubleto\Framework\Services\Router::parseRoute() (simplified)
foreach ($routes as $routePattern => $controller) {
$isRegex = str_starts_with($routePattern, '/') && str_ends_with($routePattern, '/');
if ($isRegex && preg_match($routePattern . 'i', $route, $m)) {
unset($m[0]);
$routeMatch = true;
$routeVars = $m;
} else {
$routeMatch = $routePattern == $route;
$routeVars = [];
}
if ($routeMatch) {
if (is_string($controller)) {
$routeData = [ 'controller' => $controller, 'vars' => $routeVars ];
} else {
$routeData = $controller; // ['controller' => ..., 'vars' => [...]]
}
}
}
The regular expressions are matched case-insensitively (the i modifier is added automatically).
Framework routes
The framework registers generic routes used by tables and forms (api/table-describe-and-load, api/form-describe-and-load, api/record/save, api/record/delete, api/record/lookup, ...). Don't register routes that start with api/ in your app. See API controllers.
