19 min read

Cross Join With Keys

Laravel ships with Arr::crossJoin and $collection->crossJoin helpers that generate the cartesian product (every combination) of each array. This is useful for building a single matrix from multiple arrays.

This is simple using the array method + spread operator but requires an extra step to use keys with collections.

Skip to the Collection macro section to get a snippet that allows you to cross join collections using keys.

Cross join assoc array (spread)

           $builds = [
   'php' => [7.3, 7.4, 8.0],
   'stability' => ['prefer-lowest', 'prefer-stable'],
   'laravel' => ['6.x', '7.x', '8.x']
 ];
 
 $matrix = Arr::crossJoin(...$builds);
        

Using Collections

If we use a similar approach with collections we do not get the same output.

           collect([
   'php' => [7.3, 7.4, 8.0],
   'laravel' => ['6.x', '7.x', '8.x']
 ])
->crossJoin([
   'stability' => ['prefer-lowest', 'prefer-stable']
 ]);
        

Oops, that’s not what we want.

Cross join without keys then map

In order to produce the same output as the original Arr::crossJoin we could collection and cross join the array values and map the keys back in at the end.

           collect([7.3, 7.4, 8.0])
     ->crossJoin(
         ['prefer-lowest', 'prefer-stable'],
         ['6.x', '7.x', '8.x']
     )
     ->map(fn($build) => [
         'php' => $build[0],
         'stability' => $build[1],
         'laravel' => $build[2],
     ]);
        

or by using array combine...

This would work but depending on how your original data is coming in it could get a bit messy. Let’s create a collection macro to behave in a similar way to the array functions.

Collection macro

Add the following macro to a service provider. It merges any given items with the existing collection and spreads into the array cross join function.

           php artisan make:provider CollectionServiceProvider
        

This allows us to collect our builds using crossJoinWithKeys

           collect([
   'php' => [7.3, 7.4, 8.0],
   'laravel' => ['6.x', '7.x', '8.x']
 ])
->crossJoinWithKeys([
   'stability' => ['prefer-lowest', 'prefer-stable']
 ]);
        

or even without arguments...

           collect([
   'php' => [7.3, 7.4, 8.0],
   'laravel' => ['6.x', '7.x', '8.x'],
   'stability' => ['prefer-lowest', 'prefer-stable']
 ])
->crossJoinWithKeys();
 ```s
 
 ```php title="Output"
 [
   [
     "php" => 7.3,
     "stability" => "prefer-lowest",
     "laravel" => "6.x",
   ],
   [
     "php" => 7.3,
     "stability" => "prefer-lowest",
     "laravel" => "7.x",
   ],
   // more items...
   [
     "php" => 8.0,
     "stability" => "prefer-stable",
     "laravel" => "7.x",
   ],
   [
     "php" => 8.0,
     "stability" => "prefer-stable",
     "laravel" => "8.x",
   ],
 ]
        

and still receive the same output