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.
What are your thoughts on "The pipe collection macro"?