Completed
Push — pull-request/8600 ( 367a7c )
by Kamil
56:28 queued 34:06
created

OrderContext::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
rs 9.0856
cc 1
eloc 21
nc 1
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Behat\Context\Setup;
15
16
use Behat\Behat\Context\Context;
17
use Doctrine\Common\Persistence\ObjectManager;
18
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
19
use Sylius\Behat\Service\SharedStorageInterface;
20
use Sylius\Component\Core\Model\AddressInterface;
21
use Sylius\Component\Core\Model\ChannelInterface;
22
use Sylius\Component\Core\Model\ChannelPricingInterface;
23
use Sylius\Component\Core\Model\OrderInterface;
24
use Sylius\Component\Core\Model\OrderItemInterface;
25
use Sylius\Component\Core\Model\ProductInterface;
26
use Sylius\Component\Core\Model\ProductVariantInterface;
27
use Sylius\Component\Core\Model\PromotionCouponInterface;
28
use Sylius\Component\Core\Model\ShippingMethodInterface;
29
use Sylius\Component\Core\OrderCheckoutTransitions;
30
use Sylius\Component\Core\OrderPaymentTransitions;
31
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
32
use Sylius\Component\Customer\Model\CustomerInterface;
33
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
34
use Sylius\Component\Order\OrderTransitions;
35
use Sylius\Component\Payment\Model\PaymentInterface;
36
use Sylius\Component\Payment\Model\PaymentMethodInterface;
37
use Sylius\Component\Payment\PaymentTransitions;
38
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
39
use Sylius\Component\Resource\Factory\FactoryInterface;
40
use Sylius\Component\Resource\Repository\RepositoryInterface;
41
use Sylius\Component\Shipping\ShipmentTransitions;
42
use Sylius\Component\User\Model\UserInterface;
43
44
/**
45
 * @author Łukasz Chruściel <[email protected]>
46
 */
47
final class OrderContext implements Context
48
{
49
    /**
50
     * @var SharedStorageInterface
51
     */
52
    private $sharedStorage;
53
54
    /**
55
     * @var OrderRepositoryInterface
56
     */
57
    private $orderRepository;
58
59
    /**
60
     * @var FactoryInterface
61
     */
62
    private $orderFactory;
63
64
    /**
65
     * @var FactoryInterface
66
     */
67
    private $orderItemFactory;
68
69
    /**
70
     * @var OrderItemQuantityModifierInterface
71
     */
72
    private $itemQuantityModifier;
73
74
    /**
75
     * @var FactoryInterface
76
     */
77
    private $customerFactory;
78
79
    /**
80
     * @var RepositoryInterface
81
     */
82
    private $customerRepository;
83
84
    /**
85
     * @var ObjectManager
86
     */
87
    private $objectManager;
88
89
    /**
90
     * @var StateMachineFactoryInterface
91
     */
92
    private $stateMachineFactory;
93
94
    /**
95
     * @var ProductVariantResolverInterface
96
     */
97
    private $variantResolver;
98
99
    /**
100
     * @param SharedStorageInterface $sharedStorage
101
     * @param OrderRepositoryInterface $orderRepository
102
     * @param FactoryInterface $orderFactory
103
     * @param FactoryInterface $orderItemFactory
104
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
105
     * @param FactoryInterface $customerFactory
106
     * @param RepositoryInterface $customerRepository
107
     * @param ObjectManager $objectManager
108
     * @param StateMachineFactoryInterface $stateMachineFactory
109
     * @param ProductVariantResolverInterface $variantResolver
110
     */
111
    public function __construct(
112
        SharedStorageInterface $sharedStorage,
113
        OrderRepositoryInterface $orderRepository,
114
        FactoryInterface $orderFactory,
115
        FactoryInterface $orderItemFactory,
116
        OrderItemQuantityModifierInterface $itemQuantityModifier,
117
        FactoryInterface $customerFactory,
118
        RepositoryInterface $customerRepository,
119
        ObjectManager $objectManager,
120
        StateMachineFactoryInterface $stateMachineFactory,
121
        ProductVariantResolverInterface $variantResolver
122
    ) {
123
        $this->sharedStorage = $sharedStorage;
124
        $this->orderRepository = $orderRepository;
125
        $this->orderFactory = $orderFactory;
126
        $this->orderItemFactory = $orderItemFactory;
127
        $this->itemQuantityModifier = $itemQuantityModifier;
128
        $this->customerFactory = $customerFactory;
129
        $this->customerRepository = $customerRepository;
130
        $this->objectManager = $objectManager;
131
        $this->stateMachineFactory = $stateMachineFactory;
132
        $this->variantResolver = $variantResolver;
133
    }
134
135
    /**
136
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
137
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
138
     * @Given a customer :customer placed an order :orderNumber
139
     * @Given the customer :customer has already placed an order :orderNumber
140
     */
141
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
142
    {
143
        $order = $this->createOrder($customer, $orderNumber);
144
145
        $this->sharedStorage->set('order', $order);
146
147
        $this->orderRepository->add($order);
148
    }
149
150
    /**
151
     * @Given /^the guest customer placed order with ("[^"]+" product) for "([^"]+)" and ("[^"]+" based shipping address) with ("[^"]+" shipping method) and ("[^"]+" payment)$/
152
     */
153
    public function theGuestCustomerPlacedOrderWithForAndBasedShippingAddress(
154
        ProductInterface $product,
155
        string $email,
156
        AddressInterface $address,
157
        ShippingMethodInterface $shippingMethod,
158
        PaymentMethodInterface $paymentMethod
159
    ) {
160
        /** @var CustomerInterface $customer */
161
        $customer = $this->customerFactory->createNew();
162
        $customer->setEmail($email);
163
        $customer->setFirstname('John');
164
        $customer->setLastname('Doe');
165
166
        $this->customerRepository->add($customer);
167
168
        $this->placeOrder($product, $shippingMethod, $address, $paymentMethod, $customer, 1);
169
        $this->objectManager->flush();
170
    }
171
172
    /**
173
     * @Given a customer :customer added something to cart
174
     */
175
    public function customerStartedCheckout(CustomerInterface $customer)
176
    {
177
        $cart = $this->createCart($customer);
178
179
        $this->sharedStorage->set('cart', $cart);
180
181
        $this->orderRepository->add($cart);
182
    }
183
184
    /**
185
     * @Given /^(I) placed (an order "[^"]+")$/
186
     */
187
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
188
    {
189
        $customer = $user->getCustomer();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\User\Model\UserInterface as the method getCustomer() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ShopUser.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
190
        $order = $this->createOrder($customer, $orderNumber);
191
192
        $this->sharedStorage->set('order', $order);
193
194
        $this->orderRepository->add($order);
195
    }
196
197
    /**
198
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
199
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
200
     */
201
    public function theCustomerAddressedItTo(AddressInterface $address)
202
    {
203
        /** @var OrderInterface $order */
204
        $order = $this->sharedStorage->get('order');
205
        $order->setShippingAddress($address);
206
207
        $this->objectManager->flush();
208
    }
209
210
    /**
211
     * @Given the customer changed shipping address' street to :street
212
     */
213
    public function theCustomerChangedShippingAddressStreetTo($street)
214
    {
215
        /** @var OrderInterface $order */
216
        $order = $this->sharedStorage->get('order');
217
218
        $shippingAddress = $order->getShippingAddress();
219
        $shippingAddress->setStreet($street);
220
221
        $this->objectManager->flush();
222
223
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
224
    }
225
226
    /**
227
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
228
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
229
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
230
     */
231
    public function forTheBillingAddressOf(AddressInterface $address)
232
    {
233
        /** @var OrderInterface $order */
234
        $order = $this->sharedStorage->get('order');
235
236
        $order->setBillingAddress($address);
237
238
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
239
240
        $this->objectManager->flush();
241
    }
242
243
    /**
244
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
245
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
246
     */
247
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
248
    {
249
        $this->theCustomerAddressedItTo($address);
250
        $this->forTheBillingAddressOf(clone $address);
251
    }
252
253
    /**
254
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
255
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
256
     */
257
    public function theCustomerChoseShippingToWithPayment(
258
        ShippingMethodInterface $shippingMethod,
259
        AddressInterface $address,
260
        PaymentMethodInterface $paymentMethod
261
    ) {
262
        /** @var OrderInterface $order */
263
        $order = $this->sharedStorage->get('order');
264
265
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
266
267
        $this->objectManager->flush();
268
    }
269
270
    /**
271
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
272
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
273
     */
274
    public function theCustomerChoseShippingWithPayment(
275
        ShippingMethodInterface $shippingMethod,
276
        PaymentMethodInterface $paymentMethod
277
    ) {
278
        /** @var OrderInterface $order */
279
        $order = $this->sharedStorage->get('order');
280
281
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
282
283
        $this->objectManager->flush();
284
    }
285
286
    /**
287
     * @Given /^the customer chose ("[^"]+" shipping method)$/
288
     */
289
    public function theCustomerChoseShippingMethod(ShippingMethodInterface $shippingMethod)
290
    {
291
        /** @var OrderInterface $order */
292
        $order = $this->sharedStorage->get('order');
293
294
        foreach ($order->getShipments() as $shipment) {
295
            $shipment->setMethod($shippingMethod);
296
        }
297
298
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
299
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
300
        if (!$order->getPayments()->isEmpty()) {
301
            $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH)->apply(OrderPaymentTransitions::TRANSITION_PAY);
302
        }
303
304
        $this->objectManager->flush();
305
    }
306
307
    /**
308
     * @Given /^the customer chose ("[^"]+" payment)$/
309
     */
310
    public function theCustomerChosePayment(PaymentMethodInterface $paymentMethod)
311
    {
312
        /** @var OrderInterface $order */
313
        $order = $this->sharedStorage->get('order');
314
315
        foreach ($order->getPayments() as $payment) {
316
            $payment->setMethod($paymentMethod);
317
        }
318
319
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
320
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
321
322
        $this->objectManager->flush();
323
    }
324
325
    /**
326
     * @Given the customer bought a single :product
327
     * @Given I bought a single :product
328
     */
329
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
330
    {
331
        $this->addProductVariantToOrder($this->variantResolver->getVariant($product), 1);
0 ignored issues
show
Documentation introduced by
$this->variantResolver->getVariant($product) is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
384
        $order->setPromotionCoupon($coupon);
385
386
        $this->objectManager->flush();
387
    }
388
389
    /**
390
     * @Given I used :coupon coupon
391
     */
392
    public function iUsedCoupon(PromotionCouponInterface $coupon)
393
    {
394
        $order = $this->sharedStorage->get('order');
395
        $order->setPromotionCoupon($coupon);
396
397
        $this->objectManager->flush();
398
    }
399
400
    /**
401
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" product), ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
402
     */
403
    public function iHaveAlreadyPlacedOrderNthTimes(
404
        UserInterface $user,
405
        $numberOfOrders,
406
        ProductInterface $product,
407
        ShippingMethodInterface $shippingMethod,
408
        AddressInterface $address,
409
        PaymentMethodInterface $paymentMethod
410
    ) {
411
        $customer = $user->getCustomer();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\User\Model\UserInterface as the method getCustomer() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ShopUser.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
412
        for ($i = 0; $i < $numberOfOrders; $i++) {
413
            $this->placeOrder($product, $shippingMethod, $address, $paymentMethod, $customer, $i);
414
        }
415
416
        $this->objectManager->flush();
417
    }
418
419
    /**
420
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
421
     */
422
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
423
    {
424
        $order = $this->createOrder($customer, $number);
425
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
426
        $order->setState(OrderInterface::STATE_NEW);
427
428
        $this->orderRepository->add($order);
429
    }
430
431
    /**
432
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") on a (channel "[^"]+")$/
433
     */
434
    public function thisCustomerHasPlacedAnOrderOnAChannel(CustomerInterface $customer, $number, $channel)
435
    {
436
        $order = $this->createOrder($customer, $number, $channel);
437
        $order->setState(OrderInterface::STATE_NEW);
438
439
        $this->orderRepository->add($order);
440
        $this->sharedStorage->set('order', $order);
441
    }
442
443
    /**
444
     * @Given /^(this customer) has(?:| also) started checkout on a (channel "[^"]+")$/
445
     */
446
    public function thisCustomerHasStartedCheckoutOnAChannel(CustomerInterface $customer, $channel)
447
    {
448
        $order = $this->createOrder($customer, null, $channel);
449
450
        $this->orderRepository->add($order);
451
        $this->sharedStorage->set('order', $order);
452
    }
453
454
    /**
455
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) placed (\d+) orders on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
456
     */
457
    public function thisCustomerPlacedOrdersOnChannelBuyingProducts(
458
        CustomerInterface $customer,
459
        int $orderCount,
460
        ChannelInterface $channel,
461
        int $productCount,
462
        ProductInterface $product
463
    ): void {
464
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product);
465
    }
466
467
    /**
468
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) fulfilled (\d+) orders placed on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
469
     */
470
    public function thisCustomerFulfilledOrdersPlacedOnChannelBuyingProducts(
471
        CustomerInterface $customer,
472
        int $orderCount,
473
        ChannelInterface $channel,
474
        int $productCount,
475
        ProductInterface $product
476
    ): void {
477
        $this->createOrdersForCustomer($customer, $orderCount, $channel, $productCount, $product, true);
478
    }
479
480
    /**
481
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
482
     */
483
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
484
    {
485
        $customers = $this->generateCustomers($numberOfCustomers);
486
487
        $sampleProductVariant = $this->sharedStorage->get('variant');
488
        $total = $this->getPriceFromString($total);
489
490
        for ($i = 0; $i < $numberOfCustomers; $i++) {
491
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
492
493
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
494
            $total -= $price;
495
496
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
497
498
            $this->objectManager->persist($order);
499
        }
500
501
        $this->objectManager->flush();
502
    }
503
504
    /**
505
     * @Given a single customer has placed an order for total of :total
506
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
507
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
508
     */
509
    public function customersHavePlacedOrdersForTotalOf(
510
        int $numberOfCustomers = 1,
511
        int $numberOfOrders = 1,
512
        string $total
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
513
    ): void {
514
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total);
515
    }
516
517
    /**
518
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total
519
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total
520
     */
521
    public function customersHaveFulfilledOrdersPlacedForTotalOf(
522
        int $numberOfCustomers,
523
        int $numberOfOrders,
524
        string $total
525
    ): void{
526
        $this->createOrders($numberOfCustomers, $numberOfOrders, $total, true);
527
    }
528
529
    /**
530
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
531
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
532
     */
533
    public function customersHavePlacedOrdersForTotalOfMostlyProduct(
534
        int $numberOfCustomers,
535
        int $numberOfOrders,
536
        string $total,
537
        ProductInterface $product
538
    ): void {
539
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product);
540
    }
541
542
    /**
543
     * @Given :numberOfCustomers customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
544
     * @Given then :numberOfCustomers more customers have fulfilled :numberOfOrders orders placed for total of :total mostly :product product
545
     */
546
    public function customersHaveFulfilledOrdersPlacedForTotalOfMostlyProduct(
547
        int $numberOfCustomers,
548
        int $numberOfOrders,
549
        string $total,
550
        ProductInterface $product
551
    ): void {
552
        $this->createOrdersWithProduct($numberOfCustomers, $numberOfOrders, $total, $product, true);
553
    }
554
555
    /**
556
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") buying a single ("[^"]+" product) for ("[^"]+") on the ("[^"]+" channel)$/
557
     */
558
    public function customerHasPlacedAnOrderBuyingASingleProductForOnTheChannel(
559
        CustomerInterface $customer,
560
        $orderNumber,
561
        ProductInterface $product,
562
        $price,
563
        ChannelInterface $channel
564
    ) {
565
        $order = $this->createOrder($customer, $orderNumber, $channel);
566
        $order->setState(OrderInterface::STATE_NEW);
567
568
        $this->addVariantWithPriceToOrder($order, $product->getVariants()->first(), $price);
569
570
        $this->orderRepository->add($order);
571
        $this->sharedStorage->set('order',  $order);
572
    }
573
574
    /**
575
     * @Given /^(this order) is already paid$/
576
     * @Given the order :order is already paid
577
     */
578
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
579
    {
580
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
581
582
        $this->objectManager->flush();
583
    }
584
585
    /**
586
     * @Given /^(this order) has been refunded$/
587
     */
588
    public function thisOrderHasBeenRefunded(OrderInterface $order)
589
    {
590
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_REFUND);
591
592
        $this->objectManager->flush();
593
    }
594
595
    /**
596
     * @Given /^the customer cancelled (this order)$/
597
     * @Given /^(this order) was cancelled$/
598
     * @Given the order :order was cancelled
599
     * @Given /^I cancelled (this order)$/
600
     */
601
    public function theCustomerCancelledThisOrder(OrderInterface $order)
602
    {
603
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
604
605
        $this->objectManager->flush();
606
    }
607
608
    /**
609
     * @Given /^I cancelled my last order$/
610
     */
611
    public function theCustomerCancelledMyLastOrder()
612
    {
613
        $order = $this->sharedStorage->get('order');
614
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
615
616
        $this->objectManager->flush();
617
    }
618
619
    /**
620
     * @Given /^(this order) has already been shipped$/
621
     */
622
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
623
    {
624
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
625
626
        $this->objectManager->flush();
627
    }
628
629
    /**
630
     * @When the customer used coupon :coupon
631
     */
632
    public function theCustomerUsedCoupon(PromotionCouponInterface $coupon)
633
    {
634
        /** @var OrderInterface $order */
635
        $order = $this->sharedStorage->get('order');
636
        $order->setPromotionCoupon($coupon);
637
638
        $this->objectManager->flush();
639
    }
640
641
    /**
642
     * @param OrderInterface $order
643
     * @param string $transition
644
     */
645
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
646
    {
647
        foreach ($order->getShipments() as $shipment) {
648
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
649
        }
650
    }
651
652
    /**
653
     * @param OrderInterface $order
654
     * @param string $transition
655
     */
656
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
657
    {
658
        foreach ($order->getPayments() as $payment) {
659
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
660
        }
661
    }
662
663
    /**
664
     * @param OrderInterface $order
665
     * @param string $transition
666
     */
667
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
668
    {
669
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
670
    }
671
672
    /**
673
     * @param OrderInterface $order
674
     * @param string $transition
675
     */
676
    private function applyTransitionOnOrder(OrderInterface $order, string $transition): void
677
    {
678
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply($transition);
679
    }
680
681
    /**
682
     * @param ProductVariantInterface $productVariant
683
     * @param int $quantity
684
     *
685
     * @return OrderInterface
686
     */
687
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
688
    {
689
        $order = $this->sharedStorage->get('order');
690
691
        $this->addProductVariantsToOrderWithChannelPrice(
692
            $order,
693
            $this->sharedStorage->get('channel'),
694
            $productVariant,
695
            (int) $quantity
696
        );
697
698
        return $order;
699
    }
700
701
    /**
702
     * @param OrderInterface $order
703
     * @param ChannelInterface $channel
704
     * @param ProductVariantInterface $productVariant
705
     * @param int $quantity
706
     */
707
    private function addProductVariantsToOrderWithChannelPrice(
708
        OrderInterface $order,
709
        ChannelInterface $channel,
710
        ProductVariantInterface $productVariant,
711
        int $quantity = 1
712
    ) {
713
        /** @var OrderItemInterface $item */
714
        $item = $this->orderItemFactory->createNew();
715
        $item->setVariant($productVariant);
716
717
        /** @var ChannelPricingInterface $channelPricing */
718
        $channelPricing = $productVariant->getChannelPricingForChannel($channel);
719
        $item->setUnitPrice($channelPricing->getPrice());
720
721
        $this->itemQuantityModifier->modify($item, $quantity);
722
723
        $order->addItem($item);
724
    }
725
726
    /**
727
     * @param CustomerInterface $customer
728
     * @param string $number
0 ignored issues
show
Documentation introduced by
Should the type for parameter $number not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
729
     * @param ChannelInterface|null $channel
730
     * @param string|null $localeCode
731
     *
732
     * @return OrderInterface
733
     */
734
    private function createOrder(
735
        CustomerInterface $customer,
736
        $number = null,
737
        ChannelInterface $channel = null,
738
        $localeCode = null
739
    ) {
740
        $order = $this->createCart($customer, $channel, $localeCode);
741
742
        if (null !== $number) {
743
            $order->setNumber($number);
744
        }
745
746
        $order->completeCheckout();
747
748
        return $order;
749
    }
750
751
    /**
752
     * @param CustomerInterface $customer
753
     * @param ChannelInterface|null $channel
754
     * @param string|null $localeCode
755
     *
756
     * @return OrderInterface
757
     */
758
    private function createCart(
759
        CustomerInterface $customer,
760
        ChannelInterface $channel = null,
761
        $localeCode = null
762
    ) {
763
        /** @var OrderInterface $order */
764
        $order = $this->orderFactory->createNew();
765
766
        $order->setCustomer($customer);
767
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
768
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
769
        $order->setCurrencyCode($order->getChannel()->getBaseCurrency()->getCode());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Channel\Model\ChannelInterface as the method getBaseCurrency() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\Channel.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
770
771
        return $order;
772
    }
773
774
    /**
775
     * @param int $count
776
     *
777
     * @return CustomerInterface[]
778
     */
779
    private function generateCustomers($count)
780
    {
781
        $customers = [];
782
783
        for ($i = 0; $i < $count; $i++) {
784
            $customer = $this->customerFactory->createNew();
785
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
786
            $customer->setFirstname('John');
787
            $customer->setLastname('Doe'.$i);
788
789
            $customers[] = $customer;
790
791
            $this->customerRepository->add($customer);
792
        }
793
794
        return $customers;
795
    }
796
797
    /**
798
     * @param string $price
799
     *
800
     * @return int
801
     */
802
    private function getPriceFromString($price)
803
    {
804
        return (int) round(str_replace(['€', '£', '$'], '', $price) * 100, 2);
805
    }
806
807
    /**
808
     * @param OrderInterface $order
809
     * @param ShippingMethodInterface $shippingMethod
810
     * @param AddressInterface $address
811
     * @param PaymentMethodInterface $paymentMethod
812
     */
813
    private function checkoutUsing(
814
        OrderInterface $order,
815
        ShippingMethodInterface $shippingMethod,
816
        AddressInterface $address,
817
        PaymentMethodInterface $paymentMethod
818
    ) {
819
        $order->setShippingAddress($address);
820
        $order->setBillingAddress(clone $address);
821
822
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
823
824
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
825
    }
826
827
    /**
828
     * @param OrderInterface $order
829
     * @param ShippingMethodInterface $shippingMethod
830
     * @param PaymentMethodInterface $paymentMethod
831
     */
832
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
833
    {
834
        foreach ($order->getShipments() as $shipment) {
835
            $shipment->setMethod($shippingMethod);
836
        }
837
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
838
839
        $payment = $order->getLastPayment(PaymentInterface::STATE_CART);
840
        $payment->setMethod($paymentMethod);
841
842
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
843
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
844
    }
845
846
    /**
847
     * @param OrderInterface $order
848
     * @param ProductVariantInterface $variant
849
     * @param int $price
850
     */
851
    private function addVariantWithPriceToOrder(OrderInterface $order, ProductVariantInterface $variant, $price)
852
    {
853
        $item = $this->orderItemFactory->createNew();
854
        $item->setVariant($variant);
855
        $item->setUnitPrice($price);
856
857
        $this->itemQuantityModifier->modify($item, 1);
858
859
        $order->addItem($item);
860
    }
861
862
    /**
863
     * @param int $numberOfCustomers
864
     * @param int $numberOfOrders
865
     * @param string $total
866
     * @param bool $isFulfilled
867
     */
868
    private function createOrders(
869
        int $numberOfCustomers,
870
        int $numberOfOrders,
871
        string $total,
872
        bool $isFulfilled = false
873
    ): void {
874
        $customers = $this->generateCustomers($numberOfCustomers);
875
        $sampleProductVariant = $this->sharedStorage->get('variant');
876
        $total = $this->getPriceFromString($total);
877
878
        for ($i = 0; $i < $numberOfOrders; $i++) {
879
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
880
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
881
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
882
883
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
884
            $total -= $price;
885
886
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
887
888
            if ($isFulfilled) {
889
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
890
            }
891
892
            $this->objectManager->persist($order);
893
            $this->sharedStorage->set('order', $order);
894
        }
895
896
        $this->objectManager->flush();
897
    }
898
899
    /**
900
     * @param int $numberOfCustomers
901
     * @param int $numberOfOrders
902
     * @param string $total
903
     * @param ProductInterface $product
904
     * @param bool $isFulfilled
905
     */
906
    private function createOrdersWithProduct(
907
        int $numberOfCustomers,
908
        int $numberOfOrders,
909
        string $total,
910
        ProductInterface $product,
911
        bool $isFulfilled = false
912
    ): void {
913
        $customers = $this->generateCustomers($numberOfCustomers);
914
        $sampleProductVariant = $product->getVariants()->first();
915
        $total = $this->getPriceFromString($total);
916
917
        for ($i = 0; $i < $numberOfOrders; $i++) {
918
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
919
            $order->setState(OrderInterface::STATE_NEW);
920
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
921
922
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
923
            $total -= $price;
924
925
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
926
927
            if ($isFulfilled) {
928
                $this->applyTransitionOnOrder($order, OrderTransitions::TRANSITION_FULFILL);
929
            }
930
931
            $this->objectManager->persist($order);
932
        }
933
934
        $this->objectManager->flush();
935
    }
936
937
    /**
938
     * @param CustomerInterface $customer
939
     * @param int $orderCount
940
     * @param ChannelInterface $channel
941
     * @param int $productCount
942
     * @param ProductInterface $product
943
     * @param bool $isFulfilled
944
     */
945
    private function createOrdersForCustomer(
946
        CustomerInterface $customer,
947
        int $orderCount,
948
        ChannelInterface $channel,
949
        int $productCount,
950
        ProductInterface $product,
951
        bool $isFulfilled = false
952
    ): void {
953
        for ($i = 0; $i < $orderCount; $i++) {
954
            $order = $this->createOrder($customer, uniqid('#'), $channel);
955
956
            $this->addProductVariantsToOrderWithChannelPrice(
957
                $order,
958
                $channel,
959
                $this->variantResolver->getVariant($product),
0 ignored issues
show
Documentation introduced by
$this->variantResolver->getVariant($product) is of type object<Sylius\Component\...tVariantInterface>|null, but the function expects a object<Sylius\Component\...roductVariantInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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