Completed
Push — symfony3-wololo ( 5670c9...50a832 )
by Kamil
20:37
created

OrderContext::thisOrderIsAlreadyPaid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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
            $this->sharedStorage->set('order', $order);
377
        }
378
    }
379
380
    /**
381
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
382
     */
383
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
384
    {
385
        $customers = $this->generateCustomers($numberOfCustomers);
386
387
        $sampleProductVariant = $this->sharedStorage->get('variant');
388
        $total = $this->getPriceFromString($total);
389
390
        for ($i = 0; $i < $numberOfCustomers; $i++) {
391
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
392
393
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
394
            $total -= $price;
395
396
            $item = $this->orderItemFactory->createNew();
397
            $item->setVariant($sampleProductVariant);
398
            $item->setUnitPrice($price);
399
400
            $this->itemQuantityModifier->modify($item, 1);
401
402
            $order->addItem($item);
403
404
            $this->orderRepository->add($order);
405
        }
406
    }
407
408
    /**
409
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
410
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
411
     */
412
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
413
    {
414
        $customers = $this->generateCustomers($numberOfCustomers);
415
        $sampleProductVariant = $this->sharedStorage->get('variant');
416
        $total = $this->getPriceFromString($total);
417
418
        for ($i = 0; $i < $numberOfOrders; $i++) {
419
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
420
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
421
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
422
423
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
424
            $total -= $price;
425
426
            $item = $this->orderItemFactory->createNew();
427
            $item->setVariant($sampleProductVariant);
428
            $item->setUnitPrice($price);
429
430
            $this->itemQuantityModifier->modify($item, 1);
431
432
            $order->addItem($item);
433
434
            $this->orderRepository->add($order);
435
            $this->sharedStorage->set('order', $order);
436
        }
437
    }
438
439
    /**
440
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
441
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
442
     */
443
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
444
    {
445
        $customers = $this->generateCustomers($numberOfCustomers);
446
        $sampleProductVariant = $product->getVariants()->first();
447
        $total = $this->getPriceFromString($total);
448
449
        for ($i = 0; $i < $numberOfOrders; $i++) {
450
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
451
            $order->setState(OrderInterface::STATE_NEW);
452
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
453
454
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
455
            $total -= $price;
456
457
            $item = $this->orderItemFactory->createNew();
458
            $item->setVariant($sampleProductVariant);
459
            $item->setUnitPrice($price);
460
461
            $this->itemQuantityModifier->modify($item, 1);
462
463
            $order->addItem($item);
464
465
            $this->orderRepository->add($order);
466
        }
467
    }
468
469
    /**
470
     * @Given /^(this order) is already paid$/
471
     * @Given the order :order is already paid
472
     */
473
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
474
    {
475
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
476
477
        $this->objectManager->flush();
478
    }
479
480
    /**
481
     * @Given /^the customer cancelled (this order)$/
482
     * @Given /^(this order) was cancelled$/
483
     * @Given the order :order was cancelled
484
     * @Given /^I cancelled (this order)$/
485
     */
486
    public function theCustomerCancelledThisOrder(OrderInterface $order)
487
    {
488
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
489
490
        $this->objectManager->flush();
491
    }
492
493
    /**
494
     * @Given /^I cancelled my last order$/
495
     */
496
    public function theCustomerCancelledMyLastOrder()
497
    {
498
        $order = $this->sharedStorage->get('order');
499
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
500
501
        $this->objectManager->flush();
502
    }
503
504
    /**
505
     * @Given /^(this order) has already been shipped$/
506
     */
507
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
508
    {
509
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
510
511
        $this->objectManager->flush();
512
    }
513
514
    /**
515
     * @param OrderInterface $order
516
     * @param string $transition
517
     */
518
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
519
    {
520
        foreach ($order->getShipments() as $shipment) {
521
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
522
        }
523
    }
524
525
    /**
526
     * @param OrderInterface $order
527
     * @param string $transition
528
     */
529
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
530
    {
531
        foreach ($order->getPayments() as $payment) {
532
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
533
        }
534
    }
535
536
    /**
537
     * @param OrderInterface $order
538
     * @param string $transition
539
     */
540
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
541
    {
542
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
543
    }
544
545
    /**
546
     * @param ProductVariantInterface $productVariant
547
     * @param int $quantity
548
     *
549
     * @return OrderInterface
550
     */
551
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
552
    {
553
        $order = $this->sharedStorage->get('order');
554
555
        /** @var OrderItemInterface $item */
556
        $item = $this->orderItemFactory->createNew();
557
        $item->setVariant($productVariant);
558
        $item->setUnitPrice($productVariant->getPrice());
559
560
        $this->itemQuantityModifier->modify($item, $quantity);
561
562
        $order->addItem($item);
563
564
        return $order;
565
    }
566
567
    /**
568
     * @param CustomerInterface $customer
569
     * @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...
570
     * @param ChannelInterface|null $channel
571
     * @param string|null $currencyCode
572
     * @param string|null $localeCode
573
     *
574
     * @return OrderInterface
575
     */
576
    private function createOrder(
577
        CustomerInterface $customer,
578
        $number = null,
579
        ChannelInterface $channel = null,
580
        $currencyCode = null,
581
        $localeCode = null
582
    ) {
583
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
584
585
        if (null !== $number) {
586
            $order->setNumber($number);
587
        }
588
589
        $order->completeCheckout();
590
591
        return $order;
592
    }
593
594
    /**
595
     * @param CustomerInterface $customer
596
     * @param ChannelInterface|null $channel
597
     * @param string|null $currencyCode
598
     * @param string|null $localeCode
599
     *
600
     * @return OrderInterface
601
     */
602
    private function createCart(
603
        CustomerInterface $customer,
604
        ChannelInterface $channel = null,
605
        $currencyCode = null,
606
        $localeCode = null
607
    ) {
608
        /** @var OrderInterface $order */
609
        $order = $this->orderFactory->createNew();
610
611
        $order->setCustomer($customer);
612
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
613
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
614
615
        $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...
616
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
617
618
        $order->setCurrencyCode($currency->getCode());
619
        $order->setExchangeRate($currency->getExchangeRate());
620
621
        return $order;
622
    }
623
624
    /**
625
     * @param int $count
626
     *
627
     * @return CustomerInterface[]
628
     */
629
    private function generateCustomers($count)
630
    {
631
        $customers = [];
632
633
        for ($i = 0; $i < $count; $i++) {
634
            $customer = $this->customerFactory->createNew();
635
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
636
            $customer->setFirstname('John');
637
            $customer->setLastname('Doe'.$i);
638
639
            $customers[] = $customer;
640
641
            $this->customerRepository->add($customer);
642
        }
643
644
        return $customers;
645
    }
646
647
    /**
648
     * @param string $price
649
     *
650
     * @return int
651
     */
652
    private function getPriceFromString($price)
653
    {
654
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
655
    }
656
657
    /**
658
     * @param OrderInterface $order
659
     * @param ShippingMethodInterface $shippingMethod
660
     * @param AddressInterface $address
661
     * @param PaymentMethodInterface $paymentMethod
662
     */
663
    private function checkoutUsing(
664
        OrderInterface $order,
665
        ShippingMethodInterface $shippingMethod,
666
        AddressInterface $address,
667
        PaymentMethodInterface $paymentMethod
668
    ) {
669
        $order->setShippingAddress($address);
670
        $order->setBillingAddress(clone $address);
671
672
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
673
674
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
675
    }
676
677
    /**
678
     * @param OrderInterface $order
679
     * @param ShippingMethodInterface $shippingMethod
680
     * @param PaymentMethodInterface $paymentMethod
681
     */
682
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
683
    {
684
        foreach ($order->getShipments() as $shipment) {
685
            $shipment->setMethod($shippingMethod);
686
        }
687
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
688
689
        $payment = $order->getLastNewPayment();
690
        $payment->setMethod($paymentMethod);
691
692
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
693
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
694
    }
695
}
696