TranslateCI makes human translations for your Laravel app as easy as using Git. Just push your code, and TranslateCI will take care of the rest. It scans for untranslated phrases, and uses professional human translators to support over 70 possible language pairs. When the translations are done, usually in less than 24 hours, TranslateCI makes a PR back to your repository. Check it out today!

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.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks 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