www.hubleto.eu Reddit User Guide Stack Overflow LinkedIn Report an issue

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.

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.