Search code examples
laravelsortingcollections

How do I sort a Laravel Collection by an array?


I know this has been asked several times, but none of the answers work for me.

I have a collection of products; each has an eloquent relationship containing multiple items.

Here's how I'm retrieving the collection

$collections = Collection::with('items')->get();

And this is the result of return $collections from the controller.

[
  {
    "id": 1,
    "name": "Product One",
    "items": [
      {
        "id": 1,
        "product_id": 1,
        "name": "Item One",
      },
      {
        "id": 2,
        "product_id": 1,
        "name": "Item Two",
      },
      {
          "id": 3,
          "product_id": 1,
          "name": "Item Three",
      },
  },
  {
      "id": 2,
      "name": "Product Two",
      "items": [
        {
          "id": 1,
          "product_id": 2,
          "name": "Item One",
        },
        {
          "id": 2,
          "product_id": 2,
          "name": "Item Two",
        },
        {
          "id": 3,
          "product_id": 2,
          "name": "Item Three",
        },
    }
]

I'd like to sort each product's items in a different order.

I'd like to sort Product One's items as 3, 1, 2 and Product Two's items as 2, 3, 1.

So I created an array of the new sort order, and then added a callback

$newSortOrder = [
  1 => [3, 1, 2],
  2 => [2, 3, 1]
];

$collections = Collection::with('items')->get();

foreach ($collections as $collection) {
    $collection->items->sortBy(function ($model) use ($newSortOrder) {
        return array_search($model->id, $newSortOrder[$model->id]);
    });
}

dd($collections); <-- this is still in the default sort order from the db

However, when I return $collections, the items are still in the default order. What am I doing wrong?

Edit: tried this as well but with same results; $collection items are returned in the default order.

foreach ($collections as $collection) {
    $collection->items->sortBy(function ($model) use ($order) {
        return array_search($model->getkey(), $order);
    });
}

Solution

  • $sortOrder = [
      1 => [3, 1, 2],
      2 => [2, 3, 1]
    ];
    
    $sorted = $products->map(function($product) use ($sortOrder) {
        $order = $sortOrder[$product->id];
        return [ 
            ...$product,
            'items' => $product->items->mapWithKeys(
                fn($item) => [array_search($item['id'], $order) => $item]
            )->sortKeys()
        ];
    });
    

    This Outputs

    Illuminate\Support\Collection {#4720
      #items: array:2 [
        0 => array:3 [
          "id" => 1
          "name" => "product one"
          "items" => Illuminate\Support\Collection {#4722
            #items: array:3 [
              0 => array:2 [
                "id" => 3
                "name" => "three"
              ]
              1 => array:2 [
                "id" => 1
                "name" => "one"
              ]
              2 => array:2 [
                "id" => 2
                "name" => "two"
              ]
            ]
            #escapeWhenCastingToString: false
          }
        ]
        1 => array:3 [
          "id" => 2
          "name" => "product two"
          "items" => Illuminate\Support\Collection {#4721
            #items: array:3 [
              0 => array:2 [
                "id" => 2
                "name" => "two"
              ]
              1 => array:2 [
                "id" => 3
                "name" => "three"
              ]
              2 => array:2 [
                "id" => 1
                "name" => "one"
              ]
            ]
            #escapeWhenCastingToString: false
          }
        ]
      ]
      #escapeWhenCastingToString: false
    }