Override Sorting in Eloquent

Override Sorting in Eloquent
Photo by NAT Nguyen / Unsplash

A very peculiar use case: in a Laravel application, within a complex flow of encapsulated functions which builds different types of Eloquent queries, I always have to orderBy() the created_at timestamp. Except in one situation.

The proper way to fix should be to pass a extra parameter to handle that singular case. But today I'm too lazy to refactorize that block of code, so I've found a more brutal, hackish and dirty fix: revert the previously attached orderBy() from the query, and replace it.

In Illuminate\Database\Query\Builder (which is the base building block for Illuminate\Database\Eloquent\Builder) it already exists a function named removeExistingOrdersFor(), doing exactly what its name suggests, but is protected and not accessible function. On the other hand, the attribute $orders - holding the current ordering for the query - is public and can be shamelessly manipulated.

Taking some inspiration from removeExistingOrdersFor(), it is possible to write:

// Access the actual Query\Builder from the Eloquent\Builder
$subquery = $query->getQuery();

// Access the Collection of ordering parameters, and remove the 
// reference to the previously used column
$subquery->orders = Illuminate\Support\Collection::make($subquery->orders)
  ->reject(function ($order) {
    return isset($order['column']) ? $order['column'] === 'old_column' : false;
  })->values()->all();

// Order by the intended column
$query->orderBy('new_column', 'asc');

Duck taped.