phplaravelsortinglaravel-collection

Laravel Custom Alphabetical Sort callback function


I have a collection of items (shirts) that could be a various range of sizes. The order of the sizes should be as follows.

XS1, XS3, ... S1, S3, ... M1, M3, ... L1, L3, ... XL1, XL3, ... 2XL1, ... 3XL1, ...

Right now when I run the sort,

$items->sortBy('size')

It outputs the results as

2XL#, 3XL#, L#, M#, S# XL#, XS#

which makes sense because that is the natural sort based on alphabetical and numerical orders.

What would be the best way to write a callback function to sort by my specific order needed?


Solution

  • Shy of modifying your schema so that you can perform the sorting server-side, the various sort methods of the Illuminate Collection class take an optional callable as an argument so that you can provide your own sorting mechanism. Using this, you could define an array of the various sizes you have, in the order that you want them to appear, and then use the position of the target string in that array as your sort comparator:

    $collection = collect([
        ['id' => 1, 'size' => 'S3'],
        ['id' => 2, 'size' => 'M1'],
        ['id' => 3, 'size' => 'XS1'],
        ['id' => 4, 'size' => '3XL1'],
        ['id' => 5, 'size' => 'S1'],
    ]);
    
    $sorted = $collection->sortBy([
        function($a, $b) {
            $order = ['XS1', 'XS3', 'S1', 'S3', 'M1', 'L1', 'L3', 'XL1', 'XL3', '2XL1', '3XL1'];
            return array_search($a['size'], $order) <=> array_search($b['size'], $order);
        }
    ]);
    
    print_r($sorted->toArray());
    

    This yields:

    Array
    (
        [0] => Array
            (
                [id] => 3
                [size] => XS1
            )
    
        [1] => Array
            (
                [id] => 5
                [size] => S1
            )
    
        [2] => Array
            (
                [id] => 1
                [size] => S3
            )
    
        [3] => Array
            (
                [id] => 2
                [size] => M1
            )
    
        [4] => Array
            (
                [id] => 4
                [size] => 3XL1
            )
    
    )
    

    Alternatively (and slightly more performant) you could make your sort array associative with the key as the size and the value as the sort order:

    $sorted = $collection->sortBy([
        function($a, $b) {
            $order = [
                'XS1' => 0,
                'S1' => 1,
                'S3' => 2,
                'M1' => 3,
                '3XL1' => 4,
            ];
            return $order[$a['size']] <=> $order[$b['size']];
        }
    ]);