【问题标题】:Laravel default orderByLaravel 默认 orderBy
【发布时间】:2014-01-09 04:10:09
【问题描述】:

有没有一种简洁的方法可以让某些模型按默认的属性排序? 它可以通过扩展 laravel 的 QueryBuilder 来工作,但要这样做,你必须重新连接它的一些核心功能 - 不好的做法。

原因

这样做的要点是 - 我的一个模型被许多其他人大量重复使用,现在您必须一遍又一遍地重新排序。即使为此使用闭包 - 你仍然必须调用它。能够应用默认排序会好得多,因此使用此模型且不提供自定义排序选项的每个人都将收到按默认选项排序的记录。在这里使用存储库不是一个选项,因为它会被预先加载。

解决方案

扩展基础模型:

protected $orderBy;
protected $orderDirection = 'ASC';

public function scopeOrdered($query)
{
    if ($this->orderBy)
    {
        return $query->orderBy($this->orderBy, $this->orderDirection);
    }

    return $query;
}

public function scopeGetOrdered($query)
{
    return $this->scopeOrdered($query)->get();
}

在您的模型中:

protected $orderBy = 'property';
protected $orderDirection = 'DESC';

// ordering eager loaded relation
public function anotherModel()
{
    return $this->belongsToMany('SomeModel', 'some_table')->ordered();
}

在您的控制器中:

MyModel::with('anotherModel')->getOrdered();
// or
MyModel::with('anotherModel')->ordered()->first();

【问题讨论】:

  • 投反对票,没有解释?

标签: php laravel


【解决方案1】:

Joshua Jabbour 给出的稍微改进的答案

您可以使用他在 Trait 中提供的代码,然后将该 trait 添加到您希望订购它们的模型中。

<?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);
    }
}

然后在您希望数据排序的任何模型中,您都可以使用使用:

class PostsModel extends Model {

    use AppOrdered;
    ....

现在,每次您请求该模型时,都会对数据进行排序,这在某种程度上更有条理,但我的答案是 Jabbour 的答案。

【讨论】:

    【解决方案2】:

    我构建了一个迷你 Laravel package,它可以在你的 Eloquent 模型中添加默认 orderBy。

    使用这个包的DefaultOrderBy trait,你可以设置你想要orderBy的默认列。

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

    您还可以通过设置$orderByColumnDirection 属性来设置默认 orderBy 方向。

    protected static $orderByColumnDirection = 'desc';
    

    【讨论】:

      【解决方案3】:

      在 Laravel 5.7 中,您现在可以在模型的引导函数中简单地使用 addGlobalScope:

      use Illuminate\Database\Eloquent\Builder;
      
      protected static function boot()
      {
          parent::boot();
      
          static::addGlobalScope('order', function (Builder $builder) {
              $builder->orderBy('created_at', 'desc');
          });
      }
      

      在上面的示例中,我通过created_at desc 对模型进行排序,以首先获取最新记录。您可以根据需要进行更改。

      【讨论】:

      • 这是一个很难在文档中偶然发现的东西。很好的答案。感谢分享。
      • @jonathan-roy 我怎样才能在一个中心位置使用它,这样就无需将它放在单个模型中??
      • 不要忘记添加命名空间use Illuminate\Database\Eloquent\Builder;
      • @PalakJadav 你可以创建一个trait 或者你可以创建一个抽象模型并扩展它。
      • 我创建了一个模型。继承了与她不同的模式。调用 all 方法。而且它没有排序,为什么?请提供使用示例。我使用 laravel 8。
      【解决方案4】:

      根据我的经验,永远不要在全局范围内使用 orderBy 和 GroupBy 这样的术语。否则在其他地方获取相关模型时很容易遇到数据库错误。

      错误可能是这样的:

      “ORDER BY “created_at”不明确”

      在这种情况下,解决方案可以在查询范围内的列名之前给出表名。

      "ORDER BY posts.created_at"
      

      谢谢。

      【讨论】:

        【解决方案5】:

        在 Laravel 5.2 之前

        现在我们也可以使用 Laravel 4.2 中引入的全局作用域来解决这个问题(如果我错了,请纠正我)。我们可以像这样定义一个作用域类:

        <?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;
                }
            }
        }
        

        然后,在您的模型中,您可以在 boot() 方法中添加范围:

        protected static function boot() {
            parent::boot();
            static::addGlobalScope(new OrderScope('date', 'desc'));
        }
        

        现在模型是默认排序的。请注意,如果您也在查询中手动定义顺序:MyModel::orderBy('some_column'),那么它只会添加它作为二级排序(当第一次排序的值相同时使用),它会不覆盖。为了使手动使用另一个排序成为可能,我添加了一个(可选)宏(见上文),然后你可以这样做:MyModel::unordered()-&gt;orderBy('some_column')-&gt;get()。

        Laravel 5.2 及更高版本

        Laravel 5.2 引入了一种更简洁的方式来处理全局范围。现在,我们唯一需要写的是:

        <?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);
            }
        }
        

        然后,在您的模型中,您可以在 boot() 方法中添加范围:

        protected static function boot() {
            parent::boot();
            static::addGlobalScope(new OrderScope('date', 'desc'));
        }
        

        要删除全局范围,只需使用:

        MyModel::withoutGlobalScope(OrderScope::class)->get();
        

        没有额外作用域类的解决方案

        如果你不喜欢为作用域设置一个完整的类,你也可以(从 Laravel 5.2 开始)在你的模型的 boot() 方法中定义全局作用域:

        protected static function boot() {
            parent::boot();
            static::addGlobalScope('order', function (Builder $builder) {
                $builder->orderBy('date', 'desc');
            });
        }
        

        您可以使用以下命令删除此全局范围:

        MyModel::withoutGlobalScope('order')->get();
        

        【讨论】:

        • 请注意,从 Laravel 5.2 开始,您应该使用 Scope 而不是 ScopeInterface。
        • 谢谢!确实。实际上,Laravel 5.2 提供了一种更简洁的方式来处理全局作用域。我更新了我的答案以包括这个。特别是,内联全局范围定义对于这些简单的事情非常有用。
        • @JeroenNoten :我正在使用“没有额外范围类的解决方案”。它可以工作,但有一个小问题,在此之后我的排序在 created_at 列上不起作用。有没有办法解决它。我认为它总是变成 created_at desc 并且相同的数据一次又一次地出现。其他列排序工作正常。
        【解决方案6】:

        你应该使用可以应用于所有查询的 eloquent global scope(你也可以为其设置参数)。

        对于关系,您可以使用这个有用的技巧:

        class Category extends Model {
            public function posts(){
                return $this->hasMany('App\Models\Post')->orderBy('title');
            }
        }
        

        当我们从某个类别中获取所有帖子时,这会将order by 添加到所有帖子中。 如果您在查询中添加order by,此默认order by 将取消!

        【讨论】:

          【解决方案7】:

          另一种方法是覆盖模型类中的newQuery 方法。这仅在您从不希望结果按另一个字段排序时才有效(因为稍后添加另一个 -&gt;orderBy() 不会删除此默认字段)。所以这可能不是您通常想要做的,但如果您需要始终以某种方式排序,那么这将起作用:

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

          【讨论】:

          • @RoboRobok 不幸的是,这个实现可能会导致很多错误。例如,如果每个查询都有 orderBy 语句,则在此模型上运行 count() 将不起作用。这个问题的正确答案是默认排序没有好的解决方案,因为并非所有查询都可以排序。
          • @TonyArra 无效声明它将起作用,我在计数中使用 orderBy,如果我想获得前 3 个条目,我想按字段排序,然后取前 3 个。跨度>
          • 这应该是公认的答案,因为它回答了实际问题(默认情况下这样做)。了解这样做的后果很重要,但应该由开发人员决定这是否是最好的做法。这达到了所要求的,接受的答案是“最佳选择”。举个例子,我希望这个查找/事实表在以除字母顺序以外的任何其他方式排序时没有实际用途。对这样的表强制排序的好处是没有人需要记住对其进行排序以保证顺序。
          • 嗨@Joshua Jabbour,感谢您的回答,它解决了我的问题。但我怀疑为什么我们需要传递 $ordered = true 参数。我不明白那部分
          • @VijayWilson 该参数使您可以在需要时创建未排序的查询(或者当您需要通过另一个字段手动排序时)。因为这覆盖了中心 newQuery 方法,所以 每个 查询都经过这里。这并不理想,但没有其他方法可以做到这一点。但是,有时您不希望对查询进行排序,因此关闭它的唯一方法是使用此参数。
          【解决方案8】:

          是的,您需要扩展 Eloquent 以始终​​将此作为任何查询的标准。在需要订购时向查询添加 order by 语句有什么问题?这是最简洁的方式,即您无需“破解”Eloquent 即可按自然顺序获得结果。

          MyModel::orderBy('created_at', 'asc')->get();
          

          除此之外,最接近您想要的是在您的模型中创建查询范围。

          public function scopeOrdered($query)
          {
              return $query->orderBy('created_at', 'asc')->get();
          }
          

          然后您可以调用ordered 作为方法而不是get 来检索您的排序结果。

          $data = MyModel::where('foo', '=', 'bar')->ordered();
          

          如果您希望跨不同模型使用此功能,您可以创建一个基类并将其扩展至您希望访问此作用域方法的模型。

          【讨论】:

          • 这不是默认的
          • 阅读答案...我建议您不要默认这样做。
          • 我在我的问题“即使为此使用闭包 - 你仍然必须调用它”中写了这个。
          • 是的,我在第一行说过......你必须为此破解 Eloquent......正如你写的那样:不好的做法。我已经为您提供了最接近您想要的解决方案......抱歉不能提供更多。
          • 它没有回答这个问题。默认情况下排序记录有时非常有用,尤其是当您的结果按weight 列或类似列排序时。如果没有orderBy(),您将无法期待任何特定的顺序,这对某些模型来说很好,但有时让它们始终按顺序排列会很棒。如果 Laravel 有一些用于默认范围的特殊方法,并且如果需要另一种方法可以退出它,我会很高兴。这也适用于where,当我们有一些通常不打算被用户访问的记录时。有点像软删除,但更灵活。
          猜你喜欢
          • 2021-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-31
          • 2016-10-06
          • 2020-01-13
          相关资源
          最近更新 更多