通过 ID 数组对 Laravel 集合进行排序
Sorting Laravel Collection via Array of ID's
是否可以在仍然通过关系访问的同时使用单独的 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->item_order
数组中的值对 $list->items
进行排序?
(我不能只在 'item' table 中添加一个 'order' 列,b/c 每个 'list' 的顺序都会改变。)
你可以这样做:
$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:
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);
}
}
}
然后,在您的模型中实际使用 class 覆盖 newCollection()
。在这种情况下,它将位于 ChecklistItems
class:
public function newCollection(array $models = array())
{
return new MyCollection($models);
}
您可以尝试建立一个关系,returns 结果按您要查找的顺序排列。您仍然应该能够预先加载关系,并以指定的顺序获得结果。这一切都假设 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)');
}
或者,如果您不想对 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.')');
}
现在,您可以:
$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 或类似的东西上尝试这个确切的代码,你会遇到问题。
我最近不得不做这样的事情,但额外的要求是并非所有项目都有特定的顺序。其余没有指定顺序的项目必须按字母顺序排列。
所以我尝试了接受的答案中的策略,使用自定义回调对集合进行排序,该回调试图解决丢失的项目。我通过多次调用 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;
是否可以在仍然通过关系访问的同时使用单独的 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->item_order
数组中的值对 $list->items
进行排序?
(我不能只在 'item' table 中添加一个 'order' 列,b/c 每个 'list' 的顺序都会改变。)
你可以这样做:
$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:
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);
}
}
}
然后,在您的模型中实际使用 class 覆盖 newCollection()
。在这种情况下,它将位于 ChecklistItems
class:
public function newCollection(array $models = array())
{
return new MyCollection($models);
}
您可以尝试建立一个关系,returns 结果按您要查找的顺序排列。您仍然应该能够预先加载关系,并以指定的顺序获得结果。这一切都假设 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)');
}
或者,如果您不想对 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.')');
}
现在,您可以:
$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 或类似的东西上尝试这个确切的代码,你会遇到问题。
我最近不得不做这样的事情,但额外的要求是并非所有项目都有特定的顺序。其余没有指定顺序的项目必须按字母顺序排列。
所以我尝试了接受的答案中的策略,使用自定义回调对集合进行排序,该回调试图解决丢失的项目。我通过多次调用 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;