Building Links and Routes

5 views Markdown

Addresses are generated from a route key, never written out, because every route is translated and the panel directory is not fixed.

Overview

A route key is a name; the address it produces depends on the language and on the installation. A literal path works in one language and answers 404 in the others. A panel link that hardcodes the admin directory breaks on any installation that renamed it.

Four static methods cover every case, all of them in coremio/classes/LinkGenerator.php.

Reference

LinkGenerator
public static function admin($route = '', $params = [], $lang = '', $wqs = []): string|bool;
public static function client($route = '', $params = [], $lang = '');
public static function wQS(null|string|bool $url, string|array $params = []): string;
public static function convert_to_link(string $arg): string;
admin() A panel address. The only one with a fourth argument. That makes it the only one that can append a query string on its own.
client() A website or client-area address. Three arguments, no query string; wrap the result in wQS() when you need one.
wQS() Appends a query string to an address that already exists, choosing between ? and & itself. The second argument must be an array in practice.
convert_to_link() Resolves a stored reference such as pages/12 into a full address. This is what makes a menu entry or an editor link portable. Results are memoized per request and language.

The Four Arguments

PositionArgumentWhat it must be
1$routeA key from the route table, not a path. An unknown key is not rejected, it is pasted into the address as is.
2$paramsPositional path segments. Each value fills the next (?) in the key's pattern, in order; leftovers are dropped silently. A non-array value is wrapped into a one element list.
3$lang'' uses the language being served. A code such as tr forces that language, and the literal none produces an address with no language segment at all.
4$wqsQuery string as an associative array, applied only when it is non-empty. Present on admin() only.

A Route Key Carries Its Segment Count

The router fills one (?) per parameter and stops. A key whose pattern has no placeholder ignores every parameter you pass. That is why the list and the detail address are two different keys rather than one key with arguments.

route table entries and what they generate
// coremio/locale/en/admin-routes.php - key => [address pattern, controller route]
return ['admin-routes' => [
    'services'   => ['services',         'services'],
    'services-1' => ['services/(?)',     'services/(1)'],
    'services-2' => ['services/(?)/(?)', 'services/(1)/(2)'],
]];

// LinkGenerator::admin("services",   ["detail", 5])  ->  /services        both values dropped
// LinkGenerator::admin("services-1", ["detail"])     ->  /services/detail
// LinkGenerator::admin("services-2", ["detail", 5])  ->  /services/detail/5
locale/{lang}/admin-routes.php Panel keys. One file per language, so the same key produces a different path in each.
locale/{lang}/website-routes.php Website and client-area keys, same shape.

What convert_to_link Accepts

FormExampleResolves to
Site page keyhome, contact, basketThat website route in the active language.
Client-area keyca-invoices, ca-domains, ca-ticketsThat client-area route.
Record referencepages/12, products/48, kbase-page/7The record's detail address, looked up from its translated slug.
Category referencecategory/25The catalogue, article, reference or knowledge base listing, chosen from the category's own type.
Product groupproduct-group/hostingThat group's catalogue page.
Anything elsewhateverThe string #, so a broken reference appears as a dead link instead of an error.

Example

building addresses
// A list page: no parameters, no query string.
$list = LinkGenerator::admin("services");

// A detail page. The record id travels as a query string, so the fourth
// argument is used and the third stays empty.
$detail = LinkGenerator::admin("services-1", ["detail"], '', ['id' => $serviceId]);

// The same page opened on a tab.
$tab = LinkGenerator::admin("services-1", ["detail"], '', ['id' => $serviceId, 'tab' => "invoices"]);

// A website address forced into one language, for an alternate-language tag.
$signIn = LinkGenerator::client("sign-in", [], "tr");

// client() has no query string argument, so wrap it.
$paged = LinkGenerator::wQS(LinkGenerator::client("services"), ['page' => 2]);

// A stored reference from a menu row, resolved at render time.
$href = LinkGenerator::convert_to_link($row["page"]);       // "pages/12" -> /about-us
the other side: reading the address back
// The second element of the entry is the controller route the address maps back to,
// which is why the key, not the path, is the thing code is allowed to know:
//
//   'services-1' => ['services/(?)', 'services/(1)']
//                     ^ written by       ^ read by the router, which dispatches
//                       LinkGenerator       services::page_detail()

class Services extends Controllers
{
    public function page_detail(): void
    {
        $id = (int) Filter::init("GET/id", "rnumbers");
        $this->addData("back", LinkGenerator::admin("services"));
    }
}

Pitfalls

The query string is the fourth argument, the third is the language

Writing LinkGenerator::admin("services-1", ["detail"], ['id' => 5]) puts the array where the language code belongs. Nothing is thrown. You get an Array to string conversion warning and the address /Array/{admin}/services/detail. The path segments in it still resolve, and the id you meant to send is nowhere. Pass '' for the language and keep the query string in position four.

An unknown key becomes the address

The router returns the key itself when it is not in the table. A typo then produces a plausible looking address that answers 404, not an error at the point of the mistake. The same happens when a key exists but has fewer placeholders than the parameters you passed. The extra values vanish there.

wQS takes an array, whatever the type says

The declared type of the second argument allows a string. The value is still handed to http_build_query(), which rejects anything that is not an array or object with a TypeError. Never concatenate a question mark by hand either: the generated address may already carry parameters, and you would produce a second one.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.