Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

newbie360's avatar

Laravel collection has bug for sortBy() multiple attributes with Enum?

https://laravel.com/docs/11.x/collections#method-sortby

While Testing the Filament table sorting, i'm expecting the test should pass.

Filament Table Column

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('role_id')
                    ->label(__('Role'))
                    ->sortable(['id', 'role_id']),
            ])
    }

Role Enum

<?php

namespace App\Enums;

enum Role: int
{
    case Administrator = 1;
    case Moderator = 2;
    case SuperMember = 3;
    case Member = 4;

    public static function toArray(): array
    {
        return array_column(self::cases(), 'value');
    }

    public static function count(): int
    {
        return count(self::cases());
    }
}

User Factory

<?php

namespace Database\Factories;

...
use App\Enums\Role;

class UserFactory extends Factory
{
    ...

    /**
     * Indicate that the user factory use sequence role id.
     */
    public function sequenceRoleId(): Factory
    {
        $roles = collect(Role::toArray())
            ->map(fn (int $roleId): array => ['role_id' => $roleId])
            ->all();

        return $this->sequence(...$roles);
    }
}

Tests

Without the Debug code block, the test is Failed

0 likes
11 replies
newbie360's avatar

I make a shorter version for the above test

Enum

<?php

namespace App\Enums;

enum Role: int
{
    case Administrator = 1;
    case Moderator = 2;
    case SuperMember = 3;
    case Member = 4;
}

Route

Anyone know whats wrong with above code? o.0

RemiM's avatar

When sorting a collection by multiple attributes, you may also provide closures that define each sort operation:

See in the Laravel Collections Documentation

$collection = collect([
    ['name' => 'Taylor Otwell', 'age' => 34],
    ['name' => 'Abigail Otwell', 'age' => 30],
    ['name' => 'Taylor Otwell', 'age' => 36],
    ['name' => 'Abigail Otwell', 'age' => 32],
]);
 
$sorted = $collection->sortBy([
    fn (array $a, array $b) => $a['name'] <=> $b['name'],
    fn (array $a, array $b) => $b['age'] <=> $a['age'],
]);
 
$sorted->values()->all();
 
/*
    [
        ['name' => 'Abigail Otwell', 'age' => 32],
        ['name' => 'Abigail Otwell', 'age' => 30],
        ['name' => 'Taylor Otwell', 'age' => 36],
        ['name' => 'Taylor Otwell', 'age' => 34],
    ]
*/

I guess you would have to do something like this:

    $asc = $users
        ->sortBy([
            fn ($a, $b) => $a['role_id'] <=> $b['role_id'],
            fn ($a, $b) => $a['id'] <=> $b['id'],
        ])
        ->pluck('role_id')
        ->toJson();

	$desc = $users
        ->sortByDesc([
            fn ($a, $b) => $a['role_id'] <=> $b['role_id'],
            fn ($a, $b) => $a['id'] <=> $b['id'],
        ])
        ->pluck('role_id')
        ->toJson();
newbie360's avatar

@RemiM Your sortByDesc() is something wrong

        ->sortByDesc([
            fn ($a, $b) => $a['role_id'] <=> $b['role_id'],
            fn ($a, $b) => $a['id'] <=> $b['id'],
        ])

should be ($b then $a)

        ->sortByDesc([
            fn ($a, $b) => $b['role_id'] <=> $a['role_id'],
            fn ($a, $b) => $b['id'] <=> $a['id'],
        ])

The result is same as my code

RemiM's avatar

@newbie360 I don't think there is something wrong in my sortByDesc method. It has the same logic as the sortBy, but will sort the collection in the opposite order. So you don't have to change the function's logic. I believe your correction would, however, return the exact same result as the sortBymethod.

Anyway, as @snapey suggested, you should use the values()->all() method on both $asc and $desc variables as shown in the documention example above.

newbie360's avatar

@RemiM I copied your code, and use ->values()->all()

    $asc = $users
        ->sortBy([
            fn ($a, $b) => $a['role_id'] <=> $b['role_id'],
            fn ($a, $b) => $a['id'] <=> $b['id'],
        ])
        ->values()
        ->all();

	$desc = $users
        ->sortByDesc([
            fn ($a, $b) => $a['role_id'] <=> $b['role_id'],
            fn ($a, $b) => $a['id'] <=> $b['id'],
        ])
        ->values()
        ->all();

    dd($asc === $desc); // true

The weird things is

        $asc,     // "[4,4,3,3,2,2,1,1]" expecting 1,1,2,2,3,3,4,4
        $desc,   // "[4,4,3,3,2,2,1,1]" expecting 4,4,3,3,2,2,1,1

Note: if the column isn't an Enum, in my another sorting test is work correctly

newbie360's avatar

@RemiM When i use single column sorting, the result is correct

    $asc = $users
        ->sortBy('role_id')
        ->pluck('role_id')
        ->toJson();

    $desc = $users
        ->sortByDesc('role_id')
        ->pluck('role_id')
        ->toJson();

    dd([
        'asc' => $asc,   // "[1,1,2,2,3,3,4,4]" is correct
        'desc' => $desc, // "[4,4,3,3,2,2,1,1]" is correct
    ]);
Snapey's avatar

Some suggestions

use SortBy in both cases, not sortByDesc

use ->values() after sort

newbie360's avatar

@Snapey actually values()-all() won't affect the order, i use both sortBy() result is same

    $asc = $users
        ->sortBy([
            ['role_id', 'asc'],
            ['id', 'asc'],
        ])
        ->values()
        ->all();

    $desc = $users
        ->sortBy([
            ['role_id', 'desc'],
            ['id', 'desc'],
        ])
        ->values()
        ->all();

    dd([
        'asc' => $asc,   // "[4,4,3,3,2,2,1,1]" expecting 1,1,2,2,3,3,4,4
        'desc' => $desc, // "[4,2,3,1,4,3,2,1]" expecting 4,4,3,3,2,2,1,1
    ]);

Note: the weird things is why 4,2,3,1 and 4,3,2,1

    'desc' => $desc, // "[4,2,3,1,4,3,2,1]"
newbie360's avatar

Opened an issues: https://github.com/laravel/framework/issues/54634

currently fixed by use closures and call ->value on an Enum attribute, but this still not good, let see any reply

test('table can sort users by role_id', function () {
    $users = User::factory()
        ->count(Role::count() * 2)
        ->sequenceRoleId()
        ->create();

    $component = Livewire::test(ListUsers::class);

    // FIXME: sort by multiple attributes that contain Enum return wrong order
    // @see https://github.com/laravel/framework/issues/54634
    $component
        ->sortTable('role_id')
        ->assertCanSeeTableRecords($users->sortBy([
                fn (User $a, User $b): int => $a->role_id->value <=> $b->role_id->value,
                fn (User $a, User $b): int => $a->id <=> $b->id,
            ]), inOrder: true)
        ->sortTable('role_id', 'desc')
        ->assertCanSeeTableRecords($users->sortByDesc([
                fn (User $a, User $b): int => $b->role_id->value <=> $a->role_id->value,
                fn (User $a, User $b): int => $b->id <=> $a->id,
            ]), inOrder: true);
})->repeat(3)->only();
   PASS  Tests\Feature\Filament\Resources\UserResource\Pages\ListUsersTest
  ✓ table can sort users by role_id @ repetition 1 of 3
  ✓ table can sort users by role_id @ repetition 2 of 3
  ✓ table can sort users by role_id @ repetition 3 of 3
newbie360's avatar

If i reading the ->sortBy() source code is correctly, with this syntax passing array

        ->sortBy([
            ['role_id', 'asc'],
            ['id', 'asc'],
        ]);

First call https://github.com/laravel/framework/blob/17786ca25fa9080f0b4f03af7517e9fc72fa0b4a/src/Illuminate/Collections/Collection.php#L1516

and then https://github.com/laravel/framework/blob/17786ca25fa9080f0b4f03af7517e9fc72fa0b4a/src/Illuminate/Collections/Collection.php#L1584

OK, now the problem is Enum is not comparable!!! (https://github.com/php/php-src/issues/12410)

open Tinker, always return 1

Role::Administrator <=> Role::Moderator // 1
Role::Moderator <=> Role::SuperMember   // 1
Role::SuperMember <=> Role::Member      // 1
Role::Member <=> Role::Administrator    // 1


Role::Administrator->value <=> Role::Member->value // -1
// swap
Role::Member->value <=> Role::Administrator->value // 1
newbie360's avatar
newbie360
OP
Best Answer
Level 24

based on this line https://github.com/laravel/framework/blob/17786ca25fa9080f0b4f03af7517e9fc72fa0b4a/src/Illuminate/Collections/Collection.php#L1566

test('table can sort users by role_id', function () {
    $users = User::factory()
        ->count(Role::count() * 2)
        ->sequenceRoleId()
        ->create();

    $component = Livewire::test(ListUsers::class);

    $component
        ->sortTable('role_id')
        ->assertCanSeeTableRecords($users->sortBy([
                ['role_id.value', 'asc'], // Add .value
                ['id', 'asc'],
            ]), inOrder: true)
        ->sortTable('role_id', 'desc')
        ->assertCanSeeTableRecords($users->sortByDesc([
                ['role_id.value', 'desc'], // Add .value
                ['id', 'desc'],
            ]), inOrder: true);
})->repeat(3)->only();
   PASS  Tests\Feature\Filament\Resources\UserResource\Pages\ListUsersTest
  ✓ table can sort users by role_id @ repetition 1 of 3
  ✓ table can sort users by role_id @ repetition 2 of 3
  ✓ table can sort users by role_id @ repetition 3 of 3

Please or to participate in this conversation.