fylzero's avatar
Level 67

Is it possible to "join" two collections based on a key?

Is it possible to take this...

$col1 = collect([
    [
        'slug' => 'merp',
        'label' => 'Merp',
        'desc' => 'Merp of merp',
    ],
    [
        'slug' => 'derp',
        'label' => 'Derp',
        'desc' => 'Derp derpington',
    ],
    [
        'slug' => 'whoa',
        'label' => 'Whoa There',
        'desc' => 'Hang on just a second',
    ],
]);

...and this...

$col2 = collect([
    [
        'id' => 1,
        'name' => 'Steve',
        'status_slug' => 'merp',
        'secret' => '12345',
    ],
    [
        'id' => 2,
        'name' => 'Joe',
        'status_slug' => 'derp',
        'secret' => '67890',
    ],
]);

...and have the end result be this...

=> Illuminate\Support\Collection {
     all: [
        [
            "id" => 1,
            "name" => "Steve",
            "status_slug" => "merp", // matched on slug
            "secret" => "12345",
            "slug" => "merp", // matched on status_slug
            "label" => "Merp",
            "desc" => "Merp of merp",
        ],
        [
            "id" => 2,
            "name" => "Joe",
            "status_slug" => "derp", // matched on slug
            "secret" => "67890",
            "slug" => "derp", // matched on status_slug
            "label" => "Derp",
            "desc" => "Derp derpington",
        ],
    ],
   }

I'd assume I can just map or each this somehow but am struggling to get there.

0 likes
8 replies
MichalOravec's avatar
Level 75

@fylzero This could work

$result = $col1->map(function ($item) use ($col2) {
    if ($col2->contains('status_slug', $item['slug'])) {
        return $col2->firstWhere('status_slug', $item['slug']) + $item;
    }
})->filter();
fylzero's avatar
Level 67

@michaloravec Appreciate the reply! Only thing is that this approach seems to fall apart once more data is added to $col2. I'm still playing around with it but struggling to get this to work in a flexible way. If you have any other suggestion, please let me know. Thanks again.

Update: Your answer lead me here...

$result = $col2->map(function ($user) use ($col1) {
    foreach ($col1 as $label) {
        if ($user['status_slug'] == $label['slug']) {
            return $user + $label;
        }
    }
});
MichalOravec's avatar

@fylzero What do you mean? They can connect only with slug, if there is more rows in $col2 and they don't match by slug you still get only matched rows.

fylzero's avatar
Level 67

@michaloravec Apologize for not being clear here... suffice it to say I knew I needed to map on $col2 for this to work the way I needed. Your answer got me pretty close. I basically wanted this to act like a "left join" not only a single matched result. What I was seeing by adding more data in $col2 before is that it would only match a single result then ignore the rest if there were multiple entries with the same slug. By mapping on $col2 and matching against each label in $col1 this gives the result I was seeking.

MichalOravec's avatar

@fylzero It's same what I posted before, if they don't match it return null, if you want that so just remove filter()

$result = $col1->map(function ($item) use ($col2) {
    if ($col2->contains('status_slug', $item['slug'])) {
        return $col2->firstWhere('status_slug', $item['slug']) + $item;
    }
});
MichalOravec's avatar

@fylzero Be the way if you want to use your code you have mistakes in the keys

$result = $col1->map(function ($user) use ($col2) {
    foreach ($col2 as $label) {
        if ($user['slug'] == $label['status_slug']) {
            return $user + $label;
        }
    }
});
fylzero's avatar
Level 67

@michaloravec I've tested both of your suggestions and if I add a 4th entry to $col2 for example...

[
    'id' => 4,
    'name' => 'James',
    'status_slug' => 'merp',
    'secret' => '67890',
],

Both of the code blocks you provided truncate the last entry. The one I posted seems to work.

Also, I do not believe I have any mistakes. My code is working. The last block you posted does not work. Try testing these, you'll see.

I'm basically just thinking about this as a way to replace a lookup table join on enums, if that helps.

MichalOravec's avatar

@fylzero You used $col2->map and I used $col1->map so logic is just opposite.

So in that case

$result = $col2->map(function ($item) use ($col1) {
    if ($col1->contains('slug', $item['status_slug'])) {
        return $item + $col1->firstWhere('slug', $item['status_slug']);
    }
});

it's same as

$result = $col2->map(function ($user) use ($col1) {
    foreach ($col1 as $label) {
        if ($user['status_slug'] == $label['slug']) {
            return $user + $label;
        }
    }
});

You can see it here https://laravelplayground.com/#/snippets/6fb7192e-2e85-4d3f-9886-fe2bbe95e535

Please or to participate in this conversation.