Skip to content
Viames Marino edited this page Mar 26, 2026 · 7 revisions

Pair framework: Router class

Pair\Core\Router parses the request URL and resolves:

  • module
  • action
  • path params
  • query params
  • special tokens (order-*, page-*, noLog)

It also supports custom routes from routes.php.

Standard pattern

Default route structure:

/module/action/param1/param2/...

Example:

/persons/edit/42

  • module: persons
  • action: edit
  • first param (Router::get(0)): 42

Controller mapping example:

class PersonsController extends Pair\Core\Controller {

    // Reads the first path param after /persons/edit/.
    public function editAction(): void
    {
        $id = Pair\Core\Router::get(0);

        // Load the requested record.
        // ...
    }

}

Getting router data

use Pair\Core\Router;

$router = Router::getInstance();

// Module/action resolved from the current URL.
$module = $router->module;
$action = $router->action;

// First SEF param after /module/action.
$id = Router::get(0);

// Query-string params are exposed with string keys.
$status = Router::get('status'); // from query string

Router::get() and $router->getParam() are equivalent accessors.

You can also read through magic properties:

$module = Router::getInstance()->module;
$action = Router::getInstance()->action;

Query-string parsing

If URL is:

/persons/list?status=active&year=2026

Router stores:

  • status => active
  • year => 2026

So you can read:

$status = Router::get('status');
$year = Router::get('year');

Special URL tokens

After /module/action, Router recognizes:

  • order-N -> sets ordering value ($router->order)
  • page-N -> sets current page ($router->getPage())
  • noLog -> disables AJAX log output ($router->sendLog() === false)

Example:

/persons/list/order-2/page-3/noLog

Pagination persistence

getPage() persists current page in cookie (scoped by module+action) and defaults to 1.

This is one of the most important Router behaviors in real modules:

  • setPage() stores the page in a cookie for 30 days
  • resetPage() clears the persisted page and restarts from 1
  • setOrder() resets pagination when the sort order changes
  • if the order has just changed, setPage() ignores stale page-* values carried by older URLs

You can reset it:

// Forces the next list render to restart from page 1.
Router::getInstance()->resetPage();

Fallback helper when current page becomes invalid after data changes:

// Typical after deleting the last row of the current page.
Router::exceedingPaginationFallback();

Example: stable sorting + pagination

use Pair\Core\Router;

$router = Router::getInstance();

// Current state from the request or from the persisted cookie.
$currentOrder = $router->order ?? 1;
$currentPage = $router->getPage();

// A new order URL automatically drops the current page.
$sortByDateUrl = $router->getOrderUrl(2);

// A page URL keeps the current filters and order.
$nextPageUrl = $router->getPageUrl($currentPage + 1);

Building URLs

$router = Router::getInstance();

// Builds the current route as-is.
$url = $router->getUrl();

// Rebuilds the same URL with another order or page value.
$orderUrl = $router->getOrderUrl(2);
$pageUrl = $router->getPageUrl(4);

getUrl() includes:

  • module/action
  • indexed params
  • /order-*
  • /page-*
  • query-string params

Detailed example:

$router = Router::getInstance();

// Adds a positional path param.
$router->setParam(0, '42');

// Adds a query-string param.
$router->setParam('status', 'active');

// Stores order/page in the generated URL.
$router->setOrder(2);
$router->setPage(3);

echo $router->getUrl();
// persons/edit/42/order-2/page-3?status=active

Main methods explained

getUrl(): string

Builds the canonical relative URL from the current router state already loaded in memory. Use it when you have modified params with setParam(), setOrder() or setPage() and need one final redirect or link.

$router = \Pair\Core\Router::getInstance();

// Keeps the current route and adds one filter.
$router->setParam('status', 'archived');

// Useful in POST/redirect/GET flows.
$redirectUrl = $router->getUrl();

getOrderUrl(?int $val = null): string

Returns a copy of the current URL with another order value. When the order really changes, the generated URL drops the current page so the next request starts from the beginning of the list.

$router = \Pair\Core\Router::getInstance();

// Current URL: orders/list/page-4?status=paid
$orderUrl = $router->getOrderUrl(2);

// Result: orders/list/order-2?status=paid

getPageUrl(?int $page = null): string

Returns a copy of the current URL with another page value, keeping route params, filters and order.

$router = \Pair\Core\Router::getInstance();

// Current URL: orders/list/order-2?status=paid
$pageUrl = $router->getPageUrl(3);

// Result: orders/list/order-2/page-3?status=paid

setParam(mixed $paramIdx, string $value, bool $encode = false): void

Stores either a positional path param (0, 1, 2, ...) or a named query-string param (status, year, ...). When $encode = true, the value is compressed and made URL-safe.

$router = \Pair\Core\Router::getInstance();

// Stores a filter state token without unsafe URL characters.
$router->setParam('state', 'filters:status=paid;year=2026', true);

// Later you can decode the same param.
$state = \Pair\Core\Router::get('state', true);

Custom routes

Pair loads custom routes from:

  • APPLICATION_PATH/routes.php
  • modules/<module>/routes.php

Register routes with:

use Pair\Core\Router;

Router::addRoute('/modifyPerson', 'edit');

Root shortcut with explicit module:

Router::addRoute('/login', 'login', 'user');

With named placeholders:

Router::addRoute('/article/:slug', 'show');

With regex placeholders:

Router::addRoute('/invoice/:id([0-9]+)', 'detail');

When placeholders are matched, Router stores values into named vars (slug, id) so Router::get('id') works.

Example:

// URL: /invoice/1001
$invoiceId = Router::get('id'); // "1001"

Route matching notes

  • custom routes are checked before standard parsing
  • module-level routes are prefixed with module path automatically
  • URL prefixes raw/ and ajax/ are ignored in path matching

Examples that can match the same custom rule:

  • /persons/get/4
  • /ajax/persons/get/4
  • /raw/persons/get/4

Defaults

You can set fallback module/action when URL is empty:

Router::getInstance()->setDefaults('home', 'default');

Get fallback URL:

$defaultUrl = Router::getInstance()->getDefaultUrl(); // "home/default"

Encoding/decoding parameters

Router can store encoded values via setParam(..., true) and decode with get(..., true):

$router = Router::getInstance();

// Encodes a value so it can travel safely in the URL.
$router->setParam('stateToken', 'filters:year=2026;status=active', true);

// Decodes the same value when reading it back.
$stateToken = Router::get('stateToken', true);

See also: Controller, View, Application, index.php.

Secondary methods worth knowing

These methods are used less often in day-to-day module code, but they matter when you customize routing behavior or bootstrap logic:

  • setDefaults(string $module, string $action): void Defines the fallback route used for an empty URL.
  • setModule(string $moduleName): void Assigns the current module and defines MODULE_PATH if needed.
  • setAction(?string $action): void Useful when advanced bootstrap logic changes the destination action.
  • resetParams(): void Clears current route params before rebuilding a URL programmatically.
  • sendLog(): bool Returns false when the request contains the noLog token.
  • routePathMatchesUrl(string $path, string $url): bool Internal matcher that also treats ajax/ and raw/ prefixes as transparent.

Quick reference (high-use methods)

Read route context:

  • Router::get(int|string $paramIdx, bool $decode = false): ?string
  • getParam(int|string $paramIdx, bool $decode = false): ?string
  • getAction(): ?string
  • getPage(): int
  • sendLog(): bool

Write/update route context:

  • setModule(string $moduleName): void
  • setAction(?string $action): void
  • setParam(mixed $paramIdx, string $value, bool $encode = false): void
  • setOrder(int $order): void
  • setPage(int $number): void
  • resetParams(): void
  • resetPage(): void

Route config and URLs:

  • addRoute(string $path, string $action, ?string $module = null, ?bool $raw = false)
  • setDefaults(string $module, string $action): void
  • getDefaultUrl(): string
  • getUrl(): string
  • getOrderUrl(?int $val = null): string
  • getPageUrl(?int $page = null): string

End-to-end pattern: list page with stable URL state

$router = \Pair\Core\Router::getInstance();

// /orders/list/order-2/page-3?status=paid
$status = \Pair\Core\Router::get('status') ?? 'all';
$order = $router->order ?? 0;
$page = $router->getPage();

// Builds a URL for "next page, same filters/order".
$nextPageUrl = $router->getPageUrl($page + 1);

End-to-end pattern: custom route with named placeholder

routes.php:

\Pair\Core\Router::addRoute('/invoice/:id([0-9]+)', 'detail', 'billing');

Controller:

public function detailAction(): void
{
    // Reads the named placeholder exposed by the router.
    $invoiceId = (int)\Pair\Core\Router::get('id');

    // Load the requested invoice and continue with the action.
}

Clone this wiki locally