Laravel default orderBy

后端 未结 8 1791
离开以前
离开以前 2020-12-24 10:51

Is there a clean way to enable certain models to be ordered by a property by default? It could work by extending the laravel\'s QueryBuilder, but to do so,

相关标签:
8条回答
  • 2020-12-24 11:17

    I built a mini Laravel package that can add default orderBy in your Eloquent model.

    Using the DefaultOrderBy trait of this package, you can set the default column you want to orderBy.

    use Stephenjude/DefaultModelSorting/Traits/DefaultOrderBy;
    
    class Article extends Model
    {
        use DefaultOrderBy;
    
        protected static $orderByColumn = 'title';
    }
    

    You can also set the default orderBy direction by setting the $orderByColumnDirection property.

    protected static $orderByColumnDirection = 'desc';
    
    0 讨论(0)
  • 2020-12-24 11:20

    Another way of doing this could be by overriding the newQuery method in your model class. This only works if you never, ever want results to be ordered by another field (since adding another ->orderBy() later won't remove this default one). So this is probably not what you'd normally want to do, but if you have a requirement to always sort a certain way, then this will work:

    protected $orderBy;
    protected $orderDirection = 'asc';
    
    /**
     * Get a new query builder for the model's table.
     *
     * @param bool $ordered
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function newQuery($ordered = true)
    {
        $query = parent::newQuery();    
    
        if (empty($ordered)) {
            return $query;
        }    
    
        return $query->orderBy($this->orderBy, $this->orderDirection);
    }
    
    0 讨论(0)
  • 2020-12-24 11:28

    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();
    
    0 讨论(0)
  • 2020-12-24 11:29

    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.

    0 讨论(0)
  • 2020-12-24 11:30

    A note from my experience, never to use orderBy and GroupBy such term on global scope. Otherwise you will easily face database errors while fetching related models in other places.

    Error may be something like:

    "ORDER BY "created_at" is ambiguous"

    In such case the solution can be giving table name before column names in your query scope.

    "ORDER BY posts.created_at"
    

    Thanks.

    0 讨论(0)
  • 2020-12-24 11:31

    An slightly improved answer given by Joshua Jabbour

    you can use the code he offered in a Trait, and then add that trait to the models where you want them to be ordered.

    <?php
    
    namespace App\Traits;
    
    trait AppOrdered {
    protected $orderBy = 'created_at';
    protected $orderDirection = 'desc';
    
    
    public function newQuery($ordered = true)
    {
        $query = parent::newQuery();
    
        if (empty($ordered)) {
            return $query;
        }
    
        return $query->orderBy($this->orderBy, $this->orderDirection);
    
    }
    

    then in whichever model you want the data to be ordered you can use use :

    class PostsModel extends Model {
    
        use AppOrdered;
        ....
    

    now everytime you request that model, data will be ordered, that's somehow more organized, but my answers is Jabbour's answer.

    0 讨论(0)
提交回复
热议问题