Подтвердить что ты не робот

Laravel default orderBy

Есть ли чистый способ включить определенные модели для упорядочения с помощью свойства по умолчанию? Он может работать, расширяя 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();
4b9b3361

Ответ 1

Да, вам нужно будет расширить 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();

Если вы хотите использовать это для разных моделей, вы можете создать базовый класс и просто расширить его до тех моделей, которые хотите получить доступ к этому методу с областью.

Ответ 2

До 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 и up

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

Ответ 3

Другой способ сделать это можно, переопределив метод newQuery в вашем классе модели. Это работает только в том случае, если вы никогда не хотите, чтобы результаты были заказаны другим полем (так как добавление другого ->orderBy() позже не удалит этот по умолчанию). Так что это, вероятно, не то, что вы обычно хотели бы сделать, но если у вас есть требование всегда сортировать определенный путь, то это будет работать:

protected $orderBy;
protected $orderDirection = 'asc';

/**
 * Get a new query builder for the model 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);
}

Ответ 4

вы должны использовать красноречивую глобальную область, которая может применяться ко всем запросам (также вы можете установить для нее параметр).

И для отношений вы можете использовать этот полезный трюк:

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

это добавит order by ко всем сообщениям, когда мы получим их из категории. Если вы добавите order by к вашему запросу, это значение по умолчанию order by будет отменено!

Ответ 5

В Laravel 5.7 теперь вы можете просто использовать addGlobalScope внутри функции загрузки модели:

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

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

В приведенном выше примере я заказываю модель по created_at desc, чтобы сначала получить самые последние записи. Вы можете изменить это, чтобы соответствовать вашим потребностям.

Ответ 6

Немного улучшенный ответ Джошуа Джаббура

вы можете использовать код, который он предложил в 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.

Ответ 7

Примечание из моего опыта, никогда не использовать orderBy и GroupBy такой термин в глобальном масштабе. В противном случае вы легко столкнетесь с ошибками базы данных при извлечении связанных моделей в других местах.

Ошибка может быть что-то вроде:

"ORDER BY "created_at" is ambiguous"

Благодарю.