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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28118361/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 10:46:00  来源:igfitidea点击:

Sorting Laravel Collection via Array of ID's

phplaravel

提问by Matthew Brown

Is it possible to order a relationship collection using a separate array of ID's while still accessing through the relationship?

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

The setup is Checklisthas many ChecklistItems, and I access this relationship the normal way:

设置Checklist有 many ChecklistItems,我以正常方式访问此关系:

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

Now the desired order of $itemexists as a property on $listunder the attribute $list->item_order, it is just an array of $itemID's in the users desired order, specific to the $listbeing iterated.

现在所需的顺序$item作为属性$list下的属性存在$list->item_order,它只是$item用户所需顺序的ID数组,特定于$list被迭代的。

Is there a feasible way to order the $itemsattached to the $listbased on the item_orderarray that the $listmodel has?

是否有一种可行的方法可以根据模型具有的数组来订购$items附加到的?$listitem_order$list

(I can't just add a 'order' column to the 'item' table, b/c order changes based on specific 'list' relationship).

(我不能只在“项目”表中添加一个“订单”列,b/c 订单会根据特定的“列表”关系进行更改)。

Thanks for any help!

谢谢你的帮助!

回答by lukasgeiter

You can do this:

你可以这样做:

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

Also you could add an attribute accessor to your model which does the same

你也可以在你的模型中添加一个属性访问器来做同样的事情

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

Usage:

用法:

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


If you need this sort of functionality in multiple places I suggest you create your own Collection class:

如果您在多个地方需要这种功能,我建议您创建自己的 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);
        }
    }
}

Then, to actually use that class override newCollection()in your model. In this case it would be in the ChecklistItemsclass:

然后,newCollection()在您的模型中实际使用该类覆盖。在这种情况下,它将在ChecklistItems类中:

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

回答by patricus

You can try setting up a relationship that returns the results in the order for which you're looking. You should still be able to eager load the relationship, and have the results in the specified order. This is all assuming the item_orderfield is a comma separated string list of ids.

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

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

Or, if you don't want to hardcode the tables/fields:

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

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

Now, you can:

现在你可以:

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

Just a warning: this is fairly experimental code. It looks to work with the tiny amount of test data I have available to me, but I have not done anything like this in a production environment. Also, this is only tested on a HasMany relationship. If you try this exact code on a BelongsToMany or anything like that, you'll have issues.

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