Sortieren der Laravel-Sammlung über ein Array von IDs

Lesezeit: 6 Minuten

Sortieren der Laravel Sammlung uber ein Array von IDs
Matthäus Braun

Ist es möglich, eine Beziehungssammlung mit einem separaten Array von IDs zu bestellen, während der Zugriff weiterhin über die Beziehung erfolgt?

Die Einrichtung ist Checklist hat viele ChecklistItems und die gewünschte Reihenfolge der zugehörigen Elemente existiert als Eigenschaft Checklist::$item_order. Es ist nur ein Array aus numerischen IDs in der vom Benutzer gewünschten Reihenfolge. :

class Checklist extends Model {
    protected $casts = ['item_order' => 'array'];

    public function items() {
        return $this->hasMany(ChecklistItem::class);
    }
}
class ChecklistItem extends Model {
    public function list() {
        return $this->belongsTo(Checklist::class);
    }
}

Ich greife auf diese Beziehung auf die normale Weise zu:

$list = Checklist::find(1234);

foreach ($list->items as $item) {
    // More Code Here
}

Gibt es eine praktikable Bestellmöglichkeit $list->items basierend auf den Werten in der $list->item_order Anordnung?

(Ich kann nicht einfach eine Spalte „Bestellung“ zur Tabelle „Artikel“ hinzufügen, da sich die Reihenfolge für jede „Liste“ ändert.)

  • Ich weiß nicht, wie Sie es in Laravel machen würden, aber ich weiß, dass Sie in einer direkten MySQL-Abfrage die verwenden können, wenn Sie etwas in einer vordefinierten Reihenfolge bestellen möchten field oder find_in_set als Teil Ihrer Bestellung, indem Sie einfach Werte in beliebiger Reihenfolge auflisten. Oder Sie könnten eine case-Anweisung in der Auswahl (oder Reihenfolge) verwenden, die Zahlen zurückgibt und nach dieser Spalte sortiert. Sofern Laravel keine andere Möglichkeit hat, besteht die einzige andere Möglichkeit darin, die Elemente in einem Array zu speichern und mithilfe Ihres Arrays von IDs die gewünschte Reihenfolge zu erhalten.

    – Jonathan Kühn

    23. Januar 2015 um 20:48 Uhr


  • @Matthew Braun $list->item_order ist ein Array von Artikel-IDs bestellt??

    – voodoo417

    23. Januar 2015 um 20:53 Uhr

  • @ voodoo417 ja, das ist richtig

    – Matthäus Braun

    23. Januar 2015 um 21:05 Uhr

1645704846 798 Sortieren der Laravel Sammlung uber ein Array von IDs
Lukasgeiter

Du kannst das:

$order = $list->item_order;
$list->items->sortBy(function($model) use ($order){
    return array_search($model->getKey(), $order);
}

Sie könnten Ihrem Modell auch einen Attribut-Accessor hinzufügen, der dasselbe tut

public function getSortedItemsAttribute() 
{
    if ( ! is_null($this->item_order)) {
        $order = $this->item_order;

        $list = $this->items->sortBy(function($model) use ($order){
            return array_search($model->getKey(), $order);
        });
        return $list;
    }
    return $this->items;
}

Verwendungszweck:

foreach ($list->sortedItems as $item) {
    // More Code Here
}

Wenn Sie diese Art von Funktionalität an mehreren Stellen benötigen, schlage ich vor, dass Sie Ihre eigene Collection-Klasse erstellen:

class MyCollection extends Illuminate\Database\Eloquent\Collection {

    public function sortByIds(array $ids){
        return $this->sortBy(function($model) use ($ids){
            return array_search($model->getKey(), $ids);
        }
    }
}

Dann, um diese Klassenüberschreibung tatsächlich zu verwenden newCollection() in deinem Modell. In diesem Fall wäre es in der ChecklistItems Klasse:

public function newCollection(array $models = array())
{
    return new MyCollection($models);
}

  • Ich habe diese Methode verwendet, funktioniert großartig. Ich hatte keine Ahnung, dass die sortBy-Funktion auch Schließungen akzeptiert.

    – Matthäus Braun

    28. Januar 2015 um 20:42 Uhr

Sie können versuchen, eine Beziehung einzurichten, die die Ergebnisse in der gewünschten Reihenfolge zurückgibt. Sie sollten die Beziehung immer noch eifrig laden können und die Ergebnisse in der angegebenen Reihenfolge haben. Dies setzt alles voraus item_order Feld ist eine durch Kommas getrennte Zeichenfolgenliste von IDs.

public function itemsOrdered()
{
    /**
     * Adds conditions to the original 'items' relationship. Due to the
     * join, we must specify to only select the fields for the related
     * table. Then, order by the results of the FIND_IN_SET function.
     */
    return $this->items()
        ->select('checklist_items.*')
        ->join('checklists', 'checklist_items.checklist_id', '=', 'checklists.id')
        ->orderByRaw('FIND_IN_SET(checklist_items.id, checklists.item_order)');
}

Oder, wenn Sie die Tabellen/Felder nicht fest codieren möchten:

public function itemsOrdered()
{
    $orderField = 'item_order';

    $relation = $this->items();
    $parentTable = $relation->getParent()->getTable();
    $related = $relation->getRelated();
    $relatedTable = $related->getTable();

    return $relation
        ->select($relatedTable.'.*')
        ->join($parentTable, $relation->getForeignKey(), '=', $relation->getQualifiedParentKeyName())
        ->orderByRaw('FIND_IN_SET('.$related->getQualifiedKeyName().', '.$parentTable.'.'.$orderField.')');
}

Jetzt kannst du:

$list = Checklist::with('items', 'itemsOrdered')->first();
var_export($list->item_order);
var_export($list->items->lists('id'));
var_export($list->itemsOrdered->lists('id'));

Nur eine Warnung: Dies ist ziemlich experimenteller Code. Es scheint mit der winzigen Menge an Testdaten zu funktionieren, die mir zur Verfügung stehen, aber ich habe so etwas noch nie in einer Produktionsumgebung gemacht. Außerdem wird dies nur in einer HasMany-Beziehung getestet. Wenn Sie genau diesen Code auf einem BelongsToMany oder ähnlichem ausprobieren, treten Probleme auf.

Ich musste kürzlich so etwas tun, aber mit der zusätzlichen Anforderung, dass nicht alle Artikel eine bestimmte Reihenfolge hatten. Die restlichen Artikel ohne festgelegte Reihenfolge mussten alphabetisch geordnet werden.

Also habe ich die Strategie in der akzeptierten Antwort ausprobiert und die Sammlung mit einem benutzerdefinierten Rückruf bestellt, der versucht hat, die fehlenden Elemente zu berücksichtigen. Ich habe es mit mehreren Anrufen zum Laufen gebracht orderBy() aber es war ziemlich langsam.

Als nächstes versuchte ich die andere Antwort und sortierte zuerst mit MySQL FIND_IN_SET() Funktion und dann mit dem Elementnamen. Ich musste sortieren FIND_IN_SET(...) DESC um sicherzustellen, dass die aufgelisteten Artikel ganz oben auf der Liste stehen, aber dies führte auch dazu, dass die bestellten Artikel in umgekehrter Reihenfolge waren.


Also habe ich so etwas gemacht, indem ich in der Datenbank eine Sortierung durch eine Gleichheitsprüfung für jedes Element im Array und dann nach dem Elementnamen durchgeführt habe. Im Gegensatz zur akzeptierten Antwort erfolgt dies als einfache Beziehungsmethode. Das bedeutet, dass ich die magischen Eigenschaften von Laravel behalten kann und jedes Mal, wenn ich diese Liste von Gegenständen erhalte, ist sie immer korrekt sortiert, ohne dass spezielle Methoden aufgerufen werden müssen. Und da dies auf Datenbankebene erfolgt, ist es schneller als in PHP.

class Checklist extends Model {
    protected $casts = ['item_order' => 'array'];

    public function items() {
        return $this->hasMany(ChecklistItem::class)
            ->when(count($this->item_order), function ($q) {
                $table = (new ChecklistItem)->getTable();
                $column = (new ChecklistItem)->getKeyName();
                foreach ($this->item_order as $id) {
                    $q->orderByRaw("`$table`.`$column`!=?", [$id]);
                }
                $q->orderBy('name');
            });
    }
}

Mache jetzt folgendes:

$list = Checklist::with('items')->find(123);
// assume $list->item_order === [34, 81, 28]

Ergibt eine Abfrage wie diese:

select * from `checklist_items`
where `checklist_items`.`checklist_id` = 123 and `checklist_items`.`checklist_id` is not null
order by `checklist_items`.`id`!=34, `checklist_items`.`id`!=81, `checklist_items`.`id`!=28, `name` asc;

843000cookie-checkSortieren der Laravel-Sammlung über ein Array von IDs

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy