Completed
Push — assert-overflow ( 0688b3...fbf83a )
by Kamil
66:00 queued 44:43
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
namespace Sylius\Behat\Context\Setup;
13
14
use Behat\Behat\Context\Context;
15
use Doctrine\Common\Persistence\ObjectManager;
16
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
17
use Sylius\Behat\Service\SharedStorageInterface;
18
use Sylius\Component\Core\Model\AddressInterface;
19
use Sylius\Component\Core\Model\ChannelInterface;
20
use Sylius\Component\Core\Model\ChannelPricingInterface;
21
use Sylius\Component\Core\Model\OrderInterface;
22
use Sylius\Component\Core\Model\OrderItemInterface;
23
use Sylius\Component\Core\Model\ProductInterface;
24
use Sylius\Component\Core\Model\ProductVariantInterface;
25
use Sylius\Component\Core\Model\PromotionCouponInterface;
26
use Sylius\Component\Core\Model\ShippingMethodInterface;
27
use Sylius\Component\Core\OrderCheckoutTransitions;
28
use Sylius\Component\Core\OrderPaymentTransitions;
29
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
30
use Sylius\Component\Customer\Model\CustomerInterface;
31
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
32
use Sylius\Component\Order\OrderTransitions;
33
use Sylius\Component\Payment\Model\PaymentInterface;
34
use Sylius\Component\Payment\Model\PaymentMethodInterface;
35
use Sylius\Component\Payment\PaymentTransitions;
36
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
37
use Sylius\Component\Resource\Factory\FactoryInterface;
38
use Sylius\Component\Resource\Repository\RepositoryInterface;
39
use Sylius\Component\Shipping\ShipmentTransitions;
40
use Sylius\Component\User\Model\UserInterface;
41
42
/**
43
 * @author Łukasz Chruściel <[email protected]>
44
 */
45
final class OrderContext implements Context
46
{
47
    /**
48
     * @var SharedStorageInterface
49
     */
50
    private $sharedStorage;
51
52
    /**
53
     * @var OrderRepositoryInterface
54
     */
55
    private $orderRepository;
56
57
    /**
58
     * @var FactoryInterface
59
     */
60
    private $orderFactory;
61
62
    /**
63
     * @var FactoryInterface
64
     */
65
    private $orderItemFactory;
66
67
    /**
68
     * @var OrderItemQuantityModifierInterface
69
     */
70
    private $itemQuantityModifier;
71
72
    /**
73
     * @var FactoryInterface
74
     */
75
    private $customerFactory;
76
77
    /**
78
     * @var RepositoryInterface
79
     */
80
    private $customerRepository;
81
82
    /**
83
     * @var ObjectManager
84
     */
85
    private $objectManager;
86
87
    /**
88
     * @var StateMachineFactoryInterface
89
     */
90
    private $stateMachineFactory;
91
92
    /**
93
     * @var ProductVariantResolverInterface
94
     */
95
    private $variantResolver;
96
97
    /**
98
     * @param SharedStorageInterface $sharedStorage
99
     * @param OrderRepositoryInterface $orderRepository
100
     * @param FactoryInterface $orderFactory
101
     * @param FactoryInterface $orderItemFactory
102
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
103
     * @param FactoryInterface $customerFactory
104
     * @param RepositoryInterface $customerRepository
105
     * @param ObjectManager $objectManager
106
     * @param StateMachineFactoryInterface $stateMachineFactory
107
     * @param ProductVariantResolverInterface $variantResolver
108
     */
109
    public function __construct(
110
        SharedStorageInterface $sharedStorage,
111
        OrderRepositoryInterface $orderRepository,
112
        FactoryInterface $orderFactory,
113
        FactoryInterface $orderItemFactory,
114
        OrderItemQuantityModifierInterface $itemQuantityModifier,
115
        FactoryInterface $customerFactory,
116
        RepositoryInterface $customerRepository,
117
        ObjectManager $objectManager,
118
        StateMachineFactoryInterface $stateMachineFactory,
119
        ProductVariantResolverInterface $variantResolver
120
    ) {
121
        $this->sharedStorage = $sharedStorage;
122
        $this->orderRepository = $orderRepository;
123
        $this->orderFactory = $orderFactory;
124
        $this->orderItemFactory = $orderItemFactory;
125
        $this->itemQuantityModifier = $itemQuantityModifier;
126
        $this->customerFactory = $customerFactory;
127
        $this->customerRepository = $customerRepository;
128
        $this->objectManager = $objectManager;
129
        $this->stateMachineFactory = $stateMachineFactory;
130
        $this->variantResolver = $variantResolver;
131
    }
132
133
    /**
134
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
135
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
136
     * @Given a customer :customer placed an order :orderNumber
137
     * @Given the customer :customer has already placed an order :orderNumber
138
     */
139
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
140
    {
141
        $order = $this->createOrder($customer, $orderNumber);
142
143
        $this->sharedStorage->set('order', $order);
144
145
        $this->orderRepository->add($order);
146
    }
147
148
    /**
149
     * @Given a customer :customer added something to cart
150
     */
151
    public function customerStartedCheckout(CustomerInterface $customer)
152
    {
153
        $cart = $this->createCart($customer);
154
155
        $this->sharedStorage->set('cart', $cart);
156
157
        $this->orderRepository->add($cart);
158
    }
159
160
    /**
161
     * @Given /^(I) placed (an order "[^"]+")$/
162
     */
163
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
164
    {
165
        $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...
166
        $order = $this->createOrder($customer, $orderNumber);
167
168
        $this->sharedStorage->set('order', $order);
169
170
        $this->orderRepository->add($order);
171
    }
172
173
    /**
174
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
175
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
176
     */
177
    public function theCustomerAddressedItTo(AddressInterface $address)
178
    {
179
        /** @var OrderInterface $order */
180
        $order = $this->sharedStorage->get('order');
181
        $order->setShippingAddress($address);
182
183
        $this->objectManager->flush();
184
    }
185
186
    /**
187
     * @Given the customer changed shipping address' street to :street
188
     */
189
    public function theCustomerChangedShippingAddressStreetTo($street)
190
    {
191
        /** @var OrderInterface $order */
192
        $order = $this->sharedStorage->get('order');
193
194
        $shippingAddress = $order->getShippingAddress();
195
        $shippingAddress->setStreet($street);
196
197
        $this->objectManager->flush();
198
199
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
200
    }
201
202
    /**
203
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
204
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
205
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
206
     */
207
    public function forTheBillingAddressOf(AddressInterface $address)
208
    {
209
        /** @var OrderInterface $order */
210
        $order = $this->sharedStorage->get('order');
211
212
        $order->setBillingAddress($address);
213
214
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
215
216
        $this->objectManager->flush();
217
    }
218
219
    /**
220
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
221
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
222
     */
223
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
224
    {
225
        $this->theCustomerAddressedItTo($address);
226
        $this->forTheBillingAddressOf(clone $address);
227
    }
228
229
    /**
230
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
231
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
232
     */
233
    public function theCustomerChoseShippingToWithPayment(
234
        ShippingMethodInterface $shippingMethod,
235
        AddressInterface $address,
236
        PaymentMethodInterface $paymentMethod
237
    ) {
238
        /** @var OrderInterface $order */
239
        $order = $this->sharedStorage->get('order');
240
241
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
242
243
        $this->objectManager->flush();
244
    }
245
246
    /**
247
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
248
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
249
     */
250
    public function theCustomerChoseShippingWithPayment(
251
        ShippingMethodInterface $shippingMethod,
252
        PaymentMethodInterface $paymentMethod
253
    ) {
254
        /** @var OrderInterface $order */
255
        $order = $this->sharedStorage->get('order');
256
257
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
258
259
        $this->objectManager->flush();
260
    }
261
262
    /**
263
     * @Given /^the customer chose ("[^"]+" shipping method)$/
264
     */
265
    public function theCustomerChoseShippingMethod(ShippingMethodInterface $shippingMethod) {
266
        /** @var OrderInterface $order */
267
        $order = $this->sharedStorage->get('order');
268
269
        foreach ($order->getShipments() as $shipment) {
270
            $shipment->setMethod($shippingMethod);
271
        }
272
273
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
274
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
275
        $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH)->apply(OrderPaymentTransitions::TRANSITION_PAY);
276
277
        $this->objectManager->flush();
278
    }
279
280
    /**
281
     * @Given the customer bought a single :product
282
     * @Given I bought a single :product
283
     */
284
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
285
    {
286
        $this->addProductVariantToOrder($this->variantResolver->getVariant($product), 1);
0 ignored issues
show
Compatibility introduced by
$this->variantResolver->getVariant($product) of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
287
288
        $this->objectManager->flush();
289
    }
290
291
    /**
292
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
293
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
294
     */
295
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
296
    {
297
        $this->theCustomerBoughtSingleProduct($product);
298
        $this->theCustomerBoughtSingleProduct($secondProduct);
299
    }
300
301
    /**
302
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
303
     */
304
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
305
    {
306
        $variant = $this->variantResolver->getVariant($product);
307
        $this->addProductVariantToOrder($variant, $quantity);
0 ignored issues
show
Compatibility introduced by
$variant of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
308
309
        $this->objectManager->flush();
310
    }
311
312
    /**
313
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
314
     */
315
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
316
    {
317
        $this->addProductVariantToOrder($variant, $quantity);
318
319
        $this->objectManager->flush();
320
    }
321
322
    /**
323
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
324
     */
325
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
326
    {
327
        $this->addProductVariantToOrder($productVariant);
328
329
        $this->objectManager->flush();
330
    }
331
332
    /**
333
     * @Given the customer bought a single :product using :coupon coupon
334
     * @Given I bought a single :product using :coupon coupon
335
     */
336
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
337
    {
338
        $order = $this->addProductVariantToOrder($this->variantResolver->getVariant($product));
0 ignored issues
show
Compatibility introduced by
$this->variantResolver->getVariant($product) of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
339
        $order->setPromotionCoupon($coupon);
340
341
        $this->objectManager->flush();
342
    }
343
344
    /**
345
     * @Given I used :coupon coupon
346
     */
347
    public function iUsedCoupon(PromotionCouponInterface $coupon)
348
    {
349
        $order = $this->sharedStorage->get('order');
350
        $order->setPromotionCoupon($coupon);
351
352
        $this->objectManager->flush();
353
    }
354
355
    /**
356
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" product), ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
357
     */
358
    public function iHaveAlreadyPlacedOrderNthTimes(
359
        UserInterface $user,
360
        $numberOfOrders,
361
        ProductInterface $product,
362
        ShippingMethodInterface $shippingMethod,
363
        AddressInterface $address,
364
        PaymentMethodInterface $paymentMethod
365
    ) {
366
        $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...
367
        for ($i = 0; $i < $numberOfOrders; $i++) {
368
            /** @var ProductVariantInterface $variant */
369
            $variant = $this->variantResolver->getVariant($product);
370
371
            /** @var ChannelPricingInterface $channelPricing */
372
            $channelPricing = $variant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
373
374
            /** @var \Sylius\Component\Order\Model\OrderItemInterface $item */
375
            $item = $this->orderItemFactory->createNew();
376
            $item->setVariant($variant);
377
            $item->setUnitPrice($channelPricing->getPrice());
378
379
            $this->itemQuantityModifier->modify($item, 1);
380
381
            $order = $this->createOrder($customer, '#00000'.$i);
382
            $order->addItem($item);
383
384
            $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
385
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
386
387
            $this->objectManager->persist($order);
388
            $this->sharedStorage->set('order', $order);
389
        }
390
391
        $this->objectManager->flush();
392
    }
393
394
    /**
395
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
396
     */
397
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
398
    {
399
        $order = $this->createOrder($customer, $number);
400
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
401
        $order->setState(OrderInterface::STATE_NEW);
402
403
        $this->orderRepository->add($order);
404
    }
405
406
    /**
407
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") on a (channel "[^"]+")$/
408
     */
409
    public function thisCustomerHasPlacedAnOrderOnAChannel(CustomerInterface $customer, $number, $channel)
410
    {
411
        $order = $this->createOrder($customer, $number, $channel);
412
        $order->setState(OrderInterface::STATE_NEW);
413
414
        $this->orderRepository->add($order);
415
    }
416
417
    /**
418
     * @Given /^(customer "[^"]+"|this customer) has(?:| also) placed (\d+) orders on the ("[^"]+" channel) in each buying (\d+) ("[^"]+" products?)$/
419
     */
420
    public function thisCustomerPlacedOrdersOnChannelBuyingProducts(
421
        CustomerInterface $customer,
422
        $orderCount,
423
        ChannelInterface $channel,
424
        $productCount,
425
        ProductInterface $product
426
    ) {
427
        for ($i = 0; $i < $orderCount; $i++) {
428
            $order = $this->createOrder($customer, uniqid('#'), $channel);
429
430
            $this->addProductVariantsToOrderWithChannelPrice(
431
                $order,
432
                $channel,
433
                $this->variantResolver->getVariant($product),
0 ignored issues
show
Compatibility introduced by
$this->variantResolver->getVariant($product) of type object<Sylius\Component\...roductVariantInterface> is not a sub-type of object<Sylius\Component\...roductVariantInterface>. It seems like you assume a child interface of the interface Sylius\Component\Product...ProductVariantInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
434
                $productCount
435
            );
436
437
            $order->setState(OrderInterface::STATE_NEW);
438
439
            $this->objectManager->persist($order);
440
        }
441
442
        $this->objectManager->flush();
443
    }
444
445
    /**
446
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
447
     */
448
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
449
    {
450
        $customers = $this->generateCustomers($numberOfCustomers);
451
452
        $sampleProductVariant = $this->sharedStorage->get('variant');
453
        $total = $this->getPriceFromString($total);
454
455
        for ($i = 0; $i < $numberOfCustomers; $i++) {
456
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
457
458
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
459
            $total -= $price;
460
461
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
462
463
            $this->objectManager->persist($order);
464
        }
465
466
        $this->objectManager->flush();
467
    }
468
469
    /**
470
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
471
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
472
     */
473
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
474
    {
475
        $customers = $this->generateCustomers($numberOfCustomers);
476
        $sampleProductVariant = $this->sharedStorage->get('variant');
477
        $total = $this->getPriceFromString($total);
478
479
        for ($i = 0; $i < $numberOfOrders; $i++) {
480
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
481
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
482
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
483
484
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
485
            $total -= $price;
486
487
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
488
489
            $this->objectManager->persist($order);
490
            $this->sharedStorage->set('order', $order);
491
        }
492
493
        $this->objectManager->flush();
494
    }
495
496
    /**
497
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
498
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
499
     */
500
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
501
    {
502
        $customers = $this->generateCustomers($numberOfCustomers);
503
        $sampleProductVariant = $product->getVariants()->first();
504
        $total = $this->getPriceFromString($total);
505
506
        for ($i = 0; $i < $numberOfOrders; $i++) {
507
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
508
            $order->setState(OrderInterface::STATE_NEW);
509
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
510
511
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
512
            $total -= $price;
513
514
            $this->addVariantWithPriceToOrder($order, $sampleProductVariant, $price);
515
516
            $this->objectManager->persist($order);
517
        }
518
519
        $this->objectManager->flush();
520
    }
521
522
    /**
523
     * @Given /^(this customer) has(?:| also) placed (an order "[^"]+") buying a single ("[^"]+" product) for ("[^"]+") on the ("[^"]+" channel)$/
524
     */
525
    public function customerHasPlacedAnOrderBuyingASingleProductForOnTheChannel(
526
        CustomerInterface $customer,
527
        $orderNumber,
528
        ProductInterface $product,
529
        $price,
530
        ChannelInterface $channel
531
    ) {
532
        $order = $this->createOrder($customer, $orderNumber, $channel);
533
        $order->setState(OrderInterface::STATE_NEW);
534
535
        $this->addVariantWithPriceToOrder($order, $product->getVariants()->first(), $price);
536
537
        $this->orderRepository->add($order);
538
    }
539
540
    /**
541
     * @Given /^(this order) is already paid$/
542
     * @Given the order :order is already paid
543
     */
544
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
545
    {
546
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
547
548
        $this->objectManager->flush();
549
    }
550
551
    /**
552
     * @Given /^(this order) has been refunded$/
553
     */
554
    public function thisOrderHasBeenRefunded(OrderInterface $order)
555
    {
556
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_REFUND);
557
558
        $this->objectManager->flush();
559
    }
560
561
    /**
562
     * @Given /^the customer cancelled (this order)$/
563
     * @Given /^(this order) was cancelled$/
564
     * @Given the order :order was cancelled
565
     * @Given /^I cancelled (this order)$/
566
     */
567
    public function theCustomerCancelledThisOrder(OrderInterface $order)
568
    {
569
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
570
571
        $this->objectManager->flush();
572
    }
573
574
    /**
575
     * @Given /^I cancelled my last order$/
576
     */
577
    public function theCustomerCancelledMyLastOrder()
578
    {
579
        $order = $this->sharedStorage->get('order');
580
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
581
582
        $this->objectManager->flush();
583
    }
584
585
    /**
586
     * @Given /^(this order) has already been shipped$/
587
     */
588
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
589
    {
590
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
591
592
        $this->objectManager->flush();
593
    }
594
595
    /**
596
     * @param OrderInterface $order
597
     * @param string $transition
598
     */
599
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
600
    {
601
        foreach ($order->getShipments() as $shipment) {
602
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
603
        }
604
    }
605
606
    /**
607
     * @param OrderInterface $order
608
     * @param string $transition
609
     */
610
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
611
    {
612
        foreach ($order->getPayments() as $payment) {
613
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
614
        }
615
    }
616
617
    /**
618
     * @param OrderInterface $order
619
     * @param string $transition
620
     */
621
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
622
    {
623
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
624
    }
625
626
    /**
627
     * @param ProductVariantInterface $productVariant
628
     * @param int $quantity
629
     *
630
     * @return OrderInterface
631
     */
632
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
633
    {
634
        $order = $this->sharedStorage->get('order');
635
636
        $this->addProductVariantsToOrderWithChannelPrice(
637
            $order,
638
            $this->sharedStorage->get('channel'),
639
            $productVariant,
640
            $quantity
641
        );
642
643
        return $order;
644
    }
645
646
    /**
647
     * @param OrderInterface $order
648
     * @param ChannelInterface $channel
649
     * @param ProductVariantInterface $productVariant
650
     * @param int $quantity
651
     */
652
    private function addProductVariantsToOrderWithChannelPrice(
653
        OrderInterface $order,
654
        ChannelInterface $channel,
655
        ProductVariantInterface $productVariant,
656
        $quantity = 1
657
    ) {
658
        /** @var OrderItemInterface $item */
659
        $item = $this->orderItemFactory->createNew();
660
        $item->setVariant($productVariant);
661
662
        /** @var ChannelPricingInterface $channelPricing */
663
        $channelPricing = $productVariant->getChannelPricingForChannel($channel);
664
        $item->setUnitPrice($channelPricing->getPrice());
665
666
        $this->itemQuantityModifier->modify($item, $quantity);
667
668
        $order->addItem($item);
669
    }
670
671
    /**
672
     * @param CustomerInterface $customer
673
     * @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...
674
     * @param ChannelInterface|null $channel
675
     * @param string|null $localeCode
676
     *
677
     * @return OrderInterface
678
     */
679
    private function createOrder(
680
        CustomerInterface $customer,
681
        $number = null,
682
        ChannelInterface $channel = null,
683
        $localeCode = null
684
    ) {
685
        $order = $this->createCart($customer, $channel, $localeCode);
686
687
        if (null !== $number) {
688
            $order->setNumber($number);
689
        }
690
691
        $order->completeCheckout();
692
693
        return $order;
694
    }
695
696
    /**
697
     * @param CustomerInterface $customer
698
     * @param ChannelInterface|null $channel
699
     * @param string|null $localeCode
700
     *
701
     * @return OrderInterface
702
     */
703
    private function createCart(
704
        CustomerInterface $customer,
705
        ChannelInterface $channel = null,
706
        $localeCode = null
707
    ) {
708
        /** @var OrderInterface $order */
709
        $order = $this->orderFactory->createNew();
710
711
        $order->setCustomer($customer);
712
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
713
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
714
        $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...
715
716
        return $order;
717
    }
718
719
    /**
720
     * @param int $count
721
     *
722
     * @return CustomerInterface[]
723
     */
724
    private function generateCustomers($count)
725
    {
726
        $customers = [];
727
728
        for ($i = 0; $i < $count; $i++) {
729
            $customer = $this->customerFactory->createNew();
730
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
731
            $customer->setFirstname('John');
732
            $customer->setLastname('Doe'.$i);
733
734
            $customers[] = $customer;
735
736
            $this->customerRepository->add($customer);
737
        }
738
739
        return $customers;
740
    }
741
742
    /**
743
     * @param string $price
744
     *
745
     * @return int
746
     */
747
    private function getPriceFromString($price)
748
    {
749
        return (int) round(str_replace(['€', '£', '$'], '', $price) * 100, 2);
750
    }
751
752
    /**
753
     * @param OrderInterface $order
754
     * @param ShippingMethodInterface $shippingMethod
755
     * @param AddressInterface $address
756
     * @param PaymentMethodInterface $paymentMethod
757
     */
758
    private function checkoutUsing(
759
        OrderInterface $order,
760
        ShippingMethodInterface $shippingMethod,
761
        AddressInterface $address,
762
        PaymentMethodInterface $paymentMethod
763
    ) {
764
        $order->setShippingAddress($address);
765
        $order->setBillingAddress(clone $address);
766
767
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
768
769
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
770
    }
771
772
    /**
773
     * @param OrderInterface $order
774
     * @param ShippingMethodInterface $shippingMethod
775
     * @param PaymentMethodInterface $paymentMethod
776
     */
777
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
778
    {
779
        foreach ($order->getShipments() as $shipment) {
780
            $shipment->setMethod($shippingMethod);
781
        }
782
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
783
784
        $payment = $order->getLastPayment(PaymentInterface::STATE_CART);
785
        $payment->setMethod($paymentMethod);
786
787
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
788
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
789
    }
790
791
    /**
792
     * @param OrderInterface $order
793
     * @param ProductVariantInterface $variant
794
     * @param int $price
795
     */
796
    private function addVariantWithPriceToOrder(OrderInterface $order, ProductVariantInterface $variant, $price)
797
    {
798
        $item = $this->orderItemFactory->createNew();
799
        $item->setVariant($variant);
800
        $item->setUnitPrice($price);
801
802
        $this->itemQuantityModifier->modify($item, 1);
803
804
        $order->addItem($item);
805
    }
806
}
807