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();