Completed
Push — pagerfanta-fix ( 187c46...923c07 )
by Kamil
25:06 queued 03:45
created

customersHavePlacedOrdersForTotalOfMostlyProduct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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