Search code examples
phparrayssortingobject

How to sort an array of objects by specific criteria on the keys?


Supposing we have this array of objects:


$myWondersArray =

array (
  0 => 
  (object) array(
     'currentUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 1/3)',
     'currentUnitPart' => 'wonderskills/starter_book_2/unit_3_helping_mom/part_1/',
  ),
  1 => 
  (object) array(
     'currentUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 2/3)',
     'currentUnitPart' => 'wonderskills/starter_book_2/unit_3_helping_mom/part_2/',
  ),
  2 => 
  (object) array(
     'currentUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 3/3)',
     'currentUnitPart' => 'wonderskills/starter_book_2/unit_3_helping_mom/part_3/',
  ),
  3 => 
  (object) array(
     'previousUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 2 - Sid\'s New Neighbors (Part 1/3)',
     'previousUnitPart' => 'wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_1/',
  ),
  4 => 
  (object) array(
     'previousUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 2 - Sid\'s New Neighbors (Part 2/3)',
     'previousUnitPart' => 'wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_2/',
  ),
  5 => 
  (object) array(
     'previousUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 2 - Sid\'s New Neighbors (Part 3/3)',
     'previousUnitPart' => 'wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_3/',
  ),
  6 => 
  (object) array(
     'nextUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 1/3)',
     'nextUnitPart' => 'wonderskills/starter_book_2/unit_4_food_from_the_garden/part_1/',
  ),
  7 => 
  (object) array(
     'nextUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 2/3)',
     'nextUnitPart' => 'wonderskills/starter_book_2/unit_4_food_from_the_garden/part_2/',
  ),
  8 => 
  (object) array(
     'nextUnitDisplay' => 'WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 3/3)',
     'nextUnitPart' => 'wonderskills/starter_book_2/unit_4_food_from_the_garden/part_3/',
  ),
)

The desired order of the array is for the objects whose keys start with 'previous' be at the top. Then the objects whose keys start with 'current', and finally the objects whose keys start with 'next'.

The objects themselves should be unchanged, but the correct order of parts (in the value) should be maintained.

The desired outcome would be like this:

Array
(
    [0] => stdClass Object
        (
            [previousUnitDisplay] => WonderSkills: Starter Book 2, Unit 2 - Sid's New Neighbors (Part 1/3)
            [previousUnitPart] => wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_1/
        )

    [1] => stdClass Object
        (
            [previousUnitDisplay] => WonderSkills: Starter Book 2, Unit 2 - Sid's New Neighbors (Part 2/3)
            [previousUnitPart] => wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_2/
        )

    [2] => stdClass Object
        (
            [previousUnitDisplay] => WonderSkills: Starter Book 2, Unit 2 - Sid's New Neighbors (Part 3/3)
            [previousUnitPart] => wonderskills/starter_book_2/unit_2_sids_new_neighbors/part_3/
        )

    [3] => stdClass Object
        (
            [currentUnitDisplay] => WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 1/3)
            [currentUnitPart] => wonderskills/starter_book_2/unit_3_helping_mom/part_1/

        )

    [4] => stdClass Object
        (
            [currentUnitDisplay] => WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 2/3)
            [currentUnitPart] => wonderskills/starter_book_2/unit_3_helping_mom/part_2/
        )

    [5] => stdClass Object
        (
            [currentUnitDisplay] => WonderSkills: Starter Book 2, Unit 3 - Helping Mom (Part 3/3)
            [currentUnitPart] => wonderskills/starter_book_2/unit_3_helping_mom/part_3/
        )

    [6] => stdClass Object
        (
            [nextUnitDisplay] => WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 1/3)
            [nextUnitPart] => wonderskills/starter_book_2/unit_4_food_from_the_garden/part_1/
        )

    [7] => stdClass Object
        (
            [nextUnitDisplay] => WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 2/3)
            [nextUnitPart] => wonderskills/starter_book_2/unit_4_food_from_the_garden/part_2/
        )

    [8] => stdClass Object
        (
            [nextUnitDisplay] => WonderSkills: Starter Book 2, Unit 4 - Food from the Garden (Part 3/3)
            [nextUnitPart] => wonderskills/starter_book_2/unit_4_food_from_the_garden/part_3/
        )

)

I have tried the usual php sorting functions, but I need a bit more than just alphabetical order. I was able to produce an array containing all the items above, but I ended up losing the objects in the process. I really want to keep the objects intact.

I tried creating a new array by first creating 3 arrays (one for previous, current and next), like so:

foreach($myWondersArray as $item => $value) {
    if($value->previousUnitDisplay){
        //echo "got a previous" ;
        array_push($previousArray, $value->previousUnitDisplay, $value->previousUnitPart) ;
    }
} 

Then I merged them, like this:

$completeWondersArray = array_merge($previousArray, $currentArray, $nextArray);

But now I have lost my objects, it's just a simple array.

Any help or pointers is very much appreciated.


Solution

  • Here is an approach to sort the array of objects by first property name in each object. Using array_multisort() is ideal because it reduces the number of time processing function calls are required (versus usort()).

    If you need to add more sorting rules, add them between the 1st and 2nd parameters of array_multisort().

    Code: (Demo)

    $priorities = array_flip(['prev', 'curr', 'next']);
    array_multisort(
        array_map(fn($obj) => $priorities[substr(key((array) $obj), 0, 4)] ?? PHP_INT_MAX, $myWondersArray),
        $myWondersArray
    );
    var_export($myWondersArray);
    

    To sort using "buckets", define the order of the buckets, then fill the corresponding buckets as you iterate the objects, then flatten the bucket items. This will have better time complexity, but I assume higher memory consumption. Demo

    $buckets = array_fill_keys(['previous', 'current', 'next'], []);
    foreach ($myWondersArray as $obj) {
        $buckets[substr(key((array) $obj), 0, -11)][] = $obj;
    }
    var_export(array_merge(...array_values($buckets)));