Completed
Push — master ( 521b85...5af0de )
by Paweł
46:25 queued 36:11
created

thisCustomerHasStartedCheckoutOnAChannel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Behat\Context\Setup;
15
16
use Behat\Behat\Context\Context;
17
use Doctrine\Common\Persistence\ObjectManager;
18
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
19
use Sylius\Behat\Service\SharedStorageInterface;
20
use Sylius\Component\Core\Model\AddressInterface;
21
use Sylius\Component\Core\Model\ChannelInterface;
22
use Sylius\Component\Core\Model\ChannelPricingInterface;
23
use Sylius\Component\Core\Model\OrderInterface;
24
use Sylius\Component\Core\Model\OrderItemInterface;
25
use Sylius\Component\Core\Model\ProductInterface;
26
use Sylius\Component\Core\Model\ProductVariantInterface;
27
use Sylius\Component\Core\Model\PromotionCouponInterface;
28
use Sylius\Component\Core\Model\ShippingMethodInterface;
29
use Sylius\Component\Core\OrderCheckoutTransitions;
30
use Sylius\Component\Core\OrderPaymentTransitions;
31
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
32
use Sylius\Component\Customer\Model\CustomerInterface;
33
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
34
use Sylius\Component\Order\OrderTransitions;
35
use Sylius\Component\Payment\Model\PaymentInterface;
36
use Sylius\Component\Payment\Model\PaymentMethodInterface;
37
use Sylius\Component\Payment\PaymentTransitions;
38
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
39
use Sylius\Component\Resource\Factory\FactoryInterface;
40
use Sylius\Component\Resource\Repository\RepositoryInterface;
41
use Sylius\Component\Shipping\ShipmentTransitions;
42
use Sylius\Component\User\Model\UserInterface;
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 FactoryInterface
66
     */
67
    private $orderItemFactory;
68
69
    /**
70
     * @var OrderItemQuantityModifierInterface
71
     */
72
    private $itemQuantityModifier;
73
74
    /**
75
     * @var FactoryInterface
76
     */
77
    private $customerFactory;
78
79
    /**
80
     * @var RepositoryInterface
81
     */
82
    private $customerRepository;
83
84
    /**
85
     * @var ObjectManager
86
     */
87
    private $objectManager;
88
89
    /**
90
     * @var StateMachineFactoryInterface
91
     */
92
    private $stateMachineFactory;
93
94
    /**
95
     * @var ProductVariantResolverInterface
96
     */
97
    private $variantResolver;
98
99
    /**
100
     * @param SharedStorageInterface $sharedStorage
101
     * @param OrderRepositoryInterface $orderRepository
102
     * @param FactoryInterface $orderFactory
103
     * @param FactoryInterface $orderItemFactory
104
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
105
     * @param FactoryInterface $customerFactory
106
     * @param RepositoryInterface $customerRepository
107
     * @param ObjectManager $objectManager
108
     * @param StateMachineFactoryInterface $stateMachineFactory
109
     * @param ProductVariantResolverInterface $variantResolver
110
     */
111
    public function __construct(
112
        SharedStorageInterface $sharedStorage,
113
        OrderRepositoryInterface $orderRepository,
114
        FactoryInterface $orderFactory,
115
        FactoryInterface $orderItemFactory,
116
        OrderItemQuantityModifierInterface $itemQuantityModifier,
117
        FactoryInterface $customerFactory,
118
        RepositoryInterface $customerRepository,
119
        ObjectManager $objectManager,
120
        StateMachineFactoryInterface $stateMachineFactory,
121
        ProductVariantResolverInterface $variantResolver
122
    ) {
123
        $this->sharedStorage = $sharedStorage;
124
        $this->orderRepository = $orderRepository;
125
        $this->orderFactory = $orderFactory;
126
        $this->orderItemFactory = $orderItemFactory;
127
        $this->itemQuantityModifier = $itemQuantityModifier;
128
        $this->customerFactory = $customerFactory;
129
        $this->customerRepository = $customerRepository;
130
        $this->objectManager = $objectManager;
131
        $this->stateMachineFactory = $stateMachineFactory;
132
        $this->variantResolver = $variantResolver;
133
    }
134
135
    /**
136
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
137
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
138
     * @Given a customer :customer placed an order :orderNumber
139
     * @Given the customer :customer has already placed an order :orderNumber
140
     */
141
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
142
    {
143
        $order = $this->createOrder($customer, $orderNumber);
144
145
        $this->sharedStorage->set('order', $order);
146
147
        $this->orderRepository->add($order);
148
    }
149
150
    /**
151
     * @Given /^the guest customer placed order with ("[^"]+" product) for "([^"]+)" and ("[^"]+" based shipping address) with ("[^"]+" shipping method) and ("[^"]+" payment)$/
152
     */
153
    public function theGuestCustomerPlacedOrderWithForAndBasedShippingAddress(
154
        ProductInterface $product,
155
        string $email,
156
        AddressInterface $address,
157
        ShippingMethodInterface $shippingMethod,
158
        PaymentMethodInterface $paymentMethod
159
    ) {
160
        /** @var CustomerInterface $customer */
161
        $customer = $this->customerFactory->createNew();
162
        $customer->setEmail($email);
163
        $customer->setFirstname('John');
164
        $customer->setLastname('Doe');
165
166
        $this->customerRepository->add($customer);
167
168
        $this->placeOrder($product, $shippingMethod, $address, $paymentMethod, $customer, 1);
169
        $this->objectManager->flush();
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 the customer changed shipping address' street to :street
212
     */
213
    public function theCustomerChangedShippingAddressStreetTo($street)
214
    {
215
        /** @var OrderInterface $order */
216
        $order = $this->sharedStorage->get('order');
217
218
        $shippingAddress = $order->getShippingAddress();
219
        $shippingAddress->setStreet($street);
220
221
        $this->objectManager->flush();
222
223
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
224
    }
225
226
    /**
227
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
228
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
229
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
230
     */
231
    public function forTheBillingAddressOf(AddressInterface $address)
232
    {
233
        /** @var OrderInterface $order */
234
        $order = $this->sharedStorage->get('order');
235
236
        $order->setBillingAddress($address);
237
238
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
239
240
        $this->objectManager->flush();
241
    }
242
243
    /**
244
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
245
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
246
     */
247
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
248
    {
249
        $this->theCustomerAddressedItTo($address);
250
        $this->forTheBillingAddressOf(clone $address);
251
    }
252
253
    /**
254
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
255
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
256
     */
257
    public function theCustomerChoseShippingToWithPayment(
258
        ShippingMethodInterface $shippingMethod,
259
        AddressInterface $address,
260
        PaymentMethodInterface $paymentMethod
261
    ) {
262
        /** @var OrderInterface $order */
263
        $order = $this->sharedStorage->get('order');
264
265
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
266
267
        $this->objectManager->flush();
268
    }
269
270
    /**
271
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
272
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
273
     */
274
    public function theCustomerChoseShippingWithPayment(
275
        ShippingMethodInterface $shippingMethod,
276
        PaymentMethodInterface $paymentMethod
277
    ) {
278
        /** @var OrderInterface $order */
279
        $order = $this->sharedStorage->get('order');
280
281
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
282
283
        $this->objectManager->flush();
284
    }
285
286
    /**
287
     * @Given /^the customer chose ("[^"]+" shipping method)$/
288
     */
289
    public function theCustomerChoseShippingMethod(ShippingMethodInterface $shippingMethod)
290
    {
291
        /** @var OrderInterface $order */
292
        $order = $this->sharedStorage->get('order');
293
294
        foreach ($order->getShipments() as $shipment) {
295
            $shipment->setMethod($shippingMethod);
296
        }
297
298
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
299
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
300
        if (!$order->getPayments()->isEmpty()) {
301
            $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH)->apply(OrderPaymentTransitions::TRANSITION_PAY);
302
        }
303
304
        $this->objectManager->flush();
305
    }
306
307
    /**
308
     * @Given /^the customer chose ("[^"]+" payment)$/
309
     */
310
    public function theCustomerChosePayment(PaymentMethodInterface $paymentMethod)
311
    {
312
        /** @var OrderInterface $order */
313
        $order = $this->sharedStorage->get('order');
314
315
        foreach ($order->getPayments() as $payment) {
316
            $payment->setMethod($paymentMethod);
317
        }
318
319
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
320
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
321
322
        $this->objectManager->flush();
323
    }
324
325
    /**
326
     * @Given the customer bought a single :product
327
     * @Given I bought a single :product
328
     */
329
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
330
    {
331
        $this->addProductVariantToOrder($this->variantResolver->getVariant($product), 1);
0 ignored issues
show
Documentation introduced by
$this->variantResolver->getVariant($product) is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
332
333
        $this->objectManager->flush();
334
    }
335
336
    /**
337
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
338
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
339
     */
340
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
341
    {
342
        $this->theCustomerBoughtSingleProduct($product);
343
        $this->theCustomerBoughtSingleProduct($secondProduct);
344
    }
345
346
    /**
347
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
348
     */
349
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
350
    {
351
        $variant = $this->variantResolver->getVariant($product);
352
        $this->addProductVariantToOrder($variant, $quantity);
0 ignored issues
show
Documentation introduced by
$variant is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
353
354
        $this->objectManager->flush();
355
    }
356
357
    /**
358
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
359
     */
360
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
361
    {
362
        $this->addProductVariantToOrder($variant, $quantity);
363
364
        $this->objectManager->flush();
365
    }
366
367
    /**
368
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
369
     */
370
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
371
    {
372
        $this->addProductVariantToOrder($productVariant);
373
374
        $this->objectManager->flush();
375
    }
376
377
    /**
378
     * @Given the customer bought a single :product using :coupon coupon
379
     * @Given I bought a single :product using :coupon coupon
380
     */
381
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
382
    {
383
        $order = $this->addProductVariantToOrder($this->variantResolver->getVariant($product));
0 ignored issues
show
Documentation introduced by
$this->variantResolver->getVariant($product) is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
384
        $order->setPromotionCoupon($coupon);
385
386
        $this->objectManager->flush();
387
    }
388
389
    /**
390
     * @Given I used :coupon coupon
391
     */
392
    public function iUsedCoupon(PromotionCouponInterface $coupon)
393
    {
394
        $order = $this->sharedStorage->get('order');
395
        $order->setPromotionCoupon($coupon);
396
397
        $this->objectManager->flush();
398
    }
399
400
    /**
401
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" product), ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
402
     */
403
    public function iHaveAlreadyPlacedOrderNthTimes(
404
        UserInterface $user,
405
        $numberOfOrders,
406
        ProductInterface $product,
407
        ShippingMethodInterface $shippingMethod,
408
        AddressInterface $address,
409
        PaymentMethodInterface $paymentMethod
410
    ) {
411
        $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...
412
        for ($i = 0; $i < $numberOfOrders; $i++) {
413
            $this->placeOrder($product, $shippingMethod, $address, $paymentMethod, $customer, $i);
414
        }
415
416
        $this->objectManager->flush();
417
    }
418
419
    /**
420
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
421
     */
422
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
423
    {
424
        $order = $this->createOrder($customer, $number);
425
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
426
        $order->setState(OrderInterface::STATE_NEW);
427
428
        $this->orderRepository->add($order);
429
    }
430
431
    /**
432
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") on a (channel "[^"]+")$/
433
     */
434
    public function thisCustomerHasPlacedAnOrderOnAChannel(CustomerInterface $customer, $number, $channel)
435
    {
436
        $order = $this->createOrder($customer, $number, $channel);
437
        $order->setState(OrderInterface::STATE_NEW);
438
439
        $this->orderRepository->add($order);
440
        $this->sharedStorage->set('order', $order);
441
    }
442
443
    /**
444
     * @Given /^(this customer) has(?:| also) started checkout on a (channel "[^"]+")$/
445
     */
446
    public function thisCustomerHasStartedCheckoutOnAChannel(CustomerInterface $customer, $channel)
447
    {
448
        $order = $this->createOrder($customer, null, $channel);
449
450
        $this->orderRepository->add($order);
451
        $this->sharedStorage->set('order', $order);
452
    }
453
454
    /**
455
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) placed (\d+) orders on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
456
     */
457
    public function thisCustomerPlacedOrdersOnChannelBuyingProducts(
458
        CustomerInterface $customer,
459
        int $orderCount,
460
        ChannelInterface $channel,
461
        int $productCount,
462
        ProductInterface $product
463
    ): void {
464
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product);
465
    }
466
467
    /**
468
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) fulfilled (\d+) orders placed on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
469
     */
470
    public function thisCustomerFulfilledOrdersPlacedOnChannelBuyingProducts(
471
        CustomerInterface $customer,
472
        int $orderCount,
473
        ChannelInterface $channel,
474
        int $productCount,
475
        ProductInterface $product
476
    ): void {
477
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product, true);
478
    }
479
480
    /**
481
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
482
     */
483
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
484
    {
485
        $customers = $this->generateCustomers($numberOfCustomers);
486
487
        $sampleProductVariant = $this->sharedStorage->get('variant');
488
        $total = $this->getPriceFromString($total);
489
490
        for ($i = 0; $i < $numberOfCustomers; $i++) {
491
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
492
493
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
494
            $total -= $price;
495
496
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
497
498
            $this->objectManager->persist($order);
499
        }
500
501
        $this->objectManager->flush();
502
    }
503
504
    /**
505
     * @Given a single customer has placed an order for total of :total
506
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
507
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
508
     */
509
    public function customersHavePlacedOrdersForTotalOf(
510
        int $numberOfCustomers = 1,
511
        int $numberOfOrders = 1,
512
        string $total
513
    ): void {
514
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total);
515
    }
516
517
    /**
518
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total
519
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total
520
     */
521
    public function customersHaveFulfilledOrdersPlacedForTotalOf(
522
        int $numberOfCustomers,
523
        int $numberOfOrders,
524
        string $total
525
    ): void{
526
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total, true);
527
    }
528
529
    /**
530
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
531
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
532
     */
533
    public function customersHavePlacedOrdersForTotalOfMostlyProduct(
534
        int $numberOfCustomers,
535
        int $numberOfOrders,
536
        string $total,
537
        ProductInterface $product
538
    ): void {
539
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product);
540
    }
541
542
    /**
543
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
544
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
545
     */
546
    public function customersHaveFulfilledOrdersPlacedForTotalOfMostlyProduct(
547
        int $numberOfCustomers,
548
        int $numberOfOrders,
549
        string $total,
550
        ProductInterface $product
551
    ): void {
552
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product, true);
553
    }
554
555
    /**
556
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") buying a single ("[^"]+" product) for ("[^"]+") on the ("[^"]+" channel)$/
557
     */
558
    public function customerHasPlacedAnOrderBuyingASingleProductForOnTheChannel(
559
        CustomerInterface $customer,
560
        $orderNumber,
561
        ProductInterface $product,
562
        $price,
563
        ChannelInterface $channel
564
    ) {
565
        $order = $this->createOrder($customer, $orderNumber, $channel);
566
        $order->setState(OrderInterface::STATE_NEW);
567
568
        $this->addVariantWithPriceToOrder($order, $product->getVariants()->first(), $price);
569
570
        $this->orderRepository->add($order);
571
        $this->sharedStorage->set('order',  $order);
572
    }
573
574
    /**
575
     * @Given /^(this order) is already paid$/
576
     * @Given the order :order is already paid
577
     */
578
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
579
    {
580
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
581
582
        $this->objectManager->flush();
583
    }
584
585
    /**
586
     * @Given /^(this order) has been refunded$/
587
     */
588
    public function thisOrderHasBeenRefunded(OrderInterface $order)
589
    {
590
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_REFUND);
591
592
        $this->objectManager->flush();
593
    }
594
595
    /**
596
     * @Given /^the customer cancelled (this order)$/
597
     * @Given /^(this order) was cancelled$/
598
     * @Given the order :order was cancelled
599
     * @Given /^I cancelled (this order)$/
600
     */
601
    public function theCustomerCancelledThisOrder(OrderInterface $order)
602
    {
603
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
604
605
        $this->objectManager->flush();
606
    }
607
608
    /**
609
     * @Given /^I cancelled my last order$/
610
     */
611
    public function theCustomerCancelledMyLastOrder()
612
    {
613
        $order = $this->sharedStorage->get('order');
614
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
615
616
        $this->objectManager->flush();
617
    }
618
619
    /**
620
     * @Given /^(this order) has already been shipped$/
621
     */
622
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
623
    {
624
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
625
626
        $this->objectManager->flush();
627
    }
628
629
    /**
630
     * @When the customer used coupon :coupon
631
     */
632
    public function theCustomerUsedCoupon(PromotionCouponInterface $coupon)
633
    {
634
        /** @var OrderInterface $order */
635
        $order = $this->sharedStorage->get('order');
636
        $order->setPromotionCoupon($coupon);
637
638
        $this->objectManager->flush();
639
    }
640
641
    /**
642
     * @param OrderInterface $order
643
     * @param string $transition
644
     */
645
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
646
    {
647
        foreach ($order->getShipments() as $shipment) {
648
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
649
        }
650
    }
651
652
    /**
653
     * @param OrderInterface $order
654
     * @param string $transition
655
     */
656
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
657
    {
658
        foreach ($order->getPayments() as $payment) {
659
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
660
        }
661
    }
662
663
    /**
664
     * @param OrderInterface $order
665
     * @param string $transition
666
     */
667
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
668
    {
669
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
670
    }
671
672
    /**
673
     * @param OrderInterface $order
674
     * @param string $transition
675
     */
676
    private function applyTransitionOnOrder(OrderInterface $order, string $transition): void
677
    {
678
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply($transition);
679
    }
680
681
    /**
682
     * @param ProductVariantInterface $productVariant
683
     * @param int $quantity
684
     *
685
     * @return OrderInterface
686
     */
687
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
688
    {
689
        $order = $this->sharedStorage->get('order');
690
691
        $this->addProductVariantsToOrderWithChannelPrice(
692
            $order,
693
            $this->sharedStorage->get('channel'),
694
            $productVariant,
695
            (int) $quantity
696
        );
697
698
        return $order;
699
    }
700
701
    /**
702
     * @param OrderInterface $order
703
     * @param ChannelInterface $channel
704
     * @param ProductVariantInterface $productVariant
705
     * @param int $quantity
706
     */
707
    private function addProductVariantsToOrderWithChannelPrice(
708
        OrderInterface $order,
709
        ChannelInterface $channel,
710
        ProductVariantInterface $productVariant,
711
        int $quantity = 1
712
    ) {
713
        /** @var OrderItemInterface $item */
714
        $item = $this->orderItemFactory->createNew();
715
        $item->setVariant($productVariant);
716
717
        /** @var ChannelPricingInterface $channelPricing */
718
        $channelPricing = $productVariant->getChannelPricingForChannel($channel);
719
        $item->setUnitPrice($channelPricing->getPrice());
720
721
        $this->itemQuantityModifier->modify($item, $quantity);
722
723
        $order->addItem($item);
724
    }
725
726
    /**
727
     * @param CustomerInterface $customer
728
     * @param string $number
729
     * @param ChannelInterface|null $channel
730
     * @param string|null $localeCode
731
     *
732
     * @return OrderInterface
733
     */
734
    private function createOrder(
735
        CustomerInterface $customer,
736
        $number = null,
737
        ChannelInterface $channel = null,
738
        $localeCode = null
739
    ) {
740
        $order = $this->createCart($customer, $channel, $localeCode);
741
742
        if (null !== $number) {
743
            $order->setNumber($number);
744
        }
745
746
        $order->completeCheckout();
747
748
        return $order;
749
    }
750
751
    /**
752
     * @param CustomerInterface $customer
753
     * @param ChannelInterface|null $channel
754
     * @param string|null $localeCode
755
     *
756
     * @return OrderInterface
757
     */
758
    private function createCart(
759
        CustomerInterface $customer,
760
        ChannelInterface $channel = null,
761
        $localeCode = null
762
    ) {
763
        /** @var OrderInterface $order */
764
        $order = $this->orderFactory->createNew();
765
766
        $order->setCustomer($customer);
767
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
768
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
769
        $order->setCurrencyCode($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...
770
771
        return $order;
772
    }
773
774
    /**
775
     * @param int $count
776
     *
777
     * @return CustomerInterface[]
778
     */
779
    private function generateCustomers($count)
780
    {
781
        $customers = [];
782
783
        for ($i = 0; $i < $count; $i++) {
784
            $customer = $this->customerFactory->createNew();
785
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
786
            $customer->setFirstname('John');
787
            $customer->setLastname('Doe'.$i);
788
789
            $customers[] = $customer;
790
791
            $this->customerRepository->add($customer);
792
        }
793
794
        return $customers;
795
    }
796
797
    /**
798
     * @param string $price
799
     *
800
     * @return int
801
     */
802
    private function getPriceFromString($price)
803
    {
804
        return (int) round(str_replace(['€', '£', '$'], '', $price) * 100, 2);
805
    }
806
807
    /**
808
     * @param OrderInterface $order
809
     * @param ShippingMethodInterface $shippingMethod
810
     * @param AddressInterface $address
811
     * @param PaymentMethodInterface $paymentMethod
812
     */
813
    private function checkoutUsing(
814
        OrderInterface $order,
815
        ShippingMethodInterface $shippingMethod,
816
        AddressInterface $address,
817
        PaymentMethodInterface $paymentMethod
818
    ) {
819
        $order->setShippingAddress($address);
820
        $order->setBillingAddress(clone $address);
821
822
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
823
824
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
825
    }
826
827
    /**
828
     * @param OrderInterface $order
829
     * @param ShippingMethodInterface $shippingMethod
830
     * @param PaymentMethodInterface $paymentMethod
831
     */
832
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
833
    {
834
        foreach ($order->getShipments() as $shipment) {
835
            $shipment->setMethod($shippingMethod);
836
        }
837
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
838
839
        $payment = $order->getLastPayment(PaymentInterface::STATE_CART);
840
        $payment->setMethod($paymentMethod);
841
842
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
843
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
844
    }
845
846
    /**
847
     * @param OrderInterface $order
848
     * @param ProductVariantInterface $variant
849
     * @param int $price
850
     */
851
    private function addVariantWithPriceToOrder(OrderInterface $order, ProductVariantInterface $variant, $price)
852
    {
853
        $item = $this->orderItemFactory->createNew();
854
        $item->setVariant($variant);
855
        $item->setUnitPrice($price);
856
857
        $this->itemQuantityModifier->modify($item, 1);
858
859
        $order->addItem($item);
860
    }
861
862
    /**
863
     * @param int $numberOfCustomers
864
     * @param int $numberOfOrders
865
     * @param string $total
866
     * @param bool $isFulfilled
867
     */
868
    private function createOrders(
869
        int $numberOfCustomers,
870
        int $numberOfOrders,
871
        string $total,
872
        bool $isFulfilled = false
873
    ): void {
874
        $customers = $this->generateCustomers($numberOfCustomers);
875
        $sampleProductVariant = $this->sharedStorage->get('variant');
876
        $total = $this->getPriceFromString($total);
877
878
        for ($i = 0; $i < $numberOfOrders; $i++) {
879
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
880
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
881
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
882
883
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
884
            $total -= $price;
885
886
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
887
888
            if ($isFulfilled) {
889
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
890
            }
891
892
            $this->objectManager->persist($order);
893
            $this->sharedStorage->set('order', $order);
894
        }
895
896
        $this->objectManager->flush();
897
    }
898
899
    /**
900
     * @param int $numberOfCustomers
901
     * @param int $numberOfOrders
902
     * @param string $total
903
     * @param ProductInterface $product
904
     * @param bool $isFulfilled
905
     */
906
    private function createOrdersWithProduct(
907
        int $numberOfCustomers,
908
        int $numberOfOrders,
909
        string $total,
910
        ProductInterface $product,
911
        bool $isFulfilled = false
912
    ): void {
913
        $customers = $this->generateCustomers($numberOfCustomers);
914
        $sampleProductVariant = $product->getVariants()->first();
915
        $total = $this->getPriceFromString($total);
916
917
        for ($i = 0; $i < $numberOfOrders; $i++) {
918
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
919
            $order->setState(OrderInterface::STATE_NEW);
920
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
921
922
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
923
            $total -= $price;
924
925
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
926
927
            if ($isFulfilled) {
928
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
929
            }
930
931
            $this->objectManager->persist($order);
932
        }
933
934
        $this->objectManager->flush();
935
    }
936
937
    /**
938
     * @param CustomerInterface $customer
939
     * @param int $orderCount
940
     * @param ChannelInterface $channel
941
     * @param int $productCount
942
     * @param ProductInterface $product
943
     * @param bool $isFulfilled
944
     */
945
    private function createOrdersForCustomer(
946
        CustomerInterface $customer,
947
        int $orderCount,
948
        ChannelInterface $channel,
949
        int $productCount,
950
        ProductInterface $product,
951
        bool $isFulfilled = false
952
    ): void {
953
        for ($i = 0; $i < $orderCount; $i++) {
954
            $order = $this->createOrder($customer, uniqid('#'), $channel);
955
956
            $this->addProductVariantsToOrderWithChannelPrice(
957
                $order,
958
                $channel,
959
                $this->variantResolver->getVariant($product),
0 ignored issues
show
Documentation introduced by
$this->variantResolver->getVariant($product) is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
960
                (int) $productCount
961
            );
962
963
            $order->setState($isFulfilled ? OrderInterface::STATE_FULFILLED : OrderInterface::STATE_NEW);
964
965
            $this->objectManager->persist($order);
966
        }
967
968
        $this->objectManager->flush();
969
    }
970
971
    /**
972
     * @param ProductInterface $product
973
     * @param ShippingMethodInterface $shippingMethod
974
     * @param AddressInterface $address
975
     * @param PaymentMethodInterface $paymentMethod
976
     * @param CustomerInterface $customer
977
     * @param int $number
978
     */
979
    private function placeOrder(
980
        ProductInterface $product,
981
        ShippingMethodInterface $shippingMethod,
982
        AddressInterface $address,
983
        PaymentMethodInterface $paymentMethod,
984
        CustomerInterface $customer,
985
        int $number
986
    ): void {
987
        /** @var ProductVariantInterface $variant */
988
        $variant = $this->variantResolver->getVariant($product);
989
990
        /** @var ChannelPricingInterface $channelPricing */
991
        $channelPricing = $variant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
992
993
        /** @var \Sylius\Component\Order\Model\OrderItemInterface $item */
994
        $item = $this->orderItemFactory->createNew();
995
        $item->setVariant($variant);
996
        $item->setUnitPrice($channelPricing->getPrice());
997
998
        $this->itemQuantityModifier->modify($item, 1);
999
1000
        $order = $this->createOrder($customer, '#00000' . $number);
1001
        $order->addItem($item);
1002
1003
        $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
1004
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
1005
1006
        $this->objectManager->persist($order);
1007
        $this->sharedStorage->set('order', $order);
1008
    }
1009
}
1010