7
7
namespace Magento \Quote \Model \Quote \Item ;
8
8
9
9
use Magento \Catalog \Api \ProductRepositoryInterface ;
10
+ use Magento \Framework \App \ObjectManager ;
10
11
use Magento \Framework \Exception \CouldNotSaveException ;
11
12
use Magento \Framework \Exception \InputException ;
12
13
use Magento \Framework \Exception \NoSuchEntityException ;
13
14
use Magento \Quote \Api \CartItemRepositoryInterface ;
14
15
use Magento \Quote \Api \CartRepositoryInterface ;
16
+ use Magento \Quote \Api \Data \CartInterface ;
17
+ use Magento \Quote \Api \Data \CartItemInterface ;
15
18
use Magento \Quote \Api \Data \CartItemInterfaceFactory ;
19
+ use Magento \Quote \Model \QuoteMutexInterface ;
20
+ use Magento \Quote \Model \QuoteRepository ;
16
21
17
22
/**
18
23
* Repository for quote item.
24
+ *
25
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
19
26
*/
20
27
class Repository implements CartItemRepositoryInterface
21
28
{
22
29
/**
23
- * Quote repository.
24
- *
25
30
* @var CartRepositoryInterface
26
31
*/
27
32
protected $ quoteRepository ;
28
33
29
34
/**
30
- * Product repository.
31
- *
32
35
* @var ProductRepositoryInterface
33
36
*/
34
37
protected $ productRepository ;
@@ -48,25 +51,33 @@ class Repository implements CartItemRepositoryInterface
48
51
*/
49
52
private $ cartItemOptionsProcessor ;
50
53
54
+ /**
55
+ * @var ?QuoteMutexInterface
56
+ */
57
+ private ?QuoteMutexInterface $ quoteMutex ;
58
+
51
59
/**
52
60
* @param CartRepositoryInterface $quoteRepository
53
61
* @param ProductRepositoryInterface $productRepository
54
62
* @param CartItemInterfaceFactory $itemDataFactory
55
63
* @param CartItemOptionsProcessor $cartItemOptionsProcessor
56
64
* @param CartItemProcessorInterface[] $cartItemProcessors
65
+ * @param QuoteMutexInterface|null $quoteMutex
57
66
*/
58
67
public function __construct (
59
68
CartRepositoryInterface $ quoteRepository ,
60
69
ProductRepositoryInterface $ productRepository ,
61
70
CartItemInterfaceFactory $ itemDataFactory ,
62
71
CartItemOptionsProcessor $ cartItemOptionsProcessor ,
63
- array $ cartItemProcessors = []
72
+ array $ cartItemProcessors = [],
73
+ ?QuoteMutexInterface $ quoteMutex = null
64
74
) {
65
75
$ this ->quoteRepository = $ quoteRepository ;
66
76
$ this ->productRepository = $ productRepository ;
67
77
$ this ->itemDataFactory = $ itemDataFactory ;
68
78
$ this ->cartItemOptionsProcessor = $ cartItemOptionsProcessor ;
69
79
$ this ->cartItemProcessors = $ cartItemProcessors ;
80
+ $ this ->quoteMutex = $ quoteMutex ?: ObjectManager::getInstance ()->get (QuoteMutexInterface::class);
70
81
}
71
82
72
83
/**
@@ -89,7 +100,7 @@ public function getList($cartId)
89
100
/**
90
101
* @inheritdoc
91
102
*/
92
- public function save (\ Magento \ Quote \ Api \ Data \ CartItemInterface $ cartItem )
103
+ public function save (CartItemInterface $ cartItem )
93
104
{
94
105
/** @var \Magento\Quote\Model\Quote $quote */
95
106
$ cartId = $ cartItem ->getQuoteId ();
@@ -99,12 +110,35 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem)
99
110
);
100
111
}
101
112
102
- $ quote = $ this ->quoteRepository ->getActive ($ cartId );
113
+ return $ this ->quoteMutex ->execute (
114
+ [$ cartId ],
115
+ \Closure::fromCallable ([$ this , 'saveItem ' ]),
116
+ [$ cartItem ]
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Save cart item.
122
+ *
123
+ * @param CartItemInterface $cartItem
124
+ * @return CartItemInterface
125
+ * @throws NoSuchEntityException
126
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
127
+ */
128
+ private function saveItem (CartItemInterface $ cartItem )
129
+ {
130
+ $ cartId = (int )$ cartItem ->getQuoteId ();
131
+ if ($ this ->quoteRepository instanceof QuoteRepository) {
132
+ $ quote = $ this ->getNonCachedActiveQuote ($ cartId );
133
+ } else {
134
+ $ quote = $ this ->quoteRepository ->getActive ($ cartId );
135
+ }
103
136
$ quoteItems = $ quote ->getItems ();
104
137
$ quoteItems [] = $ cartItem ;
105
138
$ quote ->setItems ($ quoteItems );
106
139
$ this ->quoteRepository ->save ($ quote );
107
140
$ quote ->collectTotals ();
141
+
108
142
return $ quote ->getLastAddedItem ();
109
143
}
110
144
@@ -130,4 +164,28 @@ public function deleteById($cartId, $itemId)
130
164
131
165
return true ;
132
166
}
167
+
168
+ /**
169
+ * Returns quote repository without internal cache.
170
+ *
171
+ * Prevents usage of cached quote that causes incorrect quote items update by concurrent web-api requests.
172
+ *
173
+ * @param int $cartId
174
+ * @return CartInterface
175
+ * @throws NoSuchEntityException
176
+ */
177
+ private function getNonCachedActiveQuote (int $ cartId ): CartInterface
178
+ {
179
+ $ cachedQuote = $ this ->quoteRepository ->getActive ($ cartId );
180
+ $ className = get_class ($ this ->quoteRepository );
181
+ $ quote = ObjectManager::getInstance ()->create ($ className )->getActive ($ cartId );
182
+ foreach ($ quote ->getItems () as $ quoteItem ) {
183
+ $ cachedQuoteItem = $ cachedQuote ->getItemById ($ quoteItem ->getId ());
184
+ if ($ cachedQuoteItem ) {
185
+ $ quoteItem ->setExtensionAttributes ($ cachedQuoteItem ->getExtensionAttributes ());
186
+ }
187
+ }
188
+
189
+ return $ quote ;
190
+ }
133
191
}
0 commit comments