Spiria logo.

Tutorial: Routing in ProcessWire

April 29, 2021.

Routing is an important part of the navigation logic of a website. Programmers who work in frameworks such as Laravel or Symfony have to code it manually, unlike in ProcessWire, where the dynamics surrounding URLs are built up as templates, which construct pages in real-time. Like all CMSs, ProcessWire supports the various CRUD (Create, Read, Update, Delete) related features and is able to handle AJAX calls, to clean up URLs, etc.

First, let’s go over some of ProcessWire’s features:

  1. Each page of a site is an object simply called a page, which has its place in a tree structure.
  2. Pages are controlled by templates composed of fields.
  3. The tree structure thus created establishes the route of each page.

The diagram below illustrates the various options for programming the route.

The base structure

First of all, a “fixed” URL is inherent to a page. Thus, the home page is always “/” or “/fr” (given a second language, French in this case). Everything in black in the illustrated URLs is fixed. The language is an integral part of the base structure and is set on page 1, the home page, in the “Settings” section.

Note that the home page will always be “/”, even if you specify a suffix like “en”. The default strategy lists the initial domain name as https://www.site.com instead of https://www.site.com/en.

The second option appears under Modules/Configuration/LanguageSupportPageNames.

Though this second option is feasible, we do not recommend it, for the simple reason that if someone types https://www.site.com and is redirected to https://www.site.com/en, search engines like Google will consider this as two different addresses with the same content, and suggest that you remove this unnecessary redirection. Duplication is bad!

The standard route

The standard route is the one that follows the tree structure. The programmer does not have to do anything. As the page is based on a template, Processwire templates are used instead of the PHP file.

The “children”

Technically speaking, everything in ProcessWire is a child of the root, but if you ignore the root, the child path is shown in green in the illustration. The children follow the same standard route logic: they are also fixed routes. ProcessWire takes care to build this with what you have entered in the Parameters section of the page. The children often belong to another model, and therefore behave differently from the parent.

Let us note here a finesse of ProcessWire in multilingual mode. Let us assume the address https://www.site.com/en/blog. If we type https://www.site.com/fr/blog, ProcessWire will recognise that we want to see the French version of the same page and will automatically change to https://www.site.com/fr/blogue.

Automatic redirections

By activating the PagePathHistory module integrated in ProcessWire, we ensure that if we change the name of the route in the Settings tab, the redirections automatically follow.

 Pagination

Let’s get into the more dynamic part of routing. The most common example is pagination. This is common to sites with too many child pages.

Pagination is enabled at the template level on the URLs tab, which groups the strategies discussed here..

ProcessWire collects the required pages according to the programmed selection with a code similar to this:

$articles = pages()->find("template=blog_article,sort=-date_publication, limit=36");
$pagination = page()->renderPagination($articles);

[...]

<div><?=$pagination?></div>

The $input object or input()

ProcessWire processes the information from a URL by first assigning it to the object $input. Place the following code in any template and type in an existing address on your site:

echo '<pre>';
var_dump( input() );
echo '</pre>';
exit;

You will get the structure of the $input object which should normally be empty, except perhaps for a list of cookies.

object(ProcessWire\WireInput)#314 (6) {
  ["get"]=>
  array(0) {
  }
  ["post"]=>
  array(0) {
  }
  ["cookie"]=>
  array(7) {
    ["_ga_S0T7N638Z9"]=>
    string(33) "GS1.1.1619658225.3.1.1619659512.0"
    ["_fbp"]=>
    string(29) "fb.0.1616764431218.1660133326"
    ["_ga"]=>
    string(27) "GA1.2.2052500784.1618595859"
    ["_gid"]=>
    string(26) "GA1.2.107615638.1619647242"
    ["wires"]=>
    string(26) "k88d457vhbkbt8kaerrqi5nt2e"
    ["wires_challenge"]=>
    string(32) "bjG/6lhb8sptS732AoFvgARQ7TDhH6ie"
    ["chosenlanguage"]=>
    string(2) "fr"
  }
  ["whitelist"]=>
  NULL
  ["urlSegments"]=>
  array(0) {
  }
  ["pageNum"]=>
  int(1)
}

Let’s visit an address with the pagination in the style shown in point 3 of the illustration. We get the same $input object, but the pageNum part is filled in. Simple as that! ProcessWire takes care of finding the elements related to our programming.

object(ProcessWire\WireInput)#314 (6) {
  ["get"]=>
  array(0) {
  }
  ["post"]=>
  array(0) {
  }
  ["cookie"]=>
  NULL
  ["whitelist"]=>
  NULL
  ["urlSegments"]=>
  array(0) {
  }
  ["pageNum"]=>
  int(4)
}

GET and POST requests

GET or POST requests are also placed in the $input object. They can come from a form or an Ajax call. The GET request illustrated in point 4 results in the following object:

object(ProcessWire\WireInput)#314 (6) {
  ["get"]=>
  array(2) {
    ["album"]=>
    string(1) "2"
    ["criteria"]=>
    string(4) "blue"
  }
  ["post"]=>
  array(0) {
  }
  ["cookie"]=>
  NULL
  ["whitelist"]=>
  NULL
  ["urlSegments"]=>
  array(0) {
  }
  ["pageNum"]=>
  int(1)
}

At this point, it is up to the programmer to properly intercept the URL call, which involves sanitizing the information with the sanitizer() class or other PHP method. The process is the same with POST calls. Please refer to my article on forms for an example of code.

Segments

Segments are elements of a URL that are not part of the base URL, separated by a slash "/". These types of URLs are often used in the context of information exchange, such as payment, calls to another part of the site, or to an external site. The ProcessWire documentation clearly describes this. Not surprisingly, the information is found in the $input object.

object(ProcessWire\WireInput)#315 (6) {
  ["get"]=>
  array(0) {
  }
  ["post"]=>
  array(0) {
  }
  ["cookie"]=>
  NULL
  ["whitelist"]=>
  NULL
  ["urlSegments"]=>
  array(2) {
    [1]=>
    string(4) "view"
    [2]=>
    string(2) "1234567"
  }
  ["pageNum"]=>
  int(1)
}

An example of code would look like this:

switch (input()->urlSegment(1)) {
    case "view":
        $url2 = (int)input()->urlSegment(2);
        [...]
        break;
    case "partial":
        $url2 = sanitizer()->text(input()->urlSegment(2));
        if (isset($url2) && $url2 > 0) {
            $status = UPDATING;
        [...]            

A URL segmenting method parses $input into the appropriate urlSegment. For greater security, the use of segments can be restricted in the same URLs section described above with the added bonus of using regular expressions.

In conclusion

Once again, ProcessWire shines through the power and simplicity of its API. It’s easy to build a REST context with a few lines of code. From online payment to Ajax process integration to communication between sites, our ally is the $input object.

I have barely touched on the properties and methods of this object, as there is much to say about the other objects that help in routing. You’ll find that reading the CMS API is worth your while.