Я знаю, что при использовании построителя запросов можно сортировать по нескольким столбцам, используя
...orderBy('column1')->orderBy('column2')
но теперь я имею дело с объектом коллекции . Коллекции имеют метод sortBy
, но мне не удалось выяснить, как заставить его работать для нескольких столбцов. Интуитивно я сначала попытался использовать тот же синтаксис, что и orderBy
.
sortBy('column1')->sortBy('column2)
но это, по-видимому, просто применяет сортировки последовательно и заканчивается сортировкой по столбцу2 без учета столбца1. Я пытался
sortBy('column1', 'column2')
но это порождает ошибку «asort () ожидает, что параметр 2 длинный, строка задана». С помощью
sortBy('column1, column2')
не вызывает ошибку, но сортировка кажется довольно случайной, поэтому я действительно не знаю, что это на самом деле. Я просмотрел код метода sortBy , но, к сожалению, мне трудно понять, как это работает.
sortBy()
принимает закрытие, позволяя вам предоставить одно значение, которое должно использоваться для сортировки сравнений, но вы можете сделать его составным, объединив несколько свойств вместе
$posts = $posts->sortBy(function($post) { return sprintf('%-12s%s', $post->column1, $post->column2); });
Если вам нужна сортировка по нескольким столбцам, вам, вероятно, потребуется проложить их, чтобы убедиться, что «ABC» и «DEF» появляются после «AB» и «DEF», следовательно, спринт справа заполняется для каждого столбца до длины столбца ( по крайней мере для всех, кроме последнего столбца)
Обратите внимание, что это обычно намного эффективнее, если вы можете использовать orderBy в своем запросе, чтобы коллекция была готова к сортировке по извлечению из базы данных
Я нашел другой способ сделать это, используя sort()
в eloquent Collection. Это может потенциально работать немного лучше или, по крайней мере, немного легче понять, чем заполнять поля. Мне было бы интересно узнать, какая из них лучше, поскольку у этого есть больше сравнений, но я не делаю 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); } );
Как упоминалось в @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();
Простым решением является цепочка sortBy () несколько раз в обратном порядке того, как вы хотите, чтобы они отсортировались. Даунсайд – это, вероятно, медленнее, чем сортировка сразу в том же обратном вызове, поэтому используйте свой собственный риск для больших коллекций.
$collection->sortBy('column3')->sortBy('column2')->sortBy('column1');