【问题标题】:Sorting Laravel Collection via Array of ID's将 Laravel 集合排序为 ID 数组
【发布时间】:2015-01-23 20:39:39
【问题描述】:

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

设置为Checklist 有许多ChecklistItems,并且相关项目的所需顺序作为属性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->item_order 数组中的值对$list->items 进行排序?

(我不能只在“项目”表中添加一个“订单”列,因为每个“列表”的订单都会发生变化。)

【问题讨论】:

  • 我不知道你会如何在 laravel 中做到这一点,但我知道在直接的 mysql 查询中,如果你想要按预定义顺序排序的东西,你可以使用 field 或 @ 987654329@ 作为您订单的一部分,并以您想要的任何顺序列出值。或者,您可以在选择(或顺序)中使用 case 语句,按该列返回数字和顺序。除非 laravel 有其他方法,否则我能想到的唯一其他方法是将项目存储在一个数组中并使用您的 id 数组进行排序以获得所需的顺序。
  • @Matthew Brown $list->item_order 是有序的项目 ID 数组??
  • @voodoo417 是的,这是正确的

标签: php laravel eloquent-relationship


【解决方案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);
}

【讨论】:

  • 我确实最终使用了这种方法,效果很好。我不知道 sortBy 函数也接受闭包。
【解决方案2】:

您可以尝试建立一个关系,以您正在查找的顺序返回结果。您仍然应该能够预先加载关系,并按指定顺序获得结果。这都是假设 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)');
}

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

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 或类似的东西上尝试这个确切的代码,你会遇到问题。

【讨论】:

    【解决方案3】:

    我最近不得不做这样的事情,但附加要求并非所有物品都有特定的顺序。其余没有指定顺序的项目必须按字母顺序排列。

    所以我在接受的答案中尝试了策略,使用自定义回调对集合进行排序,该回调试图解释丢失的项目。我通过多次调用 orderBy() 让它工作,但速度很慢。

    接下来我尝试了另一个答案,首先使用 MySQL FIND_IN_SET() 函数进行排序,然后使用项目名称进行排序。我必须按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;
    

    【讨论】:

      猜你喜欢
      • 2016-11-29
      • 2019-05-26
      • 1970-01-01
      • 2015-08-23
      • 2016-09-13
      • 2016-05-12
      • 2017-06-06
      • 2019-08-10
      • 2016-11-19
      相关资源
      最近更新 更多