Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

The pipe collection macro

Original – by Freek Van der Herten – 2 minute read

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.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

What are your thoughts on "The pipe collection macro"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.