Completed
Push — symfony3-fqcn-sylius-3 ( 1d8d47...2ae323 )
by Kamil
38:58 queued 18:19
created

customersHavePlacedOrdersForTotalOfMostlyProduct()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 16
nc 3
nop 4
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sylius\Behat\Context\Setup;
13
14
use Behat\Behat\Context\Context;
15
use Doctrine\Common\Persistence\ObjectManager;
16
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
17
use Sylius\Component\Core\Currency\CurrencyStorageInterface;
18
use Sylius\Component\Core\Model\ChannelInterface;
19
use Sylius\Component\Core\Model\PromotionCouponInterface;
20
use Sylius\Component\Core\OrderCheckoutTransitions;
21
use Sylius\Component\Order\Processor\OrderProcessorInterface;
22
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
23
use Sylius\Component\Core\Model\AddressInterface;
24
use Sylius\Component\Core\Model\OrderInterface;
25
use Sylius\Component\Core\Model\OrderItemInterface;
26
use Sylius\Component\Core\Model\ProductInterface;
27
use Sylius\Component\Core\Model\ProductVariantInterface;
28
use Sylius\Component\Core\Model\ShippingMethodInterface;
29
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
30
use Sylius\Behat\Service\SharedStorageInterface;
31
use Sylius\Component\Order\OrderTransitions;
32
use Sylius\Component\Payment\Model\PaymentInterface;
33
use Sylius\Component\Payment\Model\PaymentMethodInterface;
34
use Sylius\Component\Payment\PaymentTransitions;
35
use Sylius\Component\Resource\Factory\FactoryInterface;
36
use Sylius\Component\Resource\Repository\RepositoryInterface;
37
use Sylius\Component\Shipping\ShipmentTransitions;
38
use Sylius\Component\Customer\Model\CustomerInterface;
39
use Sylius\Component\User\Model\UserInterface;
40
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
41
42
/**
43
 * @author Łukasz Chruściel <[email protected]>
44
 */
45
final class OrderContext implements Context
46
{
47
    /**
48
     * @var SharedStorageInterface
49
     */
50
    private $sharedStorage;
51
52
    /**
53
     * @var OrderRepositoryInterface
54
     */
55
    private $orderRepository;
56
57
    /**
58
     * @var FactoryInterface
59
     */
60
    private $orderFactory;
61
62
    /**
63
     * @var OrderProcessorInterface
64
     */
65
    private $orderProcessor;
66
67
    /**
68
     * @var FactoryInterface
69
     */
70
    private $orderItemFactory;
71
72
    /**
73
     * @var OrderItemQuantityModifierInterface
74
     */
75
    private $itemQuantityModifier;
76
77
    /**
78
     * @var RepositoryInterface
79
     */
80
    private $currencyRepository;
81
82
    /**
83
     * @var CurrencyStorageInterface
84
     */
85
    private $currencyStorage;
86
87
    /**
88
     * @var FactoryInterface
89
     */
90
    private $customerFactory;
91
92
    /**
93
     * @var RepositoryInterface
94
     */
95
    private $customerRepository;
96
97
    /**
98
     * @var ObjectManager
99
     */
100
    private $objectManager;
101
102
    /**
103
     * @var StateMachineFactoryInterface
104
     */
105
    private $stateMachineFactory;
106
107
    /**
108
     * @var ProductVariantResolverInterface
109
     */
110
    private $variantResolver;
111
112
    /**
113
     * @param SharedStorageInterface $sharedStorage
114
     * @param OrderRepositoryInterface $orderRepository
115
     * @param FactoryInterface $orderFactory
116
     * @param OrderProcessorInterface $orderProcessor
117
     * @param FactoryInterface $orderItemFactory
118
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
119
     * @param RepositoryInterface $currencyRepository
120
     * @param CurrencyStorageInterface $currencyStorage
121
     * @param FactoryInterface $customerFactory
122
     * @param RepositoryInterface $customerRepository
123
     * @param ObjectManager $objectManager
124
     * @param StateMachineFactoryInterface $stateMachineFactory
125
     * @param ProductVariantResolverInterface $variantResolver
126
     */
127
    public function __construct(
128
        SharedStorageInterface $sharedStorage,
129
        OrderRepositoryInterface $orderRepository,
130
        FactoryInterface $orderFactory,
131
        OrderProcessorInterface $orderProcessor,
132
        FactoryInterface $orderItemFactory,
133
        OrderItemQuantityModifierInterface $itemQuantityModifier,
134
        RepositoryInterface $currencyRepository,
135
        CurrencyStorageInterface $currencyStorage,
136
        FactoryInterface $customerFactory,
137
        RepositoryInterface $customerRepository,
138
        ObjectManager $objectManager,
139
        StateMachineFactoryInterface $stateMachineFactory,
140
        ProductVariantResolverInterface $variantResolver
141
    ) {
142
        $this->sharedStorage = $sharedStorage;
143
        $this->orderRepository = $orderRepository;
144
        $this->orderFactory = $orderFactory;
145
        $this->orderProcessor = $orderProcessor;
146
        $this->orderItemFactory = $orderItemFactory;
147
        $this->itemQuantityModifier = $itemQuantityModifier;
148
        $this->currencyRepository = $currencyRepository;
149
        $this->currencyStorage = $currencyStorage;
150
        $this->customerFactory = $customerFactory;
151
        $this->customerRepository = $customerRepository;
152
        $this->objectManager = $objectManager;
153
        $this->stateMachineFactory = $stateMachineFactory;
154
        $this->variantResolver = $variantResolver;
155
    }
156
157
    /**
158
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
159
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
160
     * @Given a customer :customer placed an order :orderNumber
161
     * @Given the customer :customer has already placed an order :orderNumber
162
     */
163
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
164
    {
165
        $order = $this->createOrder($customer, $orderNumber);
166
167
        $this->sharedStorage->set('order', $order);
168
169
        $this->orderRepository->add($order);
170
    }
171
172
    /**
173
     * @Given a customer :customer added something to cart
174
     */
175
    public function customerStartedCheckout(CustomerInterface $customer)
176
    {
177
        $cart = $this->createCart($customer);
178
179
        $this->sharedStorage->set('cart', $cart);
180
181
        $this->orderRepository->add($cart);
182
    }
183
184
    /**
185
     * @Given /^(I) placed (an order "[^"]+")$/
186
     */
187
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
188
    {
189
        $customer = $user->getCustomer();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\User\Model\UserInterface as the method getCustomer() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ShopUser.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
190
        $order = $this->createOrder($customer, $orderNumber);
191
192
        $this->sharedStorage->set('order', $order);
193
194
        $this->orderRepository->add($order);
195
    }
196
197
    /**
198
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
199
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
200
     */
201
    public function theCustomerAddressedItTo(AddressInterface $address)
202
    {
203
        /** @var OrderInterface $order */
204
        $order = $this->sharedStorage->get('order');
205
        $order->setShippingAddress($address);
206
207
        $this->objectManager->flush();
208
    }
209
210
    /**
211
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
212
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
213
     */
214
    public function forTheBillingAddressOf(AddressInterface $address)
215
    {
216
        /** @var OrderInterface $order */
217
        $order = $this->sharedStorage->get('order');
218
219
        $order->setBillingAddress($address);
220
221
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
222
223
        $this->objectManager->flush();
224
    }
225
226
    /**
227
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
228
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
229
     */
230
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
231
    {
232
        $this->theCustomerAddressedItTo($address);
233
        $this->forTheBillingAddressOf($address);
234
    }
235
236
    /**
237
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
238
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
239
     */
240
    public function theCustomerChoseShippingToWithPayment(
241
        ShippingMethodInterface $shippingMethod,
242
        AddressInterface $address,
243
        PaymentMethodInterface $paymentMethod
244
    ) {
245
        /** @var OrderInterface $order */
246
        $order = $this->sharedStorage->get('order');
247
248
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
249
250
        $this->objectManager->flush();
251
    }
252
253
    /**
254
     * @Given the customer has chosen to order in the :currencyCode currency
255
     * @Given I have chosen to order in the :currencyCode currency
256
     */
257
    public function theCustomerChoseTheCurrency($currencyCode)
258
    {
259
        $this->currencyStorage->set($this->sharedStorage->get('channel'), $currencyCode);
260
261
        /** @var OrderInterface $order */
262
        $order = $this->sharedStorage->get('order');
263
        $order->setCurrencyCode($currencyCode);
264
265
        $this->objectManager->flush();
266
    }
267
268
    /**
269
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
270
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
271
     */
272
    public function theCustomerChoseShippingWithPayment(
273
        ShippingMethodInterface $shippingMethod,
274
        PaymentMethodInterface $paymentMethod
275
    ) {
276
        /** @var OrderInterface $order */
277
        $order = $this->sharedStorage->get('order');
278
279
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
280
281
        $this->objectManager->flush();
282
    }
283
284
    /**
285
     * @Given the customer bought a single :product
286
     * @Given I bought a single :product
287
     */
288
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
289
    {
290
        $this->addProductVariantToOrder($this->variantResolver->getVariant($product), 1);
0 ignored issues
show
Compatibility introduced by
$this->variantResolver->getVariant($product) of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
291
292
        $this->objectManager->flush();
293
    }
294
295
    /**
296
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
297
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
298
     */
299
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
300
    {
301
        $this->theCustomerBoughtSingleProduct($product);
302
        $this->theCustomerBoughtSingleProduct($secondProduct);
303
    }
304
305
    /**
306
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
307
     */
308
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
309
    {
310
        $variant = $this->variantResolver->getVariant($product);
311
        $this->addProductVariantToOrder($variant, $quantity);
0 ignored issues
show
Compatibility introduced by
$variant of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
312
313
        $this->objectManager->flush();
314
    }
315
316
    /**
317
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
318
     */
319
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
320
    {
321
        $this->addProductVariantToOrder($variant, $quantity);
322
323
        $this->objectManager->flush();
324
    }
325
326
    /**
327
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
328
     */
329
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
330
    {
331
        $this->addProductVariantToOrder($productVariant);
332
333
        $this->objectManager->flush();
334
    }
335
336
    /**
337
     * @Given the customer bought a single :product using :coupon coupon
338
     * @Given I bought a single :product using :coupon coupon
339
     */
340
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
341
    {
342
        $order = $this->addProductVariantToOrder($this->variantResolver->getVariant($product));
0 ignored issues
show
Compatibility introduced by
$this->variantResolver->getVariant($product) of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
343
        $order->setPromotionCoupon($coupon);
344
345
        $this->objectManager->flush();
346
    }
347
348
    /**
349
     * @Given I used :coupon coupon
350
     */
351
    public function iUsedCoupon(PromotionCouponInterface $coupon)
352
    {
353
        $order = $this->sharedStorage->get('order');
354
        $order->setPromotionCoupon($coupon);
355
356
        $this->objectManager->flush();
357
    }
358
359
    /**
360
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
361
     */
362
    public function iHaveAlreadyPlacedOrderNthTimes(
363
        UserInterface $user,
364
        $numberOfOrders,
365
        ShippingMethodInterface $shippingMethod,
366
        AddressInterface $address,
367
        PaymentMethodInterface $paymentMethod
368
    ) {
369
        $customer = $user->getCustomer();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\User\Model\UserInterface as the method getCustomer() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ShopUser.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
370
        for ($i = 0; $i < $numberOfOrders; $i++) {
371
            $order = $this->createOrder($customer, '#00000'.$i);
372
            $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
373
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
374
375
            $this->orderRepository->add($order);
376
        }
377
    }
378
379
    /**
380
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
381
     */
382
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
383
    {
384
        $customers = $this->generateCustomers($numberOfCustomers);
385
386
        $sampleProductVariant = $this->sharedStorage->get('variant');
387
        $total = $this->getPriceFromString($total);
388
389
        for ($i = 0; $i < $numberOfCustomers; $i++) {
390
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
391
392
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
393
            $total -= $price;
394
395
            $item = $this->orderItemFactory->createNew();
396
            $item->setVariant($sampleProductVariant);
397
            $item->setUnitPrice($price);
398
399
            $this->itemQuantityModifier->modify($item, 1);
400
401
            $order->addItem($item);
402
403
            $this->orderRepository->add($order);
404
        }
405
    }
406
407
    /**
408
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
409
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
410
     */
411
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
412
    {
413
        $customers = $this->generateCustomers($numberOfCustomers);
414
        $sampleProductVariant = $this->sharedStorage->get('variant');
415
        $total = $this->getPriceFromString($total);
416
417
        for ($i = 0; $i < $numberOfOrders; $i++) {
418
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
419
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
420
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
421
422
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
423
            $total -= $price;
424
425
            $item = $this->orderItemFactory->createNew();
426
            $item->setVariant($sampleProductVariant);
427
            $item->setUnitPrice($price);
428
429
            $this->itemQuantityModifier->modify($item, 1);
430
431
            $order->addItem($item);
432
433
            $this->orderRepository->add($order);
434
        }
435
    }
436
437
    /**
438
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
439
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
440
     */
441
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
442
    {
443
        $customers = $this->generateCustomers($numberOfCustomers);
444
        $sampleProductVariant = $product->getVariants()->first();
445
        $total = $this->getPriceFromString($total);
446
447
        for ($i = 0; $i < $numberOfOrders; $i++) {
448
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
449
            $order->setState(OrderInterface::STATE_NEW);
450
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
451
452
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
453
            $total -= $price;
454
455
            $item = $this->orderItemFactory->createNew();
456
            $item->setVariant($sampleProductVariant);
457
            $item->setUnitPrice($price);
458
459
            $this->itemQuantityModifier->modify($item, 1);
460
461
            $order->addItem($item);
462
463
            $this->orderRepository->add($order);
464
        }
465
    }
466
467
    /**
468
     * @Given /^(this order) is already paid$/
469
     * @Given the order :order is already paid
470
     */
471
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
472
    {
473
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
474
475
        $this->objectManager->flush();
476
    }
477
478
    /**
479
     * @Given /^the customer cancelled (this order)$/
480
     * @Given /^(this order) was cancelled$/
481
     * @Given the order :order was cancelled
482
     * @Given /^I cancelled (this order)$/
483
     */
484
    public function theCustomerCancelledThisOrder(OrderInterface $order)
485
    {
486
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
487
488
        $this->objectManager->flush();
489
    }
490
491
    /**
492
     * @Given /^(this order) has already been shipped$/
493
     */
494
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
495
    {
496
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
497
498
        $this->objectManager->flush();
499
    }
500
501
    /**
502
     * @param OrderInterface $order
503
     * @param string $transition
504
     */
505
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
506
    {
507
        foreach ($order->getShipments() as $shipment) {
508
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
509
        }
510
    }
511
512
    /**
513
     * @param OrderInterface $order
514
     * @param string $transition
515
     */
516
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
517
    {
518
        foreach ($order->getPayments() as $payment) {
519
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
520
        }
521
    }
522
523
    /**
524
     * @param OrderInterface $order
525
     * @param string $transition
526
     */
527
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
528
    {
529
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
530
    }
531
532
    /**
533
     * @param ProductVariantInterface $productVariant
534
     * @param int $quantity
535
     *
536
     * @return OrderInterface
537
     */
538
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
539
    {
540
        $order = $this->sharedStorage->get('order');
541
542
        /** @var OrderItemInterface $item */
543
        $item = $this->orderItemFactory->createNew();
544
        $item->setVariant($productVariant);
545
        $item->setUnitPrice($productVariant->getPrice());
546
547
        $this->itemQuantityModifier->modify($item, $quantity);
548
549
        $order->addItem($item);
550
551
        return $order;
552
    }
553
554
    /**
555
     * @param CustomerInterface $customer
556
     * @param string $number
0 ignored issues
show
Documentation introduced by
Should the type for parameter $number not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
557
     * @param ChannelInterface|null $channel
558
     * @param string|null $currencyCode
559
     * @param string|null $localeCode
560
     *
561
     * @return OrderInterface
562
     */
563
    private function createOrder(
564
        CustomerInterface $customer,
565
        $number = null,
566
        ChannelInterface $channel = null,
567
        $currencyCode = null,
568
        $localeCode = null
569
    ) {
570
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
571
572
        if (null !== $number) {
573
            $order->setNumber($number);
574
        }
575
576
        $order->completeCheckout();
577
578
        return $order;
579
    }
580
581
    /**
582
     * @param CustomerInterface $customer
583
     * @param ChannelInterface|null $channel
584
     * @param string|null $currencyCode
585
     * @param string|null $localeCode
586
     *
587
     * @return OrderInterface
588
     */
589
    private function createCart(
590
        CustomerInterface $customer,
591
        ChannelInterface $channel = null,
592
        $currencyCode = null,
593
        $localeCode = null
594
    ) {
595
        /** @var OrderInterface $order */
596
        $order = $this->orderFactory->createNew();
597
598
        $order->setCustomer($customer);
599
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
600
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
601
602
        $currencyCode = $currencyCode ? $currencyCode : $order->getChannel()->getBaseCurrency()->getCode();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Channel\Model\ChannelInterface as the method getBaseCurrency() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\Channel.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
603
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
604
605
        $order->setCurrencyCode($currency->getCode());
606
        $order->setExchangeRate($currency->getExchangeRate());
607
608
        return $order;
609
    }
610
611
    /**
612
     * @param int $count
613
     *
614
     * @return CustomerInterface[]
615
     */
616
    private function generateCustomers($count)
617
    {
618
        $customers = [];
619
620
        for ($i = 0; $i < $count; $i++) {
621
            $customer = $this->customerFactory->createNew();
622
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
623
            $customer->setFirstname('John');
624
            $customer->setLastname('Doe'.$i);
625
626
            $customers[] = $customer;
627
628
            $this->customerRepository->add($customer);
629
        }
630
631
        return $customers;
632
    }
633
634
    /**
635
     * @param string $price
636
     *
637
     * @return int
638
     */
639
    private function getPriceFromString($price)
640
    {
641
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
642
    }
643
644
    /**
645
     * @param OrderInterface $order
646
     * @param ShippingMethodInterface $shippingMethod
647
     * @param AddressInterface $address
648
     * @param PaymentMethodInterface $paymentMethod
649
     */
650
    private function checkoutUsing(
651
        OrderInterface $order,
652
        ShippingMethodInterface $shippingMethod,
653
        AddressInterface $address,
654
        PaymentMethodInterface $paymentMethod
655
    ) {
656
        $order->setShippingAddress($address);
657
        $order->setBillingAddress(clone $address);
658
659
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
660
661
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
662
    }
663
664
    /**
665
     * @param OrderInterface $order
666
     * @param ShippingMethodInterface $shippingMethod
667
     * @param PaymentMethodInterface $paymentMethod
668
     */
669
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
670
    {
671
        foreach ($order->getShipments() as $shipment) {
672
            $shipment->setMethod($shippingMethod);
673
        }
674
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
675
676
        $payment = $order->getLastNewPayment();
677
        $payment->setMethod($paymentMethod);
678
679
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
680
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
681
    }
682
}
683