Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Collection Sum Multiple Columns in Collection

I have been reading about collections in Laravel. Currently I am aware of how to use it however I have a question about the sum() function.

According to the docs it can be used like this:

collect([1, 2, 3, 4, 5])->sum();

And also like this:

$collection = collect([
    ['name' => 'JavaScript: The Good Parts', 'pages' => 176],
    ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096],
]);

$collection->sum('pages');

Which is fine and I understand, however now I have an array with nested objects and I would like to know how I can achieve something like this in laravel:

$collection = collect([
    ['name' => 'JavaScript: The Good Parts', 'pages' => 176, 'price' => 100.00],
    ['name' => 'JavaScript: The Definitive Guide', 'pages' => price, 'pages' => 150.00],
]);

$collection->sum(['pages', 'prices']);

Which would obviously return either an array like this:

[1272, 250]

or on collection / object like this:

{"total_pages": 1272, "total_price": 250}

in short I would like to know how I can sum and return the results of multiple columns in a laravel collection instead of doing this twice:

$collection->sum('pages');
$collection->sum('price');

Since i assume that would cause multiple loops over the collection or am I wrong?

like image 544
user3718908x100 Avatar asked Oct 25 '18 15:10

user3718908x100


2 Answers

You would need to use the pipe method to pass the collection to a callback. Within the callback you could achieve what you're wanting to do.

$collection = collect([
    ['name' => 'JavaScript: The Good Parts', 'pages' => 176, 'price' => 100.00],
    ['name' => 'JavaScript: The Definitive Guide', 'pages' => 314, 'price' => 150.00]
]);

$result = $collection->pipe(function ($collection) {
  return collect([
    'total_pages' => $collection->sum('pages'),
    'total_price' => $collection->sum('price'),
  ]);
});
like image 162
Mark Avatar answered Nov 19 '22 20:11

Mark


Looking at the code behind the sum() function, we can see that internally $collection->reduce() is used to collect the data. So we should be able to use the same code to achieve what you are looking for:

return $collection->reduce(function ($result, $item) {
    if ($result === null) {
        $result = [
            'pages' => 0,
            'price' => 0,
        ];
    }

    $result['pages'] += $item['pages'];
    $result['price'] += $item['price'];

    return $result;
}, 0);

To get a more generic solution out of it, you could also create a macro on the Collection class within one of your service providers:

Collection::macro('sumMultiple', function ($columns) {
    return $this->reduce(function ($result, $item) use ($columns) {
        if ($result === null) {
            foreach ($columns as $column) {
                $result[$column] = 0;
            }
        }

        foreach ($columns as $column) {
            $result[$column] += $item[$column];
        }

        return $result;
    }, 0);
});

You can then use the new function with $collection->sumMultiple(['pages', 'price']) and you will receive a result like ['pages' => 123, 'price' => 234].

Pleas be aware that sumMultiple does not support nested arrays at this moment (i.e. dot notation access to fields does not work).

like image 3
Namoshek Avatar answered Nov 19 '22 20:11

Namoshek