Completed
Push — upgrade-boilerplate ( 903b21 )
by Kamil
17:46
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\ChannelPricingInterface;
20
use Sylius\Component\Core\Model\PromotionCouponInterface;
21
use Sylius\Component\Core\OrderCheckoutTransitions;
22
use Sylius\Component\Order\Processor\OrderProcessorInterface;
23
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
24
use Sylius\Component\Core\Model\AddressInterface;
25
use Sylius\Component\Core\Model\OrderInterface;
26
use Sylius\Component\Core\Model\OrderItemInterface;
27
use Sylius\Component\Core\Model\ProductInterface;
28
use Sylius\Component\Core\Model\ProductVariantInterface;
29
use Sylius\Component\Core\Model\ShippingMethodInterface;
30
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
31
use Sylius\Behat\Service\SharedStorageInterface;
32
use Sylius\Component\Order\OrderTransitions;
33
use Sylius\Component\Payment\Model\PaymentInterface;
34
use Sylius\Component\Payment\Model\PaymentMethodInterface;
35
use Sylius\Component\Payment\PaymentTransitions;
36
use Sylius\Component\Resource\Factory\FactoryInterface;
37
use Sylius\Component\Resource\Repository\RepositoryInterface;
38
use Sylius\Component\Shipping\ShipmentTransitions;
39
use Sylius\Component\Customer\Model\CustomerInterface;
40
use Sylius\Component\User\Model\UserInterface;
41
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
42
43
/**
44
 * @author Łukasz Chruściel <[email protected]>
45
 */
46
final class OrderContext implements Context
47
{
48
    /**
49
     * @var SharedStorageInterface
50
     */
51
    private $sharedStorage;
52
53
    /**
54
     * @var OrderRepositoryInterface
55
     */
56
    private $orderRepository;
57
58
    /**
59
     * @var FactoryInterface
60
     */
61
    private $orderFactory;
62
63
    /**
64
     * @var OrderProcessorInterface
65
     */
66
    private $orderProcessor;
67
68
    /**
69
     * @var FactoryInterface
70
     */
71
    private $orderItemFactory;
72
73
    /**
74
     * @var OrderItemQuantityModifierInterface
75
     */
76
    private $itemQuantityModifier;
77
78
    /**
79
     * @var RepositoryInterface
80
     */
81
    private $currencyRepository;
82
83
    /**
84
     * @var CurrencyStorageInterface
85
     */
86
    private $currencyStorage;
87
88
    /**
89
     * @var FactoryInterface
90
     */
91
    private $customerFactory;
92
93
    /**
94
     * @var RepositoryInterface
95
     */
96
    private $customerRepository;
97
98
    /**
99
     * @var ObjectManager
100
     */
101
    private $objectManager;
102
103
    /**
104
     * @var StateMachineFactoryInterface
105
     */
106
    private $stateMachineFactory;
107
108
    /**
109
     * @var ProductVariantResolverInterface
110
     */
111
    private $variantResolver;
112
113
    /**
114
     * @param SharedStorageInterface $sharedStorage
115
     * @param OrderRepositoryInterface $orderRepository
116
     * @param FactoryInterface $orderFactory
117
     * @param OrderProcessorInterface $orderProcessor
118
     * @param FactoryInterface $orderItemFactory
119
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
120
     * @param RepositoryInterface $currencyRepository
121
     * @param CurrencyStorageInterface $currencyStorage
122
     * @param FactoryInterface $customerFactory
123
     * @param RepositoryInterface $customerRepository
124
     * @param ObjectManager $objectManager
125
     * @param StateMachineFactoryInterface $stateMachineFactory
126
     * @param ProductVariantResolverInterface $variantResolver
127
     */
128
    public function __construct(
129
        SharedStorageInterface $sharedStorage,
130
        OrderRepositoryInterface $orderRepository,
131
        FactoryInterface $orderFactory,
132
        OrderProcessorInterface $orderProcessor,
133
        FactoryInterface $orderItemFactory,
134
        OrderItemQuantityModifierInterface $itemQuantityModifier,
135
        RepositoryInterface $currencyRepository,
136
        CurrencyStorageInterface $currencyStorage,
137
        FactoryInterface $customerFactory,
138
        RepositoryInterface $customerRepository,
139
        ObjectManager $objectManager,
140
        StateMachineFactoryInterface $stateMachineFactory,
141
        ProductVariantResolverInterface $variantResolver
142
    ) {
143
        $this->sharedStorage = $sharedStorage;
144
        $this->orderRepository = $orderRepository;
145
        $this->orderFactory = $orderFactory;
146
        $this->orderProcessor = $orderProcessor;
147
        $this->orderItemFactory = $orderItemFactory;
148
        $this->itemQuantityModifier = $itemQuantityModifier;
149
        $this->currencyRepository = $currencyRepository;
150
        $this->currencyStorage = $currencyStorage;
151
        $this->customerFactory = $customerFactory;
152
        $this->customerRepository = $customerRepository;
153
        $this->objectManager = $objectManager;
154
        $this->stateMachineFactory = $stateMachineFactory;
155
        $this->variantResolver = $variantResolver;
156
    }
157
158
    /**
159
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
160
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
161
     * @Given a customer :customer placed an order :orderNumber
162
     * @Given the customer :customer has already placed an order :orderNumber
163
     */
164
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
165
    {
166
        $order = $this->createOrder($customer, $orderNumber);
167
168
        $this->sharedStorage->set('order', $order);
169
170
        $this->orderRepository->add($order);
171
    }
172
173
    /**
174
     * @Given a customer :customer added something to cart
175
     */
176
    public function customerStartedCheckout(CustomerInterface $customer)
177
    {
178
        $cart = $this->createCart($customer);
179
180
        $this->sharedStorage->set('cart', $cart);
181
182
        $this->orderRepository->add($cart);
183
    }
184
185
    /**
186
     * @Given /^(I) placed (an order "[^"]+")$/
187
     */
188
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
189
    {
190
        $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...
191
        $order = $this->createOrder($customer, $orderNumber);
192
193
        $this->sharedStorage->set('order', $order);
194
195
        $this->orderRepository->add($order);
196
    }
197
198
    /**
199
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
200
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
201
     */
202
    public function theCustomerAddressedItTo(AddressInterface $address)
203
    {
204
        /** @var OrderInterface $order */
205
        $order = $this->sharedStorage->get('order');
206
        $order->setShippingAddress($address);
207
208
        $this->objectManager->flush();
209
    }
210
211
    /**
212
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
213
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
214
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
215
     */
216
    public function forTheBillingAddressOf(AddressInterface $address)
217
    {
218
        /** @var OrderInterface $order */
219
        $order = $this->sharedStorage->get('order');
220
221
        $order->setBillingAddress($address);
222
223
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
224
225
        $this->objectManager->flush();
226
    }
227
228
    /**
229
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
230
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
231
     */
232
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
233
    {
234
        $this->theCustomerAddressedItTo($address);
235
        $this->forTheBillingAddressOf($address);
236
    }
237
238
    /**
239
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
240
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
241
     */
242
    public function theCustomerChoseShippingToWithPayment(
243
        ShippingMethodInterface $shippingMethod,
244
        AddressInterface $address,
245
        PaymentMethodInterface $paymentMethod
246
    ) {
247
        /** @var OrderInterface $order */
248
        $order = $this->sharedStorage->get('order');
249
250
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
251
252
        $this->objectManager->flush();
253
    }
254
255
    /**
256
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
257
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
258
     */
259
    public function theCustomerChoseShippingWithPayment(
260
        ShippingMethodInterface $shippingMethod,
261
        PaymentMethodInterface $paymentMethod
262
    ) {
263
        /** @var OrderInterface $order */
264
        $order = $this->sharedStorage->get('order');
265
266
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
267
268
        $this->objectManager->flush();
269
    }
270
271
    /**
272
     * @Given the customer bought a single :product
273
     * @Given I bought a single :product
274
     */
275
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
276
    {
277
        $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...
278
279
        $this->objectManager->flush();
280
    }
281
282
    /**
283
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
284
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
285
     */
286
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
287
    {
288
        $this->theCustomerBoughtSingleProduct($product);
289
        $this->theCustomerBoughtSingleProduct($secondProduct);
290
    }
291
292
    /**
293
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
294
     */
295
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
296
    {
297
        $variant = $this->variantResolver->getVariant($product);
298
        $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...
299
300
        $this->objectManager->flush();
301
    }
302
303
    /**
304
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
305
     */
306
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
307
    {
308
        $this->addProductVariantToOrder($variant, $quantity);
309
310
        $this->objectManager->flush();
311
    }
312
313
    /**
314
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
315
     */
316
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
317
    {
318
        $this->addProductVariantToOrder($productVariant);
319
320
        $this->objectManager->flush();
321
    }
322
323
    /**
324
     * @Given the customer bought a single :product using :coupon coupon
325
     * @Given I bought a single :product using :coupon coupon
326
     */
327
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
328
    {
329
        $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...
330
        $order->setPromotionCoupon($coupon);
331
332
        $this->objectManager->flush();
333
    }
334
335
    /**
336
     * @Given I used :coupon coupon
337
     */
338
    public function iUsedCoupon(PromotionCouponInterface $coupon)
339
    {
340
        $order = $this->sharedStorage->get('order');
341
        $order->setPromotionCoupon($coupon);
342
343
        $this->objectManager->flush();
344
    }
345
346
    /**
347
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
348
     */
349
    public function iHaveAlreadyPlacedOrderNthTimes(
350
        UserInterface $user,
351
        $numberOfOrders,
352
        ShippingMethodInterface $shippingMethod,
353
        AddressInterface $address,
354
        PaymentMethodInterface $paymentMethod
355
    ) {
356
        $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...
357
        for ($i = 0; $i < $numberOfOrders; $i++) {
358
            $order = $this->createOrder($customer, '#00000'.$i);
359
            $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
360
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
361
362
            $this->orderRepository->add($order);
363
            $this->sharedStorage->set('order', $order);
364
        }
365
    }
366
367
    /**
368
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
369
     */
370
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
371
    {
372
        $order = $this->createOrder($customer, $number);
373
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
374
        $order->setState(OrderInterface::STATE_NEW);
375
376
        $this->orderRepository->add($order);
377
    }
378
379
    /**
380
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") on a (channel "[^"]+")$/
381
     */
382
    public function thisCustomerHasPlacedAnOrderOnAChannel(CustomerInterface $customer, $number, $channel)
383
    {
384
        $order = $this->createOrder($customer, $number, $channel);
385
        $order->setState(OrderInterface::STATE_NEW);
386
387
        $this->orderRepository->add($order);
388
    }
389
390
    /**
391
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
392
     */
393
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
394
    {
395
        $customers = $this->generateCustomers($numberOfCustomers);
396
397
        $sampleProductVariant = $this->sharedStorage->get('variant');
398
        $total = $this->getPriceFromString($total);
399
400
        for ($i = 0; $i < $numberOfCustomers; $i++) {
401
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
402
403
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
404
            $total -= $price;
405
406
            $item = $this->orderItemFactory->createNew();
407
            $item->setVariant($sampleProductVariant);
408
            $item->setUnitPrice($price);
409
410
            $this->itemQuantityModifier->modify($item, 1);
411
412
            $order->addItem($item);
413
414
            $this->orderRepository->add($order);
415
        }
416
    }
417
418
    /**
419
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
420
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
421
     */
422
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
423
    {
424
        $customers = $this->generateCustomers($numberOfCustomers);
425
        $sampleProductVariant = $this->sharedStorage->get('variant');
426
        $total = $this->getPriceFromString($total);
427
428
        for ($i = 0; $i < $numberOfOrders; $i++) {
429
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
430
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
431
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
432
433
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
434
            $total -= $price;
435
436
            $item = $this->orderItemFactory->createNew();
437
            $item->setVariant($sampleProductVariant);
438
            $item->setUnitPrice($price);
439
440
            $this->itemQuantityModifier->modify($item, 1);
441
442
            $order->addItem($item);
443
444
            $this->orderRepository->add($order);
445
            $this->sharedStorage->set('order', $order);
446
        }
447
    }
448
449
    /**
450
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
451
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
452
     */
453
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
454
    {
455
        $customers = $this->generateCustomers($numberOfCustomers);
456
        $sampleProductVariant = $product->getVariants()->first();
457
        $total = $this->getPriceFromString($total);
458
459
        for ($i = 0; $i < $numberOfOrders; $i++) {
460
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
461
            $order->setState(OrderInterface::STATE_NEW);
462
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
463
464
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
465
            $total -= $price;
466
467
            $item = $this->orderItemFactory->createNew();
468
            $item->setVariant($sampleProductVariant);
469
            $item->setUnitPrice($price);
470
471
            $this->itemQuantityModifier->modify($item, 1);
472
473
            $order->addItem($item);
474
475
            $this->orderRepository->add($order);
476
        }
477
    }
478
479
    /**
480
     * @Given /^(this order) is already paid$/
481
     * @Given the order :order is already paid
482
     */
483
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
484
    {
485
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
486
487
        $this->objectManager->flush();
488
    }
489
490
    /**
491
     * @Given /^the customer cancelled (this order)$/
492
     * @Given /^(this order) was cancelled$/
493
     * @Given the order :order was cancelled
494
     * @Given /^I cancelled (this order)$/
495
     */
496
    public function theCustomerCancelledThisOrder(OrderInterface $order)
497
    {
498
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
499
500
        $this->objectManager->flush();
501
    }
502
503
    /**
504
     * @Given /^I cancelled my last order$/
505
     */
506
    public function theCustomerCancelledMyLastOrder()
507
    {
508
        $order = $this->sharedStorage->get('order');
509
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
510
511
        $this->objectManager->flush();
512
    }
513
514
    /**
515
     * @Given /^(this order) has already been shipped$/
516
     */
517
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
518
    {
519
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
520
521
        $this->objectManager->flush();
522
    }
523
524
    /**
525
     * @param OrderInterface $order
526
     * @param string $transition
527
     */
528
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
529
    {
530
        foreach ($order->getShipments() as $shipment) {
531
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
532
        }
533
    }
534
535
    /**
536
     * @param OrderInterface $order
537
     * @param string $transition
538
     */
539
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
540
    {
541
        foreach ($order->getPayments() as $payment) {
542
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
543
        }
544
    }
545
546
    /**
547
     * @param OrderInterface $order
548
     * @param string $transition
549
     */
550
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
551
    {
552
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
553
    }
554
555
    /**
556
     * @param ProductVariantInterface $productVariant
557
     * @param int $quantity
558
     *
559
     * @return OrderInterface
560
     */
561
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
562
    {
563
        $order = $this->sharedStorage->get('order');
564
565
        /** @var OrderItemInterface $item */
566
        $item = $this->orderItemFactory->createNew();
567
        $item->setVariant($productVariant);
568
569
        /** @var ChannelPricingInterface $channelPricing */
570
        $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
571
        $item->setUnitPrice($channelPricing->getPrice());
572
573
        $this->itemQuantityModifier->modify($item, $quantity);
574
575
        $order->addItem($item);
576
577
        return $order;
578
    }
579
580
    /**
581
     * @param CustomerInterface $customer
582
     * @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...
583
     * @param ChannelInterface|null $channel
584
     * @param string|null $currencyCode
585
     * @param string|null $localeCode
586
     *
587
     * @return OrderInterface
588
     */
589
    private function createOrder(
590
        CustomerInterface $customer,
591
        $number = null,
592
        ChannelInterface $channel = null,
593
        $currencyCode = null,
594
        $localeCode = null
595
    ) {
596
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
597
598
        if (null !== $number) {
599
            $order->setNumber($number);
600
        }
601
602
        $order->completeCheckout();
603
604
        return $order;
605
    }
606
607
    /**
608
     * @param CustomerInterface $customer
609
     * @param ChannelInterface|null $channel
610
     * @param string|null $currencyCode
611
     * @param string|null $localeCode
612
     *
613
     * @return OrderInterface
614
     */
615
    private function createCart(
616
        CustomerInterface $customer,
617
        ChannelInterface $channel = null,
618
        $currencyCode = null,
619
        $localeCode = null
620
    ) {
621
        /** @var OrderInterface $order */
622
        $order = $this->orderFactory->createNew();
623
624
        $order->setCustomer($customer);
625
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
626
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
627
628
        $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...
629
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
630
631
        $order->setCurrencyCode($currency->getCode());
632
633
        return $order;
634
    }
635
636
    /**
637
     * @param int $count
638
     *
639
     * @return CustomerInterface[]
640
     */
641
    private function generateCustomers($count)
642
    {
643
        $customers = [];
644
645
        for ($i = 0; $i < $count; $i++) {
646
            $customer = $this->customerFactory->createNew();
647
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
648
            $customer->setFirstname('John');
649
            $customer->setLastname('Doe'.$i);
650
651
            $customers[] = $customer;
652
653
            $this->customerRepository->add($customer);
654
        }
655
656
        return $customers;
657
    }
658
659
    /**
660
     * @param string $price
661
     *
662
     * @return int
663
     */
664
    private function getPriceFromString($price)
665
    {
666
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
667
    }
668
669
    /**
670
     * @param OrderInterface $order
671
     * @param ShippingMethodInterface $shippingMethod
672
     * @param AddressInterface $address
673
     * @param PaymentMethodInterface $paymentMethod
674
     */
675
    private function checkoutUsing(
676
        OrderInterface $order,
677
        ShippingMethodInterface $shippingMethod,
678
        AddressInterface $address,
679
        PaymentMethodInterface $paymentMethod
680
    ) {
681
        $order->setShippingAddress($address);
682
        $order->setBillingAddress(clone $address);
683
684
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
685
686
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
687
    }
688
689
    /**
690
     * @param OrderInterface $order
691
     * @param ShippingMethodInterface $shippingMethod
692
     * @param PaymentMethodInterface $paymentMethod
693
     */
694
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
695
    {
696
        foreach ($order->getShipments() as $shipment) {
697
            $shipment->setMethod($shippingMethod);
698
        }
699
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
700
701
        $payment = $order->getLastNewPayment();
702
        $payment->setMethod($paymentMethod);
703
704
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
705
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
706
    }
707
}
708