Completed
Push — wtf-removal ( 4585f3 )
by Kamil
18:08
created

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