20

リレーションシップを介してアクセスしながら、ID の別の配列を使用してリレーションシップ コレクションを注文することは可能ですか?

セットアップにChecklistは多くChecklistItemの があり、関連するアイテムの希望する順序はプロパティとして存在しますChecklist::$item_order。これは、ユーザーが希望する順序で並べられた数値 ID の単なる配列です。:

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);
    }
}

この関係に通常の方法でアクセスします。

$list = Checklist::find(1234);

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

配列$list->itemsの値に基づいて注文する実行可能な方法はありますか?$list->item_order

(「アイテム」テーブルに「注文」列を追加することはできません。b/c 各「リスト」の順序が変更されます。)

4

3 に答える 3

33

あなたはこれを行うことができます:

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

また、同じことを行う属性アクセサーをモデルに追加することもできます

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;
}

使用法:

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

複数の場所でこの種の機能が必要な場合は、独自の Collection クラスを作成することをお勧めします。

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);
        }
    }
}

次に、モデルでそのクラスのオーバーライドを実際に使用しnewCollection()ます。この場合、それはChecklistItemsクラスにあります:

public function newCollection(array $models = array())
{
    return new MyCollection($models);
}
于 2015-01-23T22:53:51.273 に答える
0

私は最近、このようなことをしなければなりませんでしたが、すべてのアイテムに特定の順序があるわけではないという追加の要件がありました。順序が指定されていない残りの項目は、アルファベット順に並べる必要がありました。

そこで、受け入れられた回答で戦略を試し、不足しているアイテムを考慮しようとしたカスタムコールバックでコレクションを注文しました。複数の呼び出しで動作するようになりましorderBy()たが、かなり遅かったです。

FIND_IN_SET()次に、最初にMySQL関数でソートし、次にアイテム名でソートして、他の答えを試しました。FIND_IN_SET(...) DESCリストされたアイテムがリストの一番上にくるように並べ替える必要がありましたが、これにより、注文されたアイテムが逆の順序になることもありました。


だから私はこのようなことをして、配列内の各項目の等価性チェックによってデータベースでソートを行い、次に項目名でソートしました。受け入れられた回答とは異なり、これは単純な関係メソッドとして行われます。つまり、Laravel の魔法のプロパティを保持することができ、アイテムのリストを取得するたびに、特別なメソッドを呼び出さなくても常に正しくソートされます。また、データベース レベルで実行されるため、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');
            });
    }
}

今これをやっている:

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

次のようなクエリが返されます。

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;
于 2022-01-19T20:36:04.590 に答える