Laravel默认订单

时间:2013-12-20 10:13:25

标签: php laravel

是否有一种干净的方法可以通过默认启用某个属性的某些模型? 它可以通过扩展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();

8 个答案:

答案 0 :(得分:41)

在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()->orderBy('some_column')->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();

答案 1 :(得分:22)

另一种方法是覆盖模型类中的newQuery方法。这只适用于您从未希望结果由其他字段排序的情况(因为稍后添加另一个->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);
}

答案 2 :(得分:13)

是的,您需要扩展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();

如果你想在不同的模型中使用它,你可以创建一个基类,并将其扩展到你想要访问这个作用域方法的模型。

答案 3 :(得分:13)

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

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

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

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

答案 4 :(得分:8)

在Laravel 5.7中,您现在可以在模型的启动功能内简单地使用addGlobalScope

protected static function boot()
{
    parent::boot();

    static::addGlobalScope('order', function (Builder $builder) {
        $builder->orderBy('created_at', 'desc');
    });
}

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

答案 5 :(得分:1)

Joshua Jabbour给出的答案略有改善

您可以使用他在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的答案。

答案 6 :(得分:1)

根据我的经验,请不要在全球范围内使用orderByGroupBy这样的术语。否则,在其他地方获取相关模型时,您将很容易遇到数据库错误。

错误可能类似于:

  

“ ORDER BY“ created_at”不明确“

谢谢。

答案 7 :(得分:0)

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

使用此软件包的import React from 'react'; function Foo ({ onClick }) { return <button onClick={onClick}>Click me!</button>; } export default Foo; 特性,可以设置要订购的默认列。

DefaultOrderBy

您还可以通过设置use Stephenjude/DefaultModelSorting/Traits/DefaultOrderBy; class Article extends Model { use DefaultOrderBy; protected static $orderByColumn = 'title'; } 属性来设置默认的orderBy方向。

$orderByColumnDirection