Skip to content

Kirby 3.5.7.1

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 methods 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_REGULAR) 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_REGULAR, but if you want to sort by numeric values, you can use SORT_NUMERIC or other sort flags instead.

Sorting by a single field

Probably the use case most often needed is simple sorting of blog articles by date in reversed order, so that the most recent article appears first in the list:

<?php
// let's fetch all visible children from the blog page and sort them by their date field
$articles = page('blog')->children()->listed()->sortBy(function ($page) {
  return $page->date()->toDate();
}, '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 ?>

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

An alternative would be skip the second parameter and use Kirby's flip() method instead to achieve exactly the same as above:

$articles = page('blog')->children()->listed()->sortBy(function ($page) {
  return $page->date()->toDate();
})->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:

<?php
$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 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.

<?php
// 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 maps every real value to a sorting number
    $sizes = ['S'=>'0','M'=>'1','L'=>'2','XL'=>'3', 'XXL'=>'4'];

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

    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'=>'0','M'=>'1','L'=>'2','XL'=>'3', 'XXL'=>'4'];;
    $item->order = $sizes[$item->size()->value()];
    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.