Skip to content

Kirby 3.9.8

Sorting

5, 4, 3, 2, 1…, sorting content is an everyday developer's task when dealing with all sorts of content collections: articles, events, images or similar data needs to be sorted, for example, by date or title, in descending or ascending order, or even by a more complicated sorting algorithm.

Kirby's API comes with a built-in method for sorting: the sortBy() method. This method can be used with all sorts of collections (pages, files, users, roles, translations, structure field items etc.).

The sortBy($field, $direction = 'desc', $method = SORT_NATURAL | SORT_FLAG_CASE) method accepts three parameters:

$field

The content field or method you want to sort by, usually something like the date, the title etc.

$direction

The direction of your sort order, either desc for descending, or asc for ascending.

$method

The method takes an optional third parameter, the sort flag. The default is SORT_NATURAL | SORT_FLAG_CASE (human natural case-insensitive order), but you can override this e.g. with SORT_NUMERIC when sorting numbers or with SORT_LOCALE_STRING to sort according to the configured locale settings (or the language's locale settings if you use a multi-language setup). PHP also supports other sort flags you can use.

Kirby also provides a sort() method, which is an alias and does exactly the same as sortBy(). Use whichever you prefer.

Sorting by a single field

Let's assume you have a list of products, each with a price field. By default, the list should be sorted by price descending, with the most expensive product first:

<?php
// let's fetch all visible children from the products page and sort them by their price field
$products = page('products')->children()->listed()->sortBy('price', 'desc');

// let's loop through the collection and output the product name and price
foreach($products as $product): ?>
  <h2>
    <?= $product->title()->html() ?>
    <small>$<?= $product->price()->html() ?></small>
  </h2>
<?php endforeach ?>

Here, we pass two parameters to the sortBy() method: the field we want to use for sorting (price), and the sort order, here desc. If you don't pass a sort order parameter, the default is ascending order.

An alternative would be to skip the second parameter and use Kirby's flip() method instead to achieve exactly the same as above (however a bit slower for large data sets):

$products = page('products')->children()->listed()->sortBy('price')->flip();

Sorting by multiple fields

Sometimes, we don't want to limit sorting to a single field. Let's assume we wanted to sort a list of books by the authors' last and first names:

$books = page('books')->children()->listed()->sortBy('lastname', 'asc', 'firstname', 'asc');

Here, we have pass two sort fields with their sort order as parameters. Authors will now be sorted by lastname first, then by firstname. You can add more criteria to sort by if necessary.

Sorting by date

Another common use case for sorting is a list of blog articles sorted by date in reversed order, so that the most recent article appears first in the list. If your date field uses the default format yyyy-mm-dd, you can use the same syntax that we saw above:

<?php
// let's fetch all visible children from the blog page and sort them by their date field
$articles = page('blog')->children()->listed()->sortBy('date', 'desc');

// let's loop through the collection and output date and title
foreach($articles as $article): ?>
  <span><?= $article->date()->toDate('Y-m-d') ?></span>
  <h2><?= $article->title()->html() ?></h2>
<?php endforeach ?>

However this will break with different formats like dd.mm.yyyy as the sorting from left to right will then first sort by day, ignoring the year. In this case you can use a callback to parse each date into a common format (a timestamp) before they are sorted:

<?php
$articles = page('blog')->children()->listed()->sortBy(function ($page) {
  return $page->date()->toDate();
}, 'desc');

Kirby will pass each page to the callback and will use the return value to determine the sorting order.

Sorting structure field entries

The same sorting methods outlined above can also be used with structure field entries - if you use the toStructure() method.

An example: Suppose we have defined a structure field with three fields in our events blueprint:

&quot;/site/blueprints/pages/events.yml
fields:
  events:
    label: events
    type: structure
    fields:
      title:
        label: Event title
        type: text
      date:
        label: Event date
        type: date
      location:
        label: Event location
        type: text

Let's fetch the events, sort them by date and loop through them to output their content.

<?php
$events = page('events')->events()->toStructure();
$sortedEvents = $events->sortBy(function ($page) {
  return $page->date()->toDate();
}, 'asc');

foreach($sortedEvents as $event): ?>
  <span><?= $event->date()->toDate('Y-m-d') ?></span>
  <h2><?= $event->title()->html() ?></h2>
  <?= $event->location()->kirbytext() ?>
<?php endforeach ?>

If you use yaml() to create an array of events instead of a collection, and you want to sort that array, you can use the A::sort() method from the toolkit, or check out the different ways to sort arrays in the PHP manual.

Custom sorting

Let's look at a sorting scenario that is beyond the usual sorting possibilities. Consider a structure field like this:

products:
  label: products
  type: structure
  fields:
    productname:
      label: Product Name
      type: text
    size:
      label: Size
      type: text

Now, instead of numbers, our sizes field contains sizes like S, M, L, XL, XXL etc. Obviously, we cannot sort them alphabetically or otherwise, because they have no inherent order. So, what can we do? The solution here is to map a sorting value to every real value using the map() method.

// fetch the products from the structure field
$products = $page->products()->toStructure();

// map an order field to each item of the collection
$products = $products->map(function($item) {
  // array that keeps the order of sizes
  $sizes = ['S', 'M', 'L', 'XL', 'XXL'];

  // get the order number from the array based on the item's size value
  $item->order = array_search($item->size()->value(), $sizes);

  return $item;
});

// finally, sort by order
$products = $products->sortBy('order', 'asc');

Of course, we can write all that stuff a bit shorter:

$products = $page->products()->structure()->map(function($item) {
  $sizes = ['S', 'M', 'L', 'XL', 'XXL'];
  $item->order = array_search($item->size()->value(), $sizes);
  return $item;
})->sortBy('order', 'asc');

dump($products);

The map() method is really useful for all types of scenarios, so keep it in mind for next time you come across a problem that you can't solve with a simple sort.

Sorting with Page Models

We cannot only use fields that exist in our content files for sorting. In fact, we can use all the methods the different collection classes (pages, files, etc.) provide (and that make sense to sort by). For example, you can sort by the template that is used by a page or file, if that makes sense for your use case. Or you can sort by a custom page model.

Let's assume for a moment that we wanted to sort our project pages by the number of images they have. Kirby doesn't have a native method that counts our images. We could now use the map() method as outlined above, but we want to have some more fun and do something different now and create a Page Model with a method that returns the number of images.

The page model

/site/models/project.php
<?php

class ProjectPage extends Page {
  public function countImages() {
      return $this->images()->count();
  }
}

I won't go into details with Page Models here. You can read more about how to create them in the docs

Now we can use this method to sort by:

$projects = $page->children()->listed()->sortBy('countImages', 'desc');

Sorting files

Sorting files works just like with pages. We can sort by any file meta data field, or using built-in methods like modified(), filename() etc.

Some examples:

// sort by manual sort field
$files = $page->files()->sortBy('sort');

// sort by caption
$files = $page->files()->sortBy('caption', 'desc');

Note that the default sorting order of files is according to their order in the file system.

Sorting users

Users can also be sorted by any field in their profile or by inherent properties like role, language etc.

Some examples:

// sort by manual sort field
$users = $kirby->users()->sortBy('role');

// sort by filename
$users = $kirby->users()->sortBy('email');

// sort by caption
$usera = $kirby->users()->sortBy('language', 'desc');

For files and users (and all the other stuff we can sort) we could also go to extremes, but let's keep it dry and finish here.