• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aimeos / aimeos-core / 4f8b7e8d-5595-43b2-ba5c-df9921830178

17 May 2026 07:32AM UTC coverage: 92.581%. Remained the same
4f8b7e8d-5595-43b2-ba5c-df9921830178

push

circleci

aimeos
Fixed PHPStan issues

896 of 980 new or added lines in 165 files covered. (91.43%)

35 existing lines in 29 files now uncovered.

9734 of 10514 relevant lines covered (92.58%)

80.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.08
/src/MShop/Order/Manager/Update.php
1
<?php
2

3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2026
6
 * @package MShop
7
 * @subpackage Order
8
 */
9

10

11
namespace Aimeos\MShop\Order\Manager;
12

13

14
/**
15
 * Update trait for order managers
16
 *
17
 * @package MShop
18
 * @subpackage Order
19
 */
20
trait Update
21
{
22
        /**
23
         * Returns the context item object.
24
         *
25
         * @return \Aimeos\MShop\ContextIface Context item object
26
         */
27
        abstract protected function context() : \Aimeos\MShop\ContextIface;
28

29

30
        /**
31
         * Blocks the resources listed in the order.
32
         *
33
         * Every order contains resources like products or redeemed coupon codes
34
         * that must be blocked so they can't be used by another customer in a
35
         * later order. This method reduces the the stock level of products, the
36
         * counts of coupon codes and others.
37
         *
38
         * It's save to call this method multiple times for one order. In this case,
39
         * the actions will be executed only once. All subsequent calls will do
40
         * nothing as long as the resources haven't been unblocked in the meantime.
41
         *
42
         * You can also block and unblock resources several times. Please keep in
43
         * mind that unblocked resources may be reused by other orders in the
44
         * meantime. This can lead to an oversell of products!
45
         *
46
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
47
         * @return \Aimeos\MShop\Order\Item\Iface Order item object
48
         */
49
        public function block( \Aimeos\MShop\Order\Item\Iface $orderItem ) : \Aimeos\MShop\Order\Item\Iface
50
        {
51
                $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, (string) 1, -1 );
1✔
52
                $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, (string) 1, -1 );
1✔
53

54
                return $orderItem;
1✔
55
        }
56

57

58
        /**
59
         * Frees the resources listed in the order.
60
         *
61
         * If customers created orders but didn't pay for them, the blocked resources
62
         * like products and redeemed coupon codes must be unblocked so they can be
63
         * ordered again or used by other customers. This method increased the stock
64
         * level of products, the counts of coupon codes and others.
65
         *
66
         * It's save to call this method multiple times for one order. In this case,
67
         * the actions will be executed only once. All subsequent calls will do
68
         * nothing as long as the resources haven't been blocked in the meantime.
69
         *
70
         * You can also unblock and block resources several times. Please keep in
71
         * mind that unblocked resources may be reused by other orders in the
72
         * meantime. This can lead to an oversell of products!
73
         *
74
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
75
         * @return \Aimeos\MShop\Order\Item\Iface Order item object
76
         */
77
        public function unblock( \Aimeos\MShop\Order\Item\Iface $orderItem ) : \Aimeos\MShop\Order\Item\Iface
78
        {
79
                $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, (string) 0, +1 );
1✔
80
                $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, (string) 0, +1 );
1✔
81

82
                return $orderItem;
1✔
83
        }
84

85

86
        /**
87
         * Blocks or frees the resources listed in the order if necessary.
88
         *
89
         * After payment status updates, the resources like products or coupon
90
         * codes listed in the order must be blocked or unblocked. This method
91
         * cares about executing the appropriate action depending on the payment
92
         * status.
93
         *
94
         * It's save to call this method multiple times for one order. In this case,
95
         * the actions will be executed only once. All subsequent calls will do
96
         * nothing as long as the payment status hasn't changed in the meantime.
97
         *
98
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
99
         * @return \Aimeos\MShop\Order\Item\Iface Order item object
100
         */
101
        public function update( \Aimeos\MShop\Order\Item\Iface $orderItem ) : \Aimeos\MShop\Order\Item\Iface
102
        {
103
                switch( $orderItem->getStatusPayment() )
2✔
104
                {
105
                        case \Aimeos\MShop\Order\Item\Base::PAY_DELETED:
2✔
106
                        case \Aimeos\MShop\Order\Item\Base::PAY_CANCELED:
1✔
107
                        case \Aimeos\MShop\Order\Item\Base::PAY_REFUSED:
1✔
108
                        case \Aimeos\MShop\Order\Item\Base::PAY_REFUND:
1✔
109
                                $this->unblock( $orderItem );
1✔
110
                                break;
1✔
111

112
                        case \Aimeos\MShop\Order\Item\Base::PAY_PENDING:
1✔
113
                        case \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED:
×
114
                        case \Aimeos\MShop\Order\Item\Base::PAY_RECEIVED:
×
115
                                $this->block( $orderItem );
1✔
116
                                break;
1✔
117
                }
118

119
                return $orderItem;
2✔
120
        }
121

122

123
        /**
124
         * Adds a new status record to the order with the type and value.
125
         *
126
         * @param string $parentid Order ID
127
         * @param string $type Status type
128
         * @param string $value Status value
129
         * @return \Aimeos\MShop\Order\Manager\Iface Same manager for fluent interface
130
         */
131
        protected function addStatusItem( string $parentid, string $type, string $value ) : Iface
132
        {
133
                $manager = \Aimeos\MShop::create( $this->context(), 'order/status' );
1✔
134

135
                $item = $manager->create();
1✔
136
                $item->setParentId( $parentid );
1✔
137
                $item->setType( $type );
1✔
138
                $item->setValue( $value );
1✔
139

140
                $manager->save( $item, false );
1✔
141

142
                return $this;
1✔
143
        }
144

145

146
        /**
147
         * Returns the product articles and their bundle product codes for the given article ID
148
         *
149
         * @param string $prodId Product ID of the article whose stock level changed
150
         * @return array Associative list of article codes as keys and lists of bundle product codes as values
151
         */
152
        protected function getBundleMap( string $prodId ) : array
153
        {
154
                $bundleMap = [];
1✔
155
                $productManager = \Aimeos\MShop::create( $this->context(), 'product' );
1✔
156

157
                $search = $productManager->filter();
1✔
158
                $func = $search->make( 'product:has', ['product', 'default', $prodId] );
1✔
159
                $expr = array(
1✔
160
                        $search->compare( '==', 'product.type', ['bundle', 'group'] ),
1✔
161
                        $search->compare( '!=', $func, null ),
1✔
162
                );
1✔
163
                $search->setConditions( $search->and( $expr ) );
1✔
164
                $search->slice( 0, 0x7fffffff );
1✔
165

166
                $bundleItems = $productManager->search( $search, array( 'product' ) );
1✔
167

168
                foreach( $bundleItems as $bundleItem )
1✔
169
                {
170
                        foreach( $bundleItem->getRefItems( 'product', null, 'default' ) as $item ) {
1✔
171
                                $bundleMap[$item->getId()][] = $bundleItem->getId();
1✔
172
                        }
173
                }
174

175
                return $bundleMap;
1✔
176
        }
177

178

179
        /**
180
         * Returns the last status item for the given order ID.
181
         *
182
         * @param string $parentid Order ID
183
         * @param string $type Status type constant
184
         * @param string $status New status value stored along with the order item
185
         * @return \Aimeos\MShop\Order\Item\Status\Iface|null Order status item or NULL if no item is available
186
         */
187
        protected function getLastStatusItem( string $parentid, string $type, string $status ) : ?\Aimeos\MShop\Order\Item\Status\Iface
188
        {
189
                $manager = \Aimeos\MShop::create( $this->context(), 'order/status' );
2✔
190

191
                $search = $manager->filter();
2✔
192
                $expr = array(
2✔
193
                        $search->compare( '==', 'order.status.parentid', $parentid ),
2✔
194
                        $search->compare( '==', 'order.status.type', $type ),
2✔
195
                        $search->compare( '==', 'order.status.value', $status ),
2✔
196
                );
2✔
197
                $search->setConditions( $search->and( $expr ) );
2✔
198
                // @phpstan-ignore argument.type
199
                $search->setSortations( array( $search->sort( '-', 'order.status.ctime' ) ) );
2✔
200
                $search->slice( 0, 1 );
2✔
201

202
                // @phpstan-ignore return.type
203
                return $manager->search( $search )->first();
2✔
204
        }
205

206

207
        /**
208
         * Returns the stock items for the given product codes
209
         *
210
         * @param iterable $prodIds List of product codes
211
         * @param string $stockType Stock type code the stock items must belong to
212
         * @return \Aimeos\Map Associative list of \Aimeos\MShop\Stock\Item\Iface and IDs as values
213
         */
214
        protected function getStockItems( iterable $prodIds, string $stockType ) : \Aimeos\Map
215
        {
216
                $stockManager = \Aimeos\MShop::create( $this->context(), 'stock' );
2✔
217

218
                $search = $stockManager->filter()->slice( 0, 0x7fffffff )
2✔
219
                        ->add( ['stock.productid' => $prodIds, 'stock.type' => $stockType] );
2✔
220

221
                return $stockManager->search( $search );
2✔
222
        }
223

224

225
        /**
226
         * Increases or decreses the coupon code counts referenced in the order by the given value.
227
         *
228
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
229
         * @param int $how Positive or negative integer number for increasing or decreasing the coupon count
230
         * @return static Self object for method chaining
231
         */
232
        protected function updateCoupons( \Aimeos\MShop\Order\Item\Iface $orderItem, int $how = +1 )
233
        {
234
                $context = $this->context();
2✔
235
                $manager = \Aimeos\MShop::create( $context, 'order/coupon' );
2✔
236
                $couponCodeManager = \Aimeos\MShop::create( $context, 'coupon/code' );
2✔
237

238
                $search = $manager->filter();
2✔
239
                $search->setConditions( $search->compare( '==', 'order.coupon.parentid', $orderItem->getId() ) );
2✔
240

241
                $start = 0;
2✔
242

243
                $couponCodeManager->begin();
2✔
244

245
                try
246
                {
247
                        do
248
                        {
249
                                $items = $manager->search( $search );
2✔
250

251
                                foreach( $items as $item ) {
2✔
252
                                        $couponCodeManager->decrease( $item->getCode(), $how * -1 );
2✔
253
                                }
254

255
                                $count = count( $items );
1✔
256
                                $start += $count;
1✔
257
                                $search->slice( $start );
1✔
258
                        }
259
                        while( $count >= $search->getLimit() );
1✔
260

261
                        $couponCodeManager->commit();
1✔
262
                }
263
                catch( \Exception $e )
1✔
264
                {
265
                        $couponCodeManager->rollback();
1✔
266
                        throw $e;
1✔
267
                }
268

269
                return $this;
1✔
270
        }
271

272

273
        /**
274
         * Increases or decreases the stock level or the coupon code count for referenced items of the given order.
275
         *
276
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
277
         * @param string $type Constant from \Aimeos\MShop\Order\Item\Status\Base, e.g. STOCK_UPDATE or COUPON_UPDATE
278
         * @param string $status New status value stored along with the order item
279
         * @param int $value Number to increse or decrease the stock level or coupon code count
280
         * @return static Self object for method chaining
281
         */
282
        protected function updateStatus( \Aimeos\MShop\Order\Item\Iface $orderItem, string $type, string $status, int $value )
283
        {
284
                $statusItem = $this->getLastStatusItem( (string) $orderItem->getId(), $type, $status );
3✔
285

286
                if( $statusItem && $statusItem->getValue() == $status ) {
3✔
287
                        return $this;
1✔
288
                }
289

290
                if( $type == \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE ) {
2✔
291
                        $this->updateStock( $orderItem, $value );
1✔
292
                } elseif( $type == \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE ) {
1✔
293
                        $this->updateCoupons( $orderItem, $value );
1✔
294
                }
295

296
                // @phpstan-ignore return.type
297
                return $this->addStatusItem( (string) $orderItem->getId(), $type, $status );
2✔
298
        }
299

300

301
        /**
302
         * Increases or decreases the stock levels of the products referenced in the order by the given value.
303
         *
304
         * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
305
         * @param int $how Positive or negative integer number for increasing or decreasing the stock levels
306
         * @return static Self object for method chaining
307
         */
308
        protected function updateStock( \Aimeos\MShop\Order\Item\Iface $orderItem, int $how = +1 )
309
        {
310
                $context = $this->context();
4✔
311
                $stockManager = \Aimeos\MShop::create( $context, 'stock' );
4✔
312
                $manager = \Aimeos\MShop::create( $context, 'order/product' );
4✔
313

314
                $search = $manager->filter();
4✔
315
                $search->setConditions( $search->compare( '==', 'order.product.parentid', $orderItem->getId() ) );
4✔
316

317
                $start = 0;
4✔
318

319
                $stockManager->begin();
4✔
320

321
                try
322
                {
323
                        do
324
                        {
325
                                $items = $manager->search( $search );
4✔
326

327
                                foreach( $items as $item )
3✔
328
                                {
329
                                        $stockManager->decrease( [(string) $item->getProductId() => -1 * $how * $item->getQuantity()], $item->getStockType() );
3✔
330

331
                                        switch( $item->getType() ) {
3✔
332
                                                case 'default':
3✔
333
                                                        $this->updateStockBundle( (string) $item->getParentProductId(), (string) $item->getStockType() ); break;
1✔
334
                                                case 'select':
2✔
335
                                                        $this->updateStockSelection( (string) $item->getParentProductId(), (string) $item->getStockType() ); break;
1✔
336
                                        }
337
                                }
338

339
                                $count = count( $items );
3✔
340
                                $start += $count;
3✔
341
                                $search->slice( $start );
3✔
342
                        }
343
                        while( $count >= $search->getLimit() );
3✔
344

345
                        $stockManager->commit();
3✔
346
                }
347
                catch( \Exception $e )
1✔
348
                {
349
                        $stockManager->rollback();
1✔
350
                        throw $e;
1✔
351
                }
352

353
                return $this;
3✔
354
        }
355

356

357
        /**
358
         * Updates the stock levels of bundles for a specific type
359
         *
360
         * @param string $prodId Unique product ID
361
         * @param string $stockType Unique stock type
362
         * @return static Self object for method chaining
363
         */
364
        protected function updateStockBundle( string $prodId, string $stockType )
365
        {
366
                if( ( $bundleMap = $this->getBundleMap( $prodId ) ) === [] ) {
1✔
NEW
367
                        return $this;
×
368
                }
369

370

371
                $bundleIds = $stock = [];
1✔
372

373
                foreach( $this->getStockItems( array_keys( $bundleMap ), $stockType ) as $stockItem )
1✔
374
                {
375
                        if( isset( $bundleMap[$stockItem->getProductId()] ) && $stockItem->getStockLevel() !== null )
1✔
376
                        {
377
                                foreach( $bundleMap[$stockItem->getProductId()] as $bundleId )
1✔
378
                                {
379
                                        if( isset( $stock[$bundleId] ) ) {
1✔
380
                                                $stock[$bundleId] = min( $stock[$bundleId], $stockItem->getStockLevel() );
1✔
381
                                        } else {
382
                                                $stock[$bundleId] = $stockItem->getStockLevel();
1✔
383
                                        }
384

385
                                        $bundleIds[$bundleId] = null;
1✔
386
                                }
387
                        }
388
                }
389

390
                if( empty( $stock ) ) {
1✔
NEW
391
                        return $this;
×
392
                }
393

394
                $stockManager = \Aimeos\MShop::create( $this->context(), 'stock' );
1✔
395

396
                foreach( $this->getStockItems( array_keys( $bundleIds ), $stockType ) as $item )
1✔
397
                {
398
                        if( isset( $stock[$item->getProductId()] ) )
1✔
399
                        {
400
                                $item->setStockLevel( $stock[$item->getProductId()] );
1✔
401
                                // @phpstan-ignore argument.type
402
                                $stockManager->save( $item );
1✔
403
                        }
404
                }
405

406
                return $this;
1✔
407
        }
408

409

410
        /**
411
         * Updates the stock levels of selection products for a specific type
412
         *
413
         * @param string $prodId Unique product ID
414
         * @param string $stocktype Unique stock type
415
         * @return static Self object for method chaining
416
         */
417
        protected function updateStockSelection( string $prodId, string $stocktype )
418
        {
419
                $stockManager = \Aimeos\MShop::create( $this->context(), 'stock' );
1✔
420
                $productManager = \Aimeos\MShop::create( $this->context(), 'product' );
1✔
421

422
                $productItem = $productManager->get( $prodId, ['product'] );
1✔
423
                $prodIds = $productItem->getRefItems( 'product', 'default', 'default' )->getId()->push( $productItem->getId() );
1✔
424

425
                // @phpstan-ignore argument.type
426
                $stockItems = $this->getStockItems( $prodIds, $stocktype );
1✔
427
                $selStockItem = $stockItems->col( null, 'stock.productid' )->pull( $prodId ) ?: $stockManager->create();
1✔
428

429
                $sum = $stockItems->getStockLevel()->reduce( function( $result, $value ) {
1✔
430
                        return $result !== null && $value !== null ? $result + $value : null;
1✔
431
                }, 0 );
1✔
432

433
                $selStockItem->setProductId( $productItem->getId() )->setType( $stocktype )->setStockLevel( $sum );
1✔
434
                // @phpstan-ignore argument.type
435
                $stockManager->save( $selStockItem, false );
1✔
436

437
                return $this;
1✔
438
        }
439
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc