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();
Ответы
Ответ 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"
Благодарю.