mikefolsom's avatar

Flatten Eloquent Collection with Relationships

I'm looking for a simple way to flatten an Eloquent collection containing related model data, (specifically to display in a Tinkerwell table output). For example, take this query:

use App\Models\Organization;

$orgs = Organization::query()
  ->select("id", "name")
  ->with("profile:organization_id,ein")
  ->take(2)
  ->get();

This yields something like

Illuminate\Database\Eloquent\Collection {
    all: [
      App\Models\Organization {
        id: 1,
        name: "ABC Corp",
        profile: App\Models\OrganizationProfile {
          organization_id: 1,
          ein: "80-XXXXXXX",
        },
      },
      App\Models\Organization {
        id: 2,
        name: "XYZ Corp",
        profile: App\Models\OrganizationProfile {
          organization_id 2,
          ein: "85-XXXXXXX",
        },
      },
    ],
  }

What I am currently doing to get down to a single dimension:

$orgs->map(
  fn($org) => [
    "id" => $org->id,
    "name" => $org->name,
    "ein" => $org->profile->ein
  ]
);

Which gives me the desired result:

Illuminate\Support\Collection {#2401
    all: [
      [
        "id" => 1,
        "name" => "ABC Corp",
        "ein" => "80-XXXXXXX",
      ],
      [
        "id" => 2,
        "name" => "XYZ Corp",
        "ein" => "85-XXXXXXX",
      ],
    ],
  }

This works great, but it is a trivial example. Manually maping and building an associative array gets really tedious when dealing with multiple relationships and dozens of data columns/attributes. I believe there must be an easier way to flatten the data and preserve the keys...

0 likes
2 replies
LaryAI's avatar
Level 58

To flatten an Eloquent collection with relationships and preserve the keys, you can use the map method along with the toArray method. Here's an example:

$flattened = $orgs->map(function ($org) {
    return array_merge($org->toArray(), $org->profile->toArray());
});

This code will iterate over each item in the $orgs collection and merge the attributes of the organization and its profile into a single array. The toArray method is used to convert the models into arrays.

The resulting $flattened collection will have the desired flattened structure, preserving the keys:

Illuminate\Support\Collection {
    all: [
        [
            "id" => 1,
            "name" => "ABC Corp",
            "organization_id" => 1,
            "ein" => "80-XXXXXXX",
        ],
        [
            "id" => 2,
            "name" => "XYZ Corp",
            "organization_id" => 2,
            "ein" => "85-XXXXXXX",
        ],
    ],
}

This approach allows you to flatten the data and preserve the keys without manually mapping each attribute.

mikefolsom's avatar
mikefolsom
OP
Best Answer
Level 21

Based on "Lary's" reply šŸ˜‰, which got me closer, I ended up with this:

$orgs->map(
  fn($org) => array_merge(
    collect($org)
      ->forget("profile")
      ->toArray(),
    collect($org->profile)
      ->forget("organization_id")
      ->toArray()
  )
);

And it should hold up when working with much larger arrays and multiple eager-loaded relationships. šŸ‘

Please or to participate in this conversation.