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

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

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

...orderBy('column1')->orderBy('column2')

но теперь я имею дело с объектом collection. Коллекции имеют метод sortBy, но мне не удалось выяснить, как заставить его работать для нескольких столбцов. Интуитивно я сначала попытался использовать тот же синтаксис, что и orderBy.

sortBy('column1')->sortBy('column2)

но это, по-видимому, просто применяет сортировки последовательно и заканчивается сортировкой по столбцу2 без учета столбца1. Я попробовал

sortBy('column1', 'column2')

но это порождает ошибку: "asort() ожидает, что параметр 2 длинный, строка задана". Использование

sortBy('column1, column2')

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

4b9b3361

Ответ 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');