Laravel default orderBy
In Laravel 5.7, you can now simply use addGlobalScope
inside the model's boot function:
use Illuminate\Database\Eloquent\Builder;
protected static function boot()
{
parent::boot();
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('created_at', 'desc');
});
}
In the above example, I order the model by created_at desc
to get the most recent records first. You can change that to fit your needs.
Before Laravel 5.2
Nowadays we can solve this problem also with global scopes, introduced in Laravel 4.2 (correct me if I'm wrong). We can define a scope class like this:
<?php namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ScopeInterface;
class OrderScope implements ScopeInterface {
private $column;
private $direction;
public function __construct($column, $direction = 'asc')
{
$this->column = $column;
$this->direction = $direction;
}
public function apply(Builder $builder, Model $model)
{
$builder->orderBy($this->column, $this->direction);
// optional macro to undo the global scope
$builder->macro('unordered', function (Builder $builder) {
$this->remove($builder, $builder->getModel());
return $builder;
});
}
public function remove(Builder $builder, Model $model)
{
$query = $builder->getQuery();
$query->orders = collect($query->orders)->reject(function ($order) {
return $order['column'] == $this->column && $order['direction'] == $this->direction;
})->values()->all();
if (count($query->orders) == 0) {
$query->orders = null;
}
}
}
Then, in your model, you can add the scope in the boot()
method:
protected static function boot() {
parent::boot();
static::addGlobalScope(new OrderScope('date', 'desc'));
}
Now the model is ordered by default. Note that if you define the order also manually in the query: MyModel::orderBy('some_column')
, then it will only add it as a secondary ordering (used when values of the first ordering are the same), and it will not override. To make it possible to use another ordering manually, I added an (optional) macro (see above), and then you can do: MyModel::unordered()->orderBy('some_column')->get()
.
Laravel 5.2 and up
Laravel 5.2 introduced a much cleaner way to work with global scopes. Now, the only thing we have to write is the following:
<?php namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class OrderScope implements Scope
{
private $column;
private $direction;
public function __construct($column, $direction = 'asc')
{
$this->column = $column;
$this->direction = $direction;
}
public function apply(Builder $builder, Model $model)
{
$builder->orderBy($this->column, $this->direction);
}
}
Then, in your model, you can add the scope in the boot()
method:
protected static function boot() {
parent::boot();
static::addGlobalScope(new OrderScope('date', 'desc'));
}
To remove the global scope, simply use:
MyModel::withoutGlobalScope(OrderScope::class)->get();
Solution without extra scope class
If you don't like to have a whole class for the scope, you can (since Laravel 5.2) also define the global scope inline, in your model's boot()
method:
protected static function boot() {
parent::boot();
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('date', 'desc');
});
}
You can remove this global scope using this:
MyModel::withoutGlobalScope('order')->get();