The each callback

An argument present in many Perch functions is an options array:

perch_collection($collection_key='', $options_array=[], $return=false);

You can add an each option to the array when working with the majority of these functions. The each option enables you to add a callback function.

What is a callback function?

It is likely you’ve encountered callback functions (at least the term) before when working with Javascript, PHP or any programming language. In short, a callback function is a function you pass to another function (as an argument) to execute.

Many native PHP functions use callback functions. One example is array_filter(), which filters the elements of an array using the callback function you provide:

$array = [5, 7, 19, 26];

$adults = array_filter($array, function($array_element){
    return $array_element > 18;
});

Callback functions may be expected to accept a number of arguments and/or return a value. In the example above the callback function takes an array element as an argument and returns a boolean value.

What does the each callback function do?

Perch allows you to process each item (returned by the Perch function) before it goes to the templating stage. Each item is available to you as an associative array.

perch_collection('Articles', [
    'each' => function($article) {
        // $article represents a single article

        // modify the array as you wish

        // you must return the article
        return $article;
    }
]);

I personally would use $item instead of $article as the argument, but that’s down to your preference.

There are a lot of uses to this. You may want to add a variable based on an existing value

perch_collection('Events', [
    'each' => function ($item) {
        $now = date('Y-m-d H:i:s');
        $item['past'] = strtotime($now) > strtotime($item['date']);
        return $item;
    }
]);
<perch:if not-exists="past">
    <!--* not a past event; add a link for tickets *-->
</perch:if>

And while Perch has a number of tag attributes and template filters, the each callback is another option for modifying values:

perch_collection('Team', [
    'each' => function ($item) {
        // capitalize the first character of each word
        $item['name'] = ucwords($item['name']);
        return $item;
    }
]);

You can also reverse the order of repeater items:

perch_collection('Actors', [
    'each' => function ($item) {
        // reverse the order of films <perch:repeater id="films" label="Filmography"></perch:repeater>
        $item['films'] = array_reverse($item['films']);
        return $item;
    }
]);

Working with existing values

If you want to access an existing value, it is a good practice to check if it exists first:

perch_collection('Unicorns', [
    'each' => function ($item) {
        if(isset($item['name']) && !empty($item['name'])) {
            // name exists
        }

        return $item;
    }
]);

Use it to improve performance

The each callback function is only executed when at least one item is found:

perch_collection('Blog', [
    'each' => function ($item) {
        // this will only execute if at least one item is found
        return $item;
    }
]);

Let’s say we are trying to fetch a single item for a detail page in a list/detail pattern:

// url is /post.php?s=some-slug
perch_collection('Blog', [
    'template' => 'blog/_detail',
    'filter' => 'slug',
    'value' => perch_get('s'),
    'count' => 1,
]);

It is not uncommon to want to pass rendered HTML to the template. It could be something you want to fetch dynamically and display within the article or sidebar. Many may approach this by getting the items first, then passing it as a variable:

// get a random product to display in the sidebar
$product_c2a = perch_collection('Products', [
    'template' => 'products/_c2a_in_sidebar',
    'sort' => '_date',
    'sort-order' => 'RAND',
    'count' => 1,
], true);

perch_collection('Blog', [
    'template' => 'blog/_detail',
    'filter' => 'slug',
    'value' => perch_get('s'),
    'count' => 1,
    'data' => [
        'product_c2a' => $product_c2a,
    ]
]);
<perch:content id="product_c2a" type="hidden" html>

The above means the page fetches a random product on every page visit regardless whether our dynamic detail page is actually going to display an article or respond with a 404. While this may be a minor optimisation, you can ensure that you only fetch a product if your perch_collection('Blog') call returns an item by using a callback function:

perch_collection('Blog', [
    'template' => 'blog/_detail',
    'filter' => 'slug',
    'value' => perch_get('s'),
    'count' => 1,
    'each' => function($item) {
        $item['product_c2a'] = perch_collection('Products', [
            'template' => 'products/_c2a_in_sidebar',
            'sort' => '_date',
            'sort-order' => 'RAND',
            'count' => 1,
        ], true);

        return $item;
    }
]);

Reusing the same callback

I find in some projects that I need to reuse the same callback function across the codebase as I may query the same collection in many places. To make things more maintainable and my life easier, I often build a small Perch app and some runtime functions so I can use them whenever I call perch_collection():

perch_collection('Events', [
    'each' => function($item) {
        $item = myapp_events_callback($item);

        // optionally continue to modify the array here if it is a one-off

        return $item;
    }
]);

Or if you like a shorter version:

perch_collection('Events', [
    'each' => 'myapp_events_callback',
]);
link