• 6 min read

PHP 8.6 SortDirection Enum: Stop Passing 'ASC' and 'DESC' Strings Around

PHP 8.6 adds a global SortDirection enum. See how to replace ASC/DESC strings in query builders and sorters, and how to polyfill it on PHP 8.1 and up.

Featured image for "PHP 8.6 SortDirection Enum: Stop Passing 'ASC' and 'DESC' Strings Around"

Every PHP codebase I have worked on in 20 years has the same small smell. Somewhere there is a function that takes $direction, and somewhere else there is a check that it equals 'asc', 'ASC', 'desc', or SORT_DESC. Sometimes all four.

PHP 8.6 adds a tiny, always-available fix: a global SortDirection enum. It is a small feature, but it is the kind that cleans up a lot of boundary code. PHP 8.6 is in release candidate territory as of this writing, and Laravel News lists the stable release for November 19, 2026, so now is a good time to plan for it.

What the enum looks like

According to PHP.Watch, the declaration is intentionally minimal:

enum SortDirection {
    case Ascending;
    case Descending;
}

Three details matter here.

It lives in the global namespace, so you do not need a use statement. It is a unit enum, meaning the cases have no backing value like 'ASC' or 1. And it is always available, with no extension to enable.

The RFC is listed on PHP.Watch with a vote tally of 22 yes, 1 no, and 2 abstain. The no-backing-value decision is deliberate. There is no single canonical serialization for “descending”. SQL wants DESC, a query string might want -name, and a JSON API might want desc. By leaving the enum unbacked, PHP forces you to map it at the system boundary, which is where that mapping belongs.

The problem it replaces

Here is the shape of code I see in most apps:

final class UserQuery
{
    public function orderBy(string $column, string $direction = 'asc'): static
    {
        $direction = strtolower($direction);

        if (! in_array($direction, ['asc', 'desc'], true)) {
            throw new InvalidArgumentException('Direction must be asc or desc.');
        }

        // ...
        return $this;
    }
}

Every caller can pass any string. The validation is runtime only, and every method that accepts a direction needs its own copy of it. With the enum, the type system does that work:

final class UserQuery
{
    public function orderBy(string $column, SortDirection $direction = SortDirection::Ascending): static
    {
        $keyword = match ($direction) {
            SortDirection::Ascending => 'ASC',
            SortDirection::Descending => 'DESC',
        };

        // ... use $keyword when building SQL
        return $this;
    }
}

Note that $column still needs an allowlist before it goes anywhere near SQL. The enum only fixes the direction half of the problem. Never interpolate a user-supplied column name.

The match expression is exhaustive. If a future PHP version ever added a case, PHP would throw an UnhandledMatchError instead of silently falling through to a default.

Parse once, at the edge

The best use of an unbacked enum is to convert raw input into the type as early as possible, then pass the typed value everywhere else. SensioLabs’ write-up uses a similar pattern for request input:

function directionFromRequest(?string $value): SortDirection
{
    return match (strtolower($value ?? 'asc')) {
        'desc', 'descending' => SortDirection::Descending,
        default => SortDirection::Ascending,
    };
}

$direction = directionFromRequest($_GET['direction'] ?? null);

In a Laravel controller, the same idea fits naturally into a form request:

// app/Http/Requests/ListAttendeesRequest.php
public function direction(): SortDirection
{
    return $this->input('direction') === 'desc'
        ? SortDirection::Descending
        : SortDirection::Ascending;
}

Your controller, service, and repository layers then never see a string. A PHPTek.tv attendee list, a PHP Architect back-issue index, or any admin grid can share one sorting vocabulary.

Using it in a custom comparator

You can also use the enum inside usort() callbacks. This example, adapted from the pattern in the SensioLabs article, sorts a list of issues from PHP Architect Magazine by publication date:

function sortIssues(array $issues, SortDirection $direction): array
{
    usort($issues, function (array $a, array $b) use ($direction): int {
        $result = $a['published'] <=> $b['published'];

        return $direction === SortDirection::Ascending ? $result : -$result;
    });

    return $issues;
}

One caution: I could not confirm that the built-in sort functions such as sort() or usort() accept SortDirection directly in 8.6. The enum exists so that core and userland APIs can adopt it over time, as PHP.Watch describes, but today it is a type you use in your own code. Keep SORT_ASC and SORT_DESC for array_multisort() until the manual says otherwise.

Polyfilling for PHP 8.1 and up

Enums arrived in PHP 8.1, so you can start using the same name before you upgrade. PHP.Watch suggests this polyfill:

if (! enum_exists('SortDirection')) {
    enum SortDirection {
        case Ascending;
        case Descending;
    }
}

Put it in a file loaded through Composer’s autoload.files, and your code will work on both old and new runtimes. When you move to 8.6, delete the polyfill. The only backward compatibility risk is an existing class or enum already named SortDirection in the global namespace. If you have one, rename it or move it into a namespace before upgrading. Run a quick check:

grep -rn "SortDirection" app src --include=*.php

Namespaced classes are not affected, only global ones.

Where I would adopt it first

Start with the places where a string direction crosses a layer boundary:

  • Query builder or repository methods that take 'asc' or 'desc'
  • API list endpoints with a sort or direction parameter
  • Livewire components with sortable table headers (store the enum, not the string, in a computed property)
  • Collection helpers and reusable sort utilities

Leave persistence formats alone. If you store a user’s preferred sort order in a database column or session, keep writing asc and desc there and convert on the way in and out. Unit enums cannot be serialized to scalars on their own, so a mapping function or a small backed enum at that edge is still the right tool.

Testing the mapping

Because the mapping is the only logic left, it is easy to cover with a Pest dataset:

it('maps request input to a sort direction', function (?string $input, SortDirection $expected) {
    expect(directionFromRequest($input))->toBe($expected);
})->with([
    ['asc', SortDirection::Ascending],
    ['DESC', SortDirection::Descending],
    [null, SortDirection::Ascending],
    ['garbage', SortDirection::Ascending],
]);

Enum cases are singletons, so toBe() strict identity works fine.

The takeaway

SortDirection will not change how you architect an application. It removes one more stringly-typed value from your signatures and gives PHP’s standard library, frameworks, and packages a shared name for “which way”. That shared vocabulary is the real win, because libraries that agree on one type interoperate without adapters.

If you are already on PHP 8.1 or later, add the polyfill today and start typing your direction parameters. When 8.6 lands in November, you will have nothing left to change except deleting a file.

Sources