通过ID数组对Laravel集合进行排序

2022-08-30 20:40:44

是否可以使用单独的 ID 数组对关系集合进行排序,同时仍然通过关系进行访问?

该设置具有许多 s,并且相关项的所需顺序作为属性存在。它只是用户所需顺序中的数字 ID 数组。:ChecklistChecklistItemChecklist::$item_order

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每个“列表”的顺序都会发生变化。


答案 1

您可以执行以下操作:

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

答案 2

您可以尝试设置一个关系,该关系按您要查找的顺序返回结果。您仍应能够预先加载关系,并按指定顺序获得结果。这一切都假设该字段是逗号分隔的 id 字符串列表。item_order

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

或者,如果您不想对表/字段进行硬编码:

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

现在,您可以:

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

只是一个警告:这是相当实验性的代码。它看起来可以使用我可用的少量测试数据,但我在生产环境中没有做过类似的事情。此外,这仅在HasMany关系上进行测试。如果您在 BelongsToMany 或类似设备上尝试使用此确切的代码,则会遇到问题。


推荐