为什么 HasOneOrMany 不更新父关系?

Why HasOneOrMany doesn't update relations on parent?

我们正在使用 Laravel 5.3 开发一个电子商务购物车,其中包含商品。所以我们的购物车与商品有 1:n 关系。

当我们使用 Illuminate\Database\Eloquent\Relations\HasOneOrMany::save() 将一些产品添加到购物车时,它不会将新项目推送到我们的 $cart->items 集合,必须使用 Collection::push() 方法手动完成。

我们问这个是因为 Illuminate\Database\Eloquent\Relations\BelongsTo::associate() 方法会这样做,所以我们不知道这是否是一个错误。

只是为了更好地理解:

现在我们需要这样做:

$cart = Cart::first();

$cartItem = new CartItem();
$cartItem->quantity = 1;
$cartItem->base_price = 1;
$cartItem->paid_price = 1;

$cart->items()->save($cartItem);
print($cart->items->count()); # returns 0
$cart->items->push($cartItem);
print($cart->items->count()); # returns 1

我们要做的就是上面这段代码,然后能够与我们的列表进行交互(例如更新订单值),而无需调用其他方法。

$cart = Cart::first();

$cartItem = new CartItem();
$cartItem->quantity = 1;
$cartItem->base_price = 1;
$cartItem->paid_price = 1;

$cart->items()->save($cartItem);
print($cart->items->count()); # returns 1

我们正在考虑提出拉取请求,因为我们看到了代码并且可以完成。但这是正确的吗?我们能做到吗?

从这里复制:https://github.com/laravel/framework/issues/14719

谢谢

您可能对 this issue 上的讨论感兴趣。我已经在此处复制了我对该问题的评论,以防 link 在某个时候消失。

有关如何加载关系属性的信息:

This is expected behavior. Once the relationship attribute for a model instance is loaded, it will not be reloaded unless explicitly reloaded.

// relationship attribute lazy loaded here
$blog->posts->count()

$post = new Post(['title' => 'post title');

// typo in OP; save() must be called on relationship method, not relationship attribute
$blog->posts()->save($post);

// relationship already loaded. Collection has not changed.
$blog->posts->count(); // 0

// however, call to database will reflect current count
$blog->posts()->count(); // 1

// reload the relationship attribute
$blog->load('posts');

// relationship collection refreshed; count of relationship attribute will reflect this
$blog->posts->count(); // 1

为什么在将项目添加到关系时不更新关系属性 Collection

I think there's too much uncertainty to attempt modifying the Collection. Even though you're relating the post to the blog through the posts() relationship, there is no guarantee that the new post should be loaded into the relationship attribute Collection in the first place.

Imagine, for example, if your relationship were defined like this (silly example, but bear with me):

// only get posts created before today
public function posts() {
    return $this->hasMany(Post::class)->where('created_at', '<', date('Y-m-d'));
}

Given this relationship, since the new post is created today, it would be incorrect if it were simply injected into the existing lazy loaded $blog->posts collection, because it does not meet the added where condition. If you reload this relationship, the new post will still not be in it.

Another example would be this:

// get the posts, newest first
public function posts() {
    return $this->hasMany(Post::class)->orderBy('created_at', 'DESC');
}

Given this relationship, if the new post is added to the end of the existing loaded $blog->posts collection, it will be in the incorrect order. You could argue that it could be added to the beginning, but then it would be in the incorrect order if the relationship were ordered ASC. You would have to parse the relationship query to try to figure out where to add the item.

Now, those are just simple examples. I'm sure there are plenty of very complicated relationships out there where it would be impossible to parse all the constraints and conditions in order to properly insert the new item into the existing collection.

The only real alternative to the existing functionality would be to always force a reload of an existing loaded relationship whenever the relationship is modified, however that could lead to some serious performance issues. It seems best to make sure the developer knows they need to explicitly reload the relationship if needed, rather than to implicitly reload it, whether it is needed or not.