Laravel 更新模型的一对多关系项
Laravel update model's one-to-many relation's items
我有两个 Eloquent 模型:
class User extends Model
{
public function items()
{
return $this->hasMany(Item::class, "userId");
}
}
class Item extends Model
{
public function user()
{
return $this->belongsTo(User::class, "userId");
}
}
Item
的列是 id
、userId
和 name
。
当我想更新用户(PUT /users/<id>
)时我也想更新同一请求中的项目,意思是:
- 跳过现有项目
- 添加新项目
- 删除多余的项目
之后数据库和关系都应该有最新的项目。
请求数据示例:
{
"userAttribute1": "Something",
"userAttribute2": "Something else",
"items": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
我试图寻找一种使用 Laravel 执行此操作的简单方法,例如对于多对多关系,您可以调用 attach
/detach
和 sync
使用 id 自动更新它们。
我为我的一对多关系做了这个可憎的事:
// Controller method for PUT /users/<id>
public function update(User $user, Request $request)
{
// Update user attributes normal way
$user->fill($request->validated());
$user->save();
// Array, e.g. [ { "name": "Name1" }, { "name": "Name2" } ]
$newItems = $request->validated()["items"];
// Collection of items
$currentItems = $user->items;
// Array to keep track of which items to delete later
$removeItemIds = [];
foreach ($currentItems as $i => $currentItem)
{
// currentItem is Item model
$exists = false;
foreach ($newItems as $j => $newItem)
{
// newItem is array, e.g. { "name": "Name1" }
if ($currentItem->name === $newItem["name"])
{
$exists = true;
break;
}
}
if ($exists)
{
// New item already exists, remove from newItems array
unset($newItems[$j]);
}
else
{
// Current item does't exist anymore, remove from currentItems collection and mark as deletable
$removeItemIds[] = $currentItem->id;
unset($currentItems[$i]);
}
}
// Add remaining new items
foreach ($newItems as $newItem)
{
$item = Item::make($newItem);
$item->userId = $user->id;
$item->save();
$currentItems->push($item);
}
// Delete extra items
$user->items()->whereIn("id", $removeItemIds)->delete();
// Update relation so the returned data is up-to-date as well
$user->setRelation("items", $currentItems);
return [
"user" => new UserResource($user),
];
}
这个用户 + 项目模型只是一个例子 - 我有多个相似的关系(其中不仅仅是 name
列)并且将这段代码复制粘贴到各处并稍微修改它似乎有点愚蠢。
Laravel 以所有这些花哨的快捷方式和易于 use/magic 的方法而闻名,所以我的问题是:是否有更简单、更短的方法来进行此更新?
您可以使用 Laravel collections.
首先,更新用户:
$user->update($request->validated());
然后,您可以同步用户项目:
$new = ['item 1', 'item 2']; // Your request items
// Get the current items your user have:
$items = $user->items->pluck('name')->toArray();
// Now, you need to delete the items your user have but are not present in the request items array:
$deleteItems = $user->items->pluck('name', 'id')
->reject(function($value, $id) use ($new) {
return in_array($value, $new);
})
->keys();
Item::whereIn('id', $deleteItems)->delete();
// Last but not least, you need to create the new items and attach it to the user:
collect($new)->each(function($insertData) use ($user) {
$user->items()->firstOrcreate([
'name' => $insertData
]);
});
对于具有多个字段的模型,您需要将两个数组传递给firstOrCreate
方法。第一个数组将用于查找模型,如果没有找到,它将通过合并两个数组来创建:
$user->items()->firstOrcreate([
'name' => $insertData
], [
'description' => $description,
'quantity' => $quantity
]);
由于您使用的是 firstOrCreate
,它只会在未通过名称找到项目时创建该项目,并且您不会有重复的项目。
我有两个 Eloquent 模型:
class User extends Model
{
public function items()
{
return $this->hasMany(Item::class, "userId");
}
}
class Item extends Model
{
public function user()
{
return $this->belongsTo(User::class, "userId");
}
}
Item
的列是 id
、userId
和 name
。
当我想更新用户(PUT /users/<id>
)时我也想更新同一请求中的项目,意思是:
- 跳过现有项目
- 添加新项目
- 删除多余的项目
之后数据库和关系都应该有最新的项目。
请求数据示例:
{
"userAttribute1": "Something",
"userAttribute2": "Something else",
"items": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
我试图寻找一种使用 Laravel 执行此操作的简单方法,例如对于多对多关系,您可以调用 attach
/detach
和 sync
使用 id 自动更新它们。
我为我的一对多关系做了这个可憎的事:
// Controller method for PUT /users/<id>
public function update(User $user, Request $request)
{
// Update user attributes normal way
$user->fill($request->validated());
$user->save();
// Array, e.g. [ { "name": "Name1" }, { "name": "Name2" } ]
$newItems = $request->validated()["items"];
// Collection of items
$currentItems = $user->items;
// Array to keep track of which items to delete later
$removeItemIds = [];
foreach ($currentItems as $i => $currentItem)
{
// currentItem is Item model
$exists = false;
foreach ($newItems as $j => $newItem)
{
// newItem is array, e.g. { "name": "Name1" }
if ($currentItem->name === $newItem["name"])
{
$exists = true;
break;
}
}
if ($exists)
{
// New item already exists, remove from newItems array
unset($newItems[$j]);
}
else
{
// Current item does't exist anymore, remove from currentItems collection and mark as deletable
$removeItemIds[] = $currentItem->id;
unset($currentItems[$i]);
}
}
// Add remaining new items
foreach ($newItems as $newItem)
{
$item = Item::make($newItem);
$item->userId = $user->id;
$item->save();
$currentItems->push($item);
}
// Delete extra items
$user->items()->whereIn("id", $removeItemIds)->delete();
// Update relation so the returned data is up-to-date as well
$user->setRelation("items", $currentItems);
return [
"user" => new UserResource($user),
];
}
这个用户 + 项目模型只是一个例子 - 我有多个相似的关系(其中不仅仅是 name
列)并且将这段代码复制粘贴到各处并稍微修改它似乎有点愚蠢。
Laravel 以所有这些花哨的快捷方式和易于 use/magic 的方法而闻名,所以我的问题是:是否有更简单、更短的方法来进行此更新?
您可以使用 Laravel collections.
首先,更新用户:
$user->update($request->validated());
然后,您可以同步用户项目:
$new = ['item 1', 'item 2']; // Your request items
// Get the current items your user have:
$items = $user->items->pluck('name')->toArray();
// Now, you need to delete the items your user have but are not present in the request items array:
$deleteItems = $user->items->pluck('name', 'id')
->reject(function($value, $id) use ($new) {
return in_array($value, $new);
})
->keys();
Item::whereIn('id', $deleteItems)->delete();
// Last but not least, you need to create the new items and attach it to the user:
collect($new)->each(function($insertData) use ($user) {
$user->items()->firstOrcreate([
'name' => $insertData
]);
});
对于具有多个字段的模型,您需要将两个数组传递给firstOrCreate
方法。第一个数组将用于查找模型,如果没有找到,它将通过合并两个数组来创建:
$user->items()->firstOrcreate([
'name' => $insertData
], [
'description' => $description,
'quantity' => $quantity
]);
由于您使用的是 firstOrCreate
,它只会在未通过名称找到项目时创建该项目,并且您不会有重复的项目。