Completed
Push — master ( 62f72e...c36855 )
by Kamil
83:57 queued 64:20
created

OrderContext::theCustomerBoughtProductAndProduct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
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 /^(this order) is already paid$/
439
     * @Given the order :order is already paid
440
     */
441
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
442
    {
443
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
444
445
        $this->objectManager->flush();
446
    }
447
448
    /**
449
     * @Given /^the customer cancelled (this order)$/
450
     * @Given /^(this order) was cancelled$/
451
     * @Given the order :order was cancelled
452
     * @Given /^I cancelled (this order)$/
453
     */
454
    public function theCustomerCancelledThisOrder(OrderInterface $order)
455
    {
456
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
457
458
        $this->objectManager->flush();
459
    }
460
461
    /**
462
     * @Given /^(this order) has already been shipped$/
463
     */
464
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
465
    {
466
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
467
468
        $this->objectManager->flush();
469
    }
470
471
    /**
472
     * @param OrderInterface $order
473
     * @param string $transition
474
     */
475
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
476
    {
477
        foreach ($order->getShipments() as $shipment) {
478
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
479
        }
480
    }
481
482
    /**
483
     * @param OrderInterface $order
484
     * @param string $transition
485
     */
486
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
487
    {
488
        foreach ($order->getPayments() as $payment) {
489
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
490
        }
491
    }
492
493
    /**
494
     * @param OrderInterface $order
495
     * @param string $transition
496
     */
497
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
498
    {
499
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
500
    }
501
502
    /**
503
     * @param ProductVariantInterface $productVariant
504
     * @param int $quantity
505
     *
506
     * @return OrderInterface
507
     */
508
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
509
    {
510
        $order = $this->sharedStorage->get('order');
511
512
        /** @var OrderItemInterface $item */
513
        $item = $this->orderItemFactory->createNew();
514
        $item->setVariant($productVariant);
515
        $item->setUnitPrice($productVariant->getPrice());
516
517
        $this->itemQuantityModifier->modify($item, $quantity);
518
519
        $order->addItem($item);
520
521
        return $order;
522
    }
523
524
    /**
525
     * @param CustomerInterface $customer
526
     * @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...
527
     * @param ChannelInterface|null $channel
528
     * @param string|null $currencyCode
529
     * @param string|null $localeCode
530
     *
531
     * @return OrderInterface
532
     */
533
    private function createOrder(
534
        CustomerInterface $customer,
535
        $number = null,
536
        ChannelInterface $channel = null,
537
        $currencyCode = null,
538
        $localeCode = null
539
    ) {
540
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
541
542
        if (null !== $number) {
543
            $order->setNumber($number);
544
        }
545
546
        $order->completeCheckout();
547
548
        return $order;
549
    }
550
551
    /**
552
     * @param CustomerInterface $customer
553
     * @param ChannelInterface|null $channel
554
     * @param string|null $currencyCode
555
     * @param string|null $localeCode
556
     *
557
     * @return OrderInterface
558
     */
559
    private function createCart(
560
        CustomerInterface $customer,
561
        ChannelInterface $channel = null,
562
        $currencyCode = null,
563
        $localeCode = null
564
    ) {
565
        /** @var OrderInterface $order */
566
        $order = $this->orderFactory->createNew();
567
568
        $order->setCustomer($customer);
569
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
570
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
571
572
        $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...
573
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
574
575
        $order->setCurrencyCode($currency->getCode());
576
        $order->setExchangeRate($currency->getExchangeRate());
577
578
        return $order;
579
    }
580
581
    /**
582
     * @param int $count
583
     *
584
     * @return CustomerInterface[]
585
     */
586
    private function generateCustomers($count)
587
    {
588
        $customers = [];
589
590
        for ($i = 0; $i < $count; $i++) {
591
            $customer = $this->customerFactory->createNew();
592
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
593
            $customer->setFirstname('John');
594
            $customer->setLastname('Doe'.$i);
595
596
            $customers[] = $customer;
597
598
            $this->customerRepository->add($customer);
599
        }
600
601
        return $customers;
602
    }
603
604
    /**
605
     * @param string $price
606
     *
607
     * @return int
608
     */
609
    private function getPriceFromString($price)
610
    {
611
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
612
    }
613
614
    /**
615
     * @param OrderInterface $order
616
     * @param ShippingMethodInterface $shippingMethod
617
     * @param AddressInterface $address
618
     * @param PaymentMethodInterface $paymentMethod
619
     */
620
    private function checkoutUsing(
621
        OrderInterface $order,
622
        ShippingMethodInterface $shippingMethod,
623
        AddressInterface $address,
624
        PaymentMethodInterface $paymentMethod
625
    ) {
626
        $order->setShippingAddress($address);
627
        $order->setBillingAddress(clone $address);
628
629
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
630
631
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
632
    }
633
634
    /**
635
     * @param OrderInterface $order
636
     * @param ShippingMethodInterface $shippingMethod
637
     * @param PaymentMethodInterface $paymentMethod
638
     */
639
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
640
    {
641
        foreach ($order->getShipments() as $shipment) {
642
            $shipment->setMethod($shippingMethod);
643
        }
644
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
645
646
        $payment = $order->getLastNewPayment();
647
        $payment->setMethod($paymentMethod);
648
649
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
650
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
651
    }
652
}
653