Completed
Push — master ( 3f88f4...8432dc )
by Paweł
59:05 queued 46:47
created

theGuestCustomerPlacedOrderWithForAndBasedShippingAddress()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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