Completed
Push — master ( 21b783...e431c6 )
by Paweł
46:07 queued 35:24
created

OrderContext::theCustomerUsedCoupon()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
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 a customer :customer added something to cart
152
     */
153
    public function customerStartedCheckout(CustomerInterface $customer)
154
    {
155
        $cart = $this->createCart($customer);
156
157
        $this->sharedStorage->set('cart', $cart);
158
159
        $this->orderRepository->add($cart);
160
    }
161
162
    /**
163
     * @Given /^(I) placed (an order "[^"]+")$/
164
     */
165
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
166
    {
167
        $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...
168
        $order = $this->createOrder($customer, $orderNumber);
169
170
        $this->sharedStorage->set('order', $order);
171
172
        $this->orderRepository->add($order);
173
    }
174
175
    /**
176
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
177
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
178
     */
179
    public function theCustomerAddressedItTo(AddressInterface $address)
180
    {
181
        /** @var OrderInterface $order */
182
        $order = $this->sharedStorage->get('order');
183
        $order->setShippingAddress($address);
184
185
        $this->objectManager->flush();
186
    }
187
188
    /**
189
     * @Given the customer changed shipping address' street to :street
190
     */
191
    public function theCustomerChangedShippingAddressStreetTo($street)
192
    {
193
        /** @var OrderInterface $order */
194
        $order = $this->sharedStorage->get('order');
195
196
        $shippingAddress = $order->getShippingAddress();
197
        $shippingAddress->setStreet($street);
198
199
        $this->objectManager->flush();
200
201
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
202
    }
203
204
    /**
205
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
206
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
207
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
208
     */
209
    public function forTheBillingAddressOf(AddressInterface $address)
210
    {
211
        /** @var OrderInterface $order */
212
        $order = $this->sharedStorage->get('order');
213
214
        $order->setBillingAddress($address);
215
216
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
217
218
        $this->objectManager->flush();
219
    }
220
221
    /**
222
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
223
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
224
     */
225
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
226
    {
227
        $this->theCustomerAddressedItTo($address);
228
        $this->forTheBillingAddressOf(clone $address);
229
    }
230
231
    /**
232
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
233
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
234
     */
235
    public function theCustomerChoseShippingToWithPayment(
236
        ShippingMethodInterface $shippingMethod,
237
        AddressInterface $address,
238
        PaymentMethodInterface $paymentMethod
239
    ) {
240
        /** @var OrderInterface $order */
241
        $order = $this->sharedStorage->get('order');
242
243
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
244
245
        $this->objectManager->flush();
246
    }
247
248
    /**
249
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
250
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
251
     */
252
    public function theCustomerChoseShippingWithPayment(
253
        ShippingMethodInterface $shippingMethod,
254
        PaymentMethodInterface $paymentMethod
255
    ) {
256
        /** @var OrderInterface $order */
257
        $order = $this->sharedStorage->get('order');
258
259
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
260
261
        $this->objectManager->flush();
262
    }
263
264
    /**
265
     * @Given /^the customer chose ("[^"]+" shipping method)$/
266
     */
267
    public function theCustomerChoseShippingMethod(ShippingMethodInterface $shippingMethod)
268
    {
269
        /** @var OrderInterface $order */
270
        $order = $this->sharedStorage->get('order');
271
272
        foreach ($order->getShipments() as $shipment) {
273
            $shipment->setMethod($shippingMethod);
274
        }
275
276
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
277
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
278
        if (!$order->getPayments()->isEmpty()) {
279
            $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH)->apply(OrderPaymentTransitions::TRANSITION_PAY);
280
        }
281
282
        $this->objectManager->flush();
283
    }
284
285
    /**
286
     * @Given /^the customer chose ("[^"]+" payment)$/
287
     */
288
    public function theCustomerChosePayment(PaymentMethodInterface $paymentMethod)
289
    {
290
        /** @var OrderInterface $order */
291
        $order = $this->sharedStorage->get('order');
292
293
        foreach ($order->getPayments() as $payment) {
294
            $payment->setMethod($paymentMethod);
295
        }
296
297
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
298
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
299
300
        $this->objectManager->flush();
301
    }
302
303
    /**
304
     * @Given the customer bought a single :product
305
     * @Given I bought a single :product
306
     */
307
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
308
    {
309
        $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...
310
311
        $this->objectManager->flush();
312
    }
313
314
    /**
315
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
316
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
317
     */
318
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
319
    {
320
        $this->theCustomerBoughtSingleProduct($product);
321
        $this->theCustomerBoughtSingleProduct($secondProduct);
322
    }
323
324
    /**
325
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
326
     */
327
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
328
    {
329
        $variant = $this->variantResolver->getVariant($product);
330
        $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...
331
332
        $this->objectManager->flush();
333
    }
334
335
    /**
336
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
337
     */
338
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
339
    {
340
        $this->addProductVariantToOrder($variant, $quantity);
341
342
        $this->objectManager->flush();
343
    }
344
345
    /**
346
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
347
     */
348
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
349
    {
350
        $this->addProductVariantToOrder($productVariant);
351
352
        $this->objectManager->flush();
353
    }
354
355
    /**
356
     * @Given the customer bought a single :product using :coupon coupon
357
     * @Given I bought a single :product using :coupon coupon
358
     */
359
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
360
    {
361
        $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...
362
        $order->setPromotionCoupon($coupon);
363
364
        $this->objectManager->flush();
365
    }
366
367
    /**
368
     * @Given I used :coupon coupon
369
     */
370
    public function iUsedCoupon(PromotionCouponInterface $coupon)
371
    {
372
        $order = $this->sharedStorage->get('order');
373
        $order->setPromotionCoupon($coupon);
374
375
        $this->objectManager->flush();
376
    }
377
378
    /**
379
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" product), ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
380
     */
381
    public function iHaveAlreadyPlacedOrderNthTimes(
382
        UserInterface $user,
383
        $numberOfOrders,
384
        ProductInterface $product,
385
        ShippingMethodInterface $shippingMethod,
386
        AddressInterface $address,
387
        PaymentMethodInterface $paymentMethod
388
    ) {
389
        $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...
390
        for ($i = 0; $i < $numberOfOrders; $i++) {
391
            /** @var ProductVariantInterface $variant */
392
            $variant = $this->variantResolver->getVariant($product);
393
394
            /** @var ChannelPricingInterface $channelPricing */
395
            $channelPricing = $variant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
396
397
            /** @var \Sylius\Component\Order\Model\OrderItemInterface $item */
398
            $item = $this->orderItemFactory->createNew();
399
            $item->setVariant($variant);
400
            $item->setUnitPrice($channelPricing->getPrice());
401
402
            $this->itemQuantityModifier->modify($item, 1);
403
404
            $order = $this->createOrder($customer, '#00000'.$i);
405
            $order->addItem($item);
406
407
            $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
408
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
409
410
            $this->objectManager->persist($order);
411
            $this->sharedStorage->set('order', $order);
412
        }
413
414
        $this->objectManager->flush();
415
    }
416
417
    /**
418
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
419
     */
420
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
421
    {
422
        $order = $this->createOrder($customer, $number);
423
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
424
        $order->setState(OrderInterface::STATE_NEW);
425
426
        $this->orderRepository->add($order);
427
    }
428
429
    /**
430
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") on a (channel "[^"]+")$/
431
     */
432
    public function thisCustomerHasPlacedAnOrderOnAChannel(CustomerInterface $customer, $number, $channel)
433
    {
434
        $order = $this->createOrder($customer, $number, $channel);
435
        $order->setState(OrderInterface::STATE_NEW);
436
437
        $this->orderRepository->add($order);
438
    }
439
440
    /**
441
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) placed (\d+) orders on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
442
     */
443
    public function thisCustomerPlacedOrdersOnChannelBuyingProducts(
444
        CustomerInterface $customer,
445
        int $orderCount,
446
        ChannelInterface $channel,
447
        int $productCount,
448
        ProductInterface $product
449
    ): void {
450
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product);
451
    }
452
453
    /**
454
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) fulfilled (\d+) orders placed on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
455
     */
456
    public function thisCustomerFulfilledOrdersPlacedOnChannelBuyingProducts(
457
        CustomerInterface $customer,
458
        int $orderCount,
459
        ChannelInterface $channel,
460
        int $productCount,
461
        ProductInterface $product
462
    ): void {
463
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product, true);
464
    }
465
466
    /**
467
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
468
     */
469
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
470
    {
471
        $customers = $this->generateCustomers($numberOfCustomers);
472
473
        $sampleProductVariant = $this->sharedStorage->get('variant');
474
        $total = $this->getPriceFromString($total);
475
476
        for ($i = 0; $i < $numberOfCustomers; $i++) {
477
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
478
479
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
480
            $total -= $price;
481
482
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
483
484
            $this->objectManager->persist($order);
485
        }
486
487
        $this->objectManager->flush();
488
    }
489
490
    /**
491
     * @Given a single customer has placed an order for total of :total
492
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
493
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
494
     */
495
    public function customersHavePlacedOrdersForTotalOf(
496
        int $numberOfCustomers = 1,
497
        int $numberOfOrders = 1,
498
        string $total
499
    ): void {
500
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total);
501
    }
502
503
    /**
504
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total
505
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total
506
     */
507
    public function customersHaveFulfilledOrdersPlacedForTotalOf(
508
        int $numberOfCustomers,
509
        int $numberOfOrders,
510
        string $total
511
    ): void{
512
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total, true);
513
    }
514
515
    /**
516
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
517
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
518
     */
519
    public function customersHavePlacedOrdersForTotalOfMostlyProduct(
520
        int $numberOfCustomers,
521
        int $numberOfOrders,
522
        string $total,
523
        ProductInterface $product
524
    ): void {
525
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product);
526
    }
527
528
    /**
529
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
530
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
531
     */
532
    public function customersHaveFulfilledOrdersPlacedForTotalOfMostlyProduct(
533
        int $numberOfCustomers,
534
        int $numberOfOrders,
535
        string $total,
536
        ProductInterface $product
537
    ): void {
538
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product, true);
539
    }
540
541
    /**
542
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") buying a single ("[^"]+" product) for ("[^"]+") on the ("[^"]+" channel)$/
543
     */
544
    public function customerHasPlacedAnOrderBuyingASingleProductForOnTheChannel(
545
        CustomerInterface $customer,
546
        $orderNumber,
547
        ProductInterface $product,
548
        $price,
549
        ChannelInterface $channel
550
    ) {
551
        $order = $this->createOrder($customer, $orderNumber, $channel);
552
        $order->setState(OrderInterface::STATE_NEW);
553
554
        $this->addVariantWithPriceToOrder($order, $product->getVariants()->first(), $price);
555
556
        $this->orderRepository->add($order);
557
    }
558
559
    /**
560
     * @Given /^(this order) is already paid$/
561
     * @Given the order :order is already paid
562
     */
563
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
564
    {
565
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
566
567
        $this->objectManager->flush();
568
    }
569
570
    /**
571
     * @Given /^(this order) has been refunded$/
572
     */
573
    public function thisOrderHasBeenRefunded(OrderInterface $order)
574
    {
575
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_REFUND);
576
577
        $this->objectManager->flush();
578
    }
579
580
    /**
581
     * @Given /^the customer cancelled (this order)$/
582
     * @Given /^(this order) was cancelled$/
583
     * @Given the order :order was cancelled
584
     * @Given /^I cancelled (this order)$/
585
     */
586
    public function theCustomerCancelledThisOrder(OrderInterface $order)
587
    {
588
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
589
590
        $this->objectManager->flush();
591
    }
592
593
    /**
594
     * @Given /^I cancelled my last order$/
595
     */
596
    public function theCustomerCancelledMyLastOrder()
597
    {
598
        $order = $this->sharedStorage->get('order');
599
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
600
601
        $this->objectManager->flush();
602
    }
603
604
    /**
605
     * @Given /^(this order) has already been shipped$/
606
     */
607
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
608
    {
609
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
610
611
        $this->objectManager->flush();
612
    }
613
614
    /**
615
     * @When the customer used coupon :coupon
616
     */
617
    public function theCustomerUsedCoupon(PromotionCouponInterface $coupon)
618
    {
619
        /** @var OrderInterface $order */
620
        $order = $this->sharedStorage->get('order');
621
        $order->setPromotionCoupon($coupon);
622
623
        $this->objectManager->flush();
624
    }
625
626
    /**
627
     * @param OrderInterface $order
628
     * @param string $transition
629
     */
630
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
631
    {
632
        foreach ($order->getShipments() as $shipment) {
633
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
634
        }
635
    }
636
637
    /**
638
     * @param OrderInterface $order
639
     * @param string $transition
640
     */
641
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
642
    {
643
        foreach ($order->getPayments() as $payment) {
644
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
645
        }
646
    }
647
648
    /**
649
     * @param OrderInterface $order
650
     * @param string $transition
651
     */
652
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
653
    {
654
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
655
    }
656
657
    /**
658
     * @param OrderInterface $order
659
     * @param string $transition
660
     */
661
    private function applyTransitionOnOrder(OrderInterface $order, string $transition): void
662
    {
663
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply($transition);
664
    }
665
666
    /**
667
     * @param ProductVariantInterface $productVariant
668
     * @param int $quantity
669
     *
670
     * @return OrderInterface
671
     */
672
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
673
    {
674
        $order = $this->sharedStorage->get('order');
675
676
        $this->addProductVariantsToOrderWithChannelPrice(
677
            $order,
678
            $this->sharedStorage->get('channel'),
679
            $productVariant,
680
            (int) $quantity
681
        );
682
683
        return $order;
684
    }
685
686
    /**
687
     * @param OrderInterface $order
688
     * @param ChannelInterface $channel
689
     * @param ProductVariantInterface $productVariant
690
     * @param int $quantity
691
     */
692
    private function addProductVariantsToOrderWithChannelPrice(
693
        OrderInterface $order,
694
        ChannelInterface $channel,
695
        ProductVariantInterface $productVariant,
696
        int $quantity = 1
697
    ) {
698
        /** @var OrderItemInterface $item */
699
        $item = $this->orderItemFactory->createNew();
700
        $item->setVariant($productVariant);
701
702
        /** @var ChannelPricingInterface $channelPricing */
703
        $channelPricing = $productVariant->getChannelPricingForChannel($channel);
704
        $item->setUnitPrice($channelPricing->getPrice());
705
706
        $this->itemQuantityModifier->modify($item, $quantity);
707
708
        $order->addItem($item);
709
    }
710
711
    /**
712
     * @param CustomerInterface $customer
713
     * @param string $number
714
     * @param ChannelInterface|null $channel
715
     * @param string|null $localeCode
716
     *
717
     * @return OrderInterface
718
     */
719
    private function createOrder(
720
        CustomerInterface $customer,
721
        $number = null,
722
        ChannelInterface $channel = null,
723
        $localeCode = null
724
    ) {
725
        $order = $this->createCart($customer, $channel, $localeCode);
726
727
        if (null !== $number) {
728
            $order->setNumber($number);
729
        }
730
731
        $order->completeCheckout();
732
733
        return $order;
734
    }
735
736
    /**
737
     * @param CustomerInterface $customer
738
     * @param ChannelInterface|null $channel
739
     * @param string|null $localeCode
740
     *
741
     * @return OrderInterface
742
     */
743
    private function createCart(
744
        CustomerInterface $customer,
745
        ChannelInterface $channel = null,
746
        $localeCode = null
747
    ) {
748
        /** @var OrderInterface $order */
749
        $order = $this->orderFactory->createNew();
750
751
        $order->setCustomer($customer);
752
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
753
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
754
        $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...
755
756
        return $order;
757
    }
758
759
    /**
760
     * @param int $count
761
     *
762
     * @return CustomerInterface[]
763
     */
764
    private function generateCustomers($count)
765
    {
766
        $customers = [];
767
768
        for ($i = 0; $i < $count; $i++) {
769
            $customer = $this->customerFactory->createNew();
770
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
771
            $customer->setFirstname('John');
772
            $customer->setLastname('Doe'.$i);
773
774
            $customers[] = $customer;
775
776
            $this->customerRepository->add($customer);
777
        }
778
779
        return $customers;
780
    }
781
782
    /**
783
     * @param string $price
784
     *
785
     * @return int
786
     */
787
    private function getPriceFromString($price)
788
    {
789
        return (int) round(str_replace(['€', '£', '$'], '', $price) * 100, 2);
790
    }
791
792
    /**
793
     * @param OrderInterface $order
794
     * @param ShippingMethodInterface $shippingMethod
795
     * @param AddressInterface $address
796
     * @param PaymentMethodInterface $paymentMethod
797
     */
798
    private function checkoutUsing(
799
        OrderInterface $order,
800
        ShippingMethodInterface $shippingMethod,
801
        AddressInterface $address,
802
        PaymentMethodInterface $paymentMethod
803
    ) {
804
        $order->setShippingAddress($address);
805
        $order->setBillingAddress(clone $address);
806
807
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
808
809
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
810
    }
811
812
    /**
813
     * @param OrderInterface $order
814
     * @param ShippingMethodInterface $shippingMethod
815
     * @param PaymentMethodInterface $paymentMethod
816
     */
817
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
818
    {
819
        foreach ($order->getShipments() as $shipment) {
820
            $shipment->setMethod($shippingMethod);
821
        }
822
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
823
824
        $payment = $order->getLastPayment(PaymentInterface::STATE_CART);
825
        $payment->setMethod($paymentMethod);
826
827
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
828
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
829
    }
830
831
    /**
832
     * @param OrderInterface $order
833
     * @param ProductVariantInterface $variant
834
     * @param int $price
835
     */
836
    private function addVariantWithPriceToOrder(OrderInterface $order, ProductVariantInterface $variant, $price)
837
    {
838
        $item = $this->orderItemFactory->createNew();
839
        $item->setVariant($variant);
840
        $item->setUnitPrice($price);
841
842
        $this->itemQuantityModifier->modify($item, 1);
843
844
        $order->addItem($item);
845
    }
846
847
    /**
848
     * @param int $numberOfCustomers
849
     * @param int $numberOfOrders
850
     * @param string $total
851
     * @param bool $isFulfilled
852
     */
853
    private function createOrders(
854
        int $numberOfCustomers,
855
        int $numberOfOrders,
856
        string $total,
857
        bool $isFulfilled = false
858
    ): void {
859
        $customers = $this->generateCustomers($numberOfCustomers);
860
        $sampleProductVariant = $this->sharedStorage->get('variant');
861
        $total = $this->getPriceFromString($total);
862
863
        for ($i = 0; $i < $numberOfOrders; $i++) {
864
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
865
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
866
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
867
868
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
869
            $total -= $price;
870
871
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
872
873
            if ($isFulfilled) {
874
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
875
            }
876
877
            $this->objectManager->persist($order);
878
            $this->sharedStorage->set('order', $order);
879
        }
880
881
        $this->objectManager->flush();
882
    }
883
884
    /**
885
     * @param int $numberOfCustomers
886
     * @param int $numberOfOrders
887
     * @param string $total
888
     * @param ProductInterface $product
889
     * @param bool $isFulfilled
890
     */
891
    private function createOrdersWithProduct(
892
        int $numberOfCustomers,
893
        int $numberOfOrders,
894
        string $total,
895
        ProductInterface $product,
896
        bool $isFulfilled = false
897
    ): void {
898
        $customers = $this->generateCustomers($numberOfCustomers);
899
        $sampleProductVariant = $product->getVariants()->first();
900
        $total = $this->getPriceFromString($total);
901
902
        for ($i = 0; $i < $numberOfOrders; $i++) {
903
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
904
            $order->setState(OrderInterface::STATE_NEW);
905
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
906
907
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
908
            $total -= $price;
909
910
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
911
912
            if ($isFulfilled) {
913
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
914
            }
915
916
            $this->objectManager->persist($order);
917
        }
918
919
        $this->objectManager->flush();
920
    }
921
922
    /**
923
     * @param CustomerInterface $customer
924
     * @param int $orderCount
925
     * @param ChannelInterface $channel
926
     * @param int $productCount
927
     * @param ProductInterface $product
928
     * @param bool $isFulfilled
929
     */
930
    private function createOrdersForCustomer(
931
        CustomerInterface $customer,
932
        int $orderCount,
933
        ChannelInterface $channel,
934
        int $productCount,
935
        ProductInterface $product,
936
        bool $isFulfilled = false
937
    ): void {
938
        for ($i = 0; $i < $orderCount; $i++) {
939
            $order = $this->createOrder($customer, uniqid('#'), $channel);
940
941
            $this->addProductVariantsToOrderWithChannelPrice(
942
                $order,
943
                $channel,
944
                $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...
945
                (int) $productCount
946
            );
947
948
            $order->setState($isFulfilled ? OrderInterface::STATE_FULFILLED : OrderInterface::STATE_NEW);
949
950
            $this->objectManager->persist($order);
951
        }
952
953
        $this->objectManager->flush();
954
    }
955
}
956