Каков синтаксис сортировки коллекции Eloquent несколькими столбцами?

Я знаю, что при использовании построителя запросов можно сортировать по нескольким столбцам, используя

...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');