compare two collection arrays in laravel

Viewed 16910

I'm working with laravel, and I have two collection arrays that look something like this:

$collection1 = [
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 800],
];

$collection2 = [
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 400],
];

so I need to know if they are the same or not, comparing both collections. and if in some of the arrays one of the keys (or several) has a different value, then they will no longer be the same collections, for example in 1 collection the price of the second item has a value of 800 and the other collection of 400. there is Any native method of laravel collections to do this ?, or how can i do it with simple php arrays?

5 Answers

the laravel collections have a method called diff, with this method you can Get the items in the collection that are not present in the given items. and the items is $collection2 which can be an array or a collection so you can take the different items between this two collection like this

$collection1->diff($collection2);

it returns a Illuminate\Support\Enumerable class. you can get the items by call all() on this:

$collection = collect([1, 2, 3, 4, 5]);

$differentItems = $collection->diff([2, 4, 6, 8]);

$differentItems->all();

// [1, 3, 5]

this code belongs to laravel docs. https://laravel.com/docs/7.x/collections#method-diff . and at the end you can turn $differentItems to boolean. like this:

$collection = collect([1, 2, 3, 4, 5]);
$differentItems = $collection->diff([2, 4, 6, 8]);
$differentItems->isEmpty();
// return false

$collection = collect([1, 2, 3, 4, 5]);
$differentItems = $collection->diff($collection);
$differentItems->isEmpty();
// return true

more links https://laravel.com/api/7.x/Illuminate/Support/Collection.html#method_diff https://laravel.com/api/7.x/Illuminate/Support/Enumerable.html

Follows a comparison function:

function compareCollections($c1, $c2) {
    // If the colletions have different sizes we return false:
    if (count($c1) != count($c2)) {
        return false;
    }

    // The collections have the same size, we check element by element:
    foreach($c1 as $item) {

        // We find the current element in $c1 in $c2:
        $itemToCompare = array_filter($c2, function ($compareItem) use ($item) {
            return ($compareItem['id'] == $item['id']);
        });

        // If we did not find the element in $c2, the collections are different:
        if (empty($itemToCompare)) {
            return false;
        }

        $itemToCompare = current($itemToCompare);

        // We now use PHP to check the element keys:
        $diff = array_diff_key($item, $itemToCompare);

        // If there is a different, return false:
        if (!empty($diff)) {
            return false;
        }       
    }

    // If everything is ok until here, the collections are the same:
    return true;
}

And a test:

$collection1 = [
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 800],
];

$collection2 = [
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 400],
];

$collection3 = [
    ['id' => 1, 'name'=> 'tv', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3],
];

var_dump(compareCollections($collection1, $collection2)); // true
var_dump(compareCollections($collection1, $collection3)); // false

So serialize each element first, and then compare.

    $serialize1 = $collection1->map(function ($item) {
        return serialize($item);
    });

    $serialize2 = $collection2->map(function ($item) {
        return serialize($item);
    });

dd($serialize1->diff($serialize2)->isEmpty());

As far as I know, as your collection items are array, there is no native simple Laravel way to do so and you should write a function to compare them:

So, assuming that, you have:

$collection1 = collectt([
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 800],
]);

$collection2 = collect([
    ['id' => 1, 'name'=> 'phone', 'quantity' => 1, 'price' => 1200],
    ['id' => 2, 'name'=> 'tv', 'quantity' => 3, 'price' => 400],
]);

You can for example compare them by:

    private function collectionsAreEqual($collection1, $collection2)
    {
        if ($collection1->count() != $collection2->count()) {
            return false;
        }
        //assuming that, from each id, you don't have more that one item:
        $collection2 = $collection2->keyBy('id');
        foreach ($collection1->keyBy('id') as $id => $item) {
            if (!isset($collection2[$id])) {
                return false;
            }
            //your items in the collection are key value arrays 
            // and can compare them with == operator
            if ($collection2[$id] != $item) {
                return false;
            }
        }
        return true;
    }
    dd(collectionsAreEqual($collection1, $collection2));

This method only works if both collections have the same key order.

By default, Laravel has a diffAssoc method which actually compares the single item of a collection. If you want to compare two collection arrays then you have to create your own solution.

Here is my solution where I create(or you can say extend) a collection method.

First I map each item and serialize the item and then do the same with another collection. Get the difference by using the diffAssoc method and unserialize the final output.

AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Collection::macro('diffAssocMultiple', function ($anotherCollection) {
            /* @var $this Collection */
            return $this->map(function($arr) {
                return serialize($arr);
            })->diffAssoc($anotherCollection->map(function($arr) {
                return serialize($arr);
            }))->map(function($arr) {
                return unserialize($arr);
            });
        });
    }
}

Usage

$diff = $collectionOne->diffAssocMultiple($collectionTwo);

Note that, this new method returns another collection(non-zero based). and this doesn't have the 'all()' method as diffAssoc has. If you want a zero-based index array then use the values function along with.

$diff = $collectionOne->diffAssocMultiple($collectionTwo)->values();
Related