Каков синтаксис сортировки коллекции Eloquent несколькими столбцами?
Я знаю, что при использовании построителя запросов можно сортировать по нескольким столбцам с помощью
...orderBy('column1')->orderBy('column2')
но теперь я имею дело с объектом collection. Коллекции имеют метод sortBy
, но мне не удалось выяснить, как заставить его работать для нескольких столбцов. Интуитивно я сначала попытался использовать тот же синтаксис, что и orderBy
.
sortBy('column1')->sortBy('column2)
но это, по-видимому, просто применяет сортировки последовательно и заканчивается сортировкой по столбцу2 без учета столбца1. Я попробовал
sortBy('column1', 'column2')
но это порождает ошибку: "asort() ожидает, что параметр 2 длинный, строка задана". Использование
sortBy('column1, column2')
не выдает ошибку, но сортировка кажется довольно случайной, поэтому я не знаю, что это на самом деле. Я посмотрел на код метода sortBy, но, к сожалению, мне трудно понять, как это работает.
Ответы
Ответ 1
sortBy()
берет замыкание, позволяя вам предоставить одно значение, которое должно использоваться для сортировки сравнений, но вы можете сделать его составным, объединив несколько свойств вместе
$posts = $posts->sortBy(function($post) {
return sprintf('%-12s%s', $post->column1, $post->column2);
});
Если вам нужно sortBy для нескольких столбцов, вам, вероятно, потребуется проложить их, чтобы убедиться, что "ABC" и "DEF" появляются после "AB" и "DEF", следовательно, спринт справа заполняется для каждого столбца до длина столбца (по крайней мере для всех, кроме последнего столбца)
Обратите внимание, что это обычно намного эффективнее, если вы можете использовать orderBy в своем запросе, чтобы коллекция была готова к сортировке при извлечении из базы данных
Ответ 2
Я нашел другой способ сделать это, используя sort()
в красноречивой коллекции. Это может потенциально работать немного лучше или, по крайней мере, немного легче понять, чем заполнять поля. Мне было бы интересно увидеть, что работает лучше, поскольку у этого есть больше сравнений, но я не делаю sprintf()
для каждого элемента.
$items->sort(
function ($a, $b) {
// sort by column1 first, then 2, and so on
return strcmp($a->column1, $b->column1)
?: strcmp($a->column2, $b->column2)
?: strcmp($a->column3, $b->column3);
}
);
Ответ 3
Как упоминалось в @derekaug, метод sort
позволяет ввести пользовательское закрытие для сортировки коллекции. Но я думал, что его решение было довольно громоздким, и было бы неплохо иметь что-то вроде этого:
$collection = collect([/* items */])
$sort = ["column1" => "asc", "column2" => "desc"];
$comparer = $makeComparer($sort);
$collection->sort($comparer);
Фактически, это может быть легко архивировано следующей оболочкой $makeComparer
для создания закрытия сравнения:
$makeComparer = function($criteria) {
$comparer = function ($first, $second) use ($criteria) {
foreach ($criteria as $key => $orderType) {
// normalize sort direction
$orderType = strtolower($orderType);
if ($first[$key] < $second[$key]) {
return $orderType === "asc" ? -1 : 1;
} else if ($first[$key] > $second[$key]) {
return $orderType === "asc" ? 1 : -1;
}
}
// all elements were equal
return 0;
};
return $comparer;
};
<сильные > Примеры
$collection = collect([
["id" => 1, "name" => "Pascal", "age" => "15"],
["id" => 5, "name" => "Mark", "age" => "25"],
["id" => 3, "name" => "Hugo", "age" => "55"],
["id" => 2, "name" => "Angus", "age" => "25"]
]);
$criteria = ["age" => "desc", "id" => "desc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ];
*/
$criteria = ["age" => "desc", "id" => "asc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ];
*/
$criteria = ["id" => "asc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ];
*/
Теперь, поскольку мы говорим "Красноречивый", есть вероятность, что вы также используете Laravel. Поэтому мы могли бы даже привязать замыкание $makeComparer()
к IOC и разрешить его оттуда:
// app/Providers/AppServiceProvider.php
// in Laravel 5.1
class AppServiceProvider extends ServiceProvider
{
/**
* ...
*/
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind("collection.multiSort", function ($app, $criteria){
return function ($first, $second) use ($criteria) {
foreach ($criteria as $key => $orderType) {
// normalize sort direction
$orderType = strtolower($orderType);
if ($first[$key] < $second[$key]) {
return $orderType === "asc" ? -1 : 1;
} else if ($first[$key] > $second[$key]) {
return $orderType === "asc" ? 1 : -1;
}
}
// all elements were equal
return 0;
};
});
}
}
Теперь вы можете использовать его везде, где вам нужно:
$criteria = ["id" => "asc"];
$comparer = $this->app->make("collection.multiSort",$criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
Ответ 4
Простым решением является цепочка sortBy() несколько раз в обратном порядке того, как вы хотите, чтобы они отсортировались. Даунсайд, вероятно, будет медленнее, чем сортировка сразу в том же обратном вызове, поэтому используйте свой собственный риск для больших коллекций.
$collection->sortBy('column3')->sortBy('column2')->sortBy('column1');