Skip to content

Kirby 3.9.8

Grouping collections

Often, we want to group pages or files by various criteria, for example, print a list of blog posts grouped by year, group upcoming events by month, a list of authors by character in the alphabeth, a set of images by photographer and so on. With Kirby's groupBy() and group($callback) methods, this is pretty easy.

Let's look at some examples to fuel your imagination.

The field you want to group by must exist and contain a value. If necessary, filter your collection before using the methods to prevent errors.

Simple grouping by field values

The most straighforward way to group collections is by using the groupBy() method. Let's suppose our imaginary collection of projects had a year field and we wanted to group these projects by year.

// get a grouped collection
$years = page('projects')->children()->listed()->groupBy('year');

This gives us a a bi-dimensional collection with items for each year.

Now let's see how to output this collection object:

<?php
// first we loop through the years and echo the year
foreach($years as $year => $itemsPerYear): ?>
    <h2><?= $year ?></h2>
    <ul>
      <?php
      // then we loop through all the items for each year
      foreach($itemsPerYear as $item) : ?>
      <li><?= $item->title() ?></li>
      <?php endforeach; ?>
    </ul>
<?php endforeach ?>

As you can see, we use two foreach loops here, one for the criterium we group by (here the year), and then a second one for the items that belong to each group.

Complex grouping

Sometimes, the simple groupBy() method has its limits. For example, suppose in the above example we didn't have a simple year field, but a normal date field, but still wanted to group by year. That wouldn't be possible. The group() method with a callback to the rescue.

<?php

// function that returns the formatted date
$callback = function($p) {
  return $p->date()->toDate('Y');
};
// group items using $callback
$groupedItems = page('projects')->children()->listed()->group($callback);

// output items by year
foreach($groupedItems as $year => $itemsPerYear): ?>
    <h2><?= $year ?></h2>
    <ul>
      <?php foreach($itemsPerYear as $item) : ?>
      <li><?= $item->title() ?></li>
      <?php endforeach; ?>
    </ul>
<?php endforeach ?>

We first define the callback function, which returns the date in year format. Then we pass that function as parameter to the group() method.

Here is another interesting example that allows us to group by points in time or time ranges, to which we assign category names to group by like "today", "this week", or "this month".

<?php
$groups = $page->children()->listed()->group(function($article) {
  if($article->date()->toDate('Y-m-d') == date('Y-m-d')) return 'today';
  if($article->date()->toDate() > strtotime('-7 day'))   return 'this week';
  if($article->date()->toDate() > strtotime('-1 month')) return 'this month';
  return 'older articles';
});
?>
<?php foreach($groups as $description => $items): ?>
  <h2><?= $description ?></h2>

  <?php foreach($items->sortBy('clicks', 'desc') as $item): ?>
    <h2><?= $item->title()->html() ?></h2>
  <?php endforeach ?>
<?php endforeach ?>

Combining methods for more complex grouping scenarios

We can make things even more complex. Have a look at this example:

$groupedItems = page('events')->children()->listed()->map(function($p) {
    $p->eventDate = $p->from()->toDate('d.m.Y') . ' - ' . $p->to()->toDate('d.m.Y');

    return $p;
})->groupBy('eventDate');

In this example we want to group our items by a combination of two date fields, from and to. We use the map() method with a callback to create a "virtual" field called eventDate, then we pass this field to the groupBy() method.

Grouping files

We can use both group() and groupBy() with files.

Example for groupBy()

In this example, we group all images of all subpages by the photographer field and output a small thumbnail for each group:

<?php
$groups = $page->children()->images()->groupBy('photographer');
foreach($groups as $photographer => $items):
?>

<h2><?= $photographer ?></h2>
  <ul>
    <?php foreach($items as $item): ?>
    <li><?= $item->resize(100) ?></li>
    <?php endforeach ?>
  </ul>
<?php endforeach ?>

Example with group($callback)

The group()with callback example works in the same way as with pages collections:

<?php
$callback = function($f) {
  if($f->date() && $f->date()->isNotEmpty()) {
    return $f->date()->toDate('Y');
  } else {
    return 'undated';
  }
};
$groupedFiles = page('projects')->children()->listed()->images()->group($callback);

foreach($groupedFiles as $year => $images): ?>
  <h2><?= $year ?></h2>
  <?php
    // loop through the images
    foreach($images as $image): ?>
    <img src="<?= $image->url() ?>" alt="">
  <?php endforeach ?>
<?php endforeach ?>