🚀 A new era: Kirby 4 Get to know
Skip to content

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. Let's say our page collection has a start date (from) and an end date (to), and we want to group all items with the same start and end date.

For this case, we can either use a callbackback like above that returns the combined date, or we create a page model method called eventDate. In this case, let's use a page model.

Since our children use the event blueprint, we create a model called EventPage:

/site/models/event.php
<?php
class EventPage extends Page
{
    public function eventDate()
    {
        return $this->from()->toDate('d.m.Y') . ' - ' . $this->to()->toDate('d.m.Y');
    }
}

Now we can group by this method:

$groupedItems = page('events')->children()->listed()->groupBy('eventDate');

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 ?>

Author