Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

The pipe collection macro

A few days ago I blogged some code to fetch data from Packagist using our homebrew wrapper around the packagist API. To summarize the amount of downloads this code was used:

$totals = collect($packagist->getPackagesByVendor('spatie')['packageNames'])
    ->map(function ($packageName) use ($packagist) {
        return $packagist->findPackageByName($packageName)['package'];
    })
    ->reduce(function ($totals, $packageProperties) {
        foreach ($totals as $sumName => $total) {
            $totals[$sumName] += $packageProperties['downloads'][$sumName] ?? 0;
        }

        return $totals;
    }, ['daily' => 0, 'monthly' => 0, 'total' => 0]);

What was bothering me a lot was that foreach statement in the reduce call. My colleague Sebastian had the great idea to use the sum function on the collection. So the code could be rewritten as:

$totals = collect($packagist->getPackagesByVendor('spatie')['packageNames'])
        ->map(function ($packageName) use ($packagist) {
            return $packagist->findPackageByName($packageName)['package'];
        });

return [
    'daily' => $totals->sum('downloads.daily'),
    'monthly' => $totals->sum('downloads.monthly'),
    'total' => $totals->sum('downloads.total'),
];

That’s much better than the previous code. Our intent, summing up the amount of downloads, is much more clear. But wouldn’t it be great if the buildup of that last array would happen inside the collection pipeline? Adam Wathan‘s new book (which is great, go buy/read it if you haven’t done it already) mentions this little neat collection macro:

Collection::macro('pipe', function ($callback) {
    return $callback($this);
});

You can load this macro up inside the ApplicationServiceProvider or you could create a dedicated CollectionServiceProvider if you are planning on registering some more macros.

With the macro in place we can literally perform any function on our collection data. The summarization of the data can now be placed in the collection pipeline:

return collect($packagist->getPackagesByVendor('spatie')['packageNames'])
        ->map(function ($packageName) use ($packagist) {
            return $packagist->findPackageByName($packageName)['package'];
        })
        ->pipe(function($packageProperties) {
            return [
                'daily' => $packageProperties->sum('downloads.daily'),
                'monthly' => $packageProperties->sum('downloads.monthly'),
                'total' => $packageProperties->sum('downloads.total'),
            ];
        });

This is much cleaner than the original code. To me this is a great solution that’s very readable.

EDIT: There’s, no need anymore to add that macro yourself. pipe has been added to the Collection class.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band. He loves waffles and butterflies.