Completed
Push — master ( 37aba0...533498 )
by Paweł
09:21
created

src/Sylius/Behat/Context/Setup/OrderContext.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Component\Core\Currency\CurrencyStorageInterface;
18
use Sylius\Component\Core\Model\ChannelInterface;
19
use Sylius\Component\Core\Model\ChannelPricingInterface;
20
use Sylius\Component\Core\Model\PromotionCouponInterface;
21
use Sylius\Component\Core\OrderCheckoutTransitions;
22
use Sylius\Component\Order\Processor\OrderProcessorInterface;
23
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
24
use Sylius\Component\Core\Model\AddressInterface;
25
use Sylius\Component\Core\Model\OrderInterface;
26
use Sylius\Component\Core\Model\OrderItemInterface;
27
use Sylius\Component\Core\Model\ProductInterface;
28
use Sylius\Component\Core\Model\ProductVariantInterface;
29
use Sylius\Component\Core\Model\ShippingMethodInterface;
30
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
31
use Sylius\Behat\Service\SharedStorageInterface;
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\Resource\Factory\FactoryInterface;
37
use Sylius\Component\Resource\Repository\RepositoryInterface;
38
use Sylius\Component\Shipping\ShipmentTransitions;
39
use Sylius\Component\Customer\Model\CustomerInterface;
40
use Sylius\Component\User\Model\UserInterface;
41
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
42
43
/**
44
 * @author Łukasz Chruściel <[email protected]>
45
 */
46
final class OrderContext implements Context
47
{
48
    /**
49
     * @var SharedStorageInterface
50
     */
51
    private $sharedStorage;
52
53
    /**
54
     * @var OrderRepositoryInterface
55
     */
56
    private $orderRepository;
57
58
    /**
59
     * @var FactoryInterface
60
     */
61
    private $orderFactory;
62
63
    /**
64
     * @var OrderProcessorInterface
65
     */
66
    private $orderProcessor;
67
68
    /**
69
     * @var FactoryInterface
70
     */
71
    private $orderItemFactory;
72
73
    /**
74
     * @var OrderItemQuantityModifierInterface
75
     */
76
    private $itemQuantityModifier;
77
78
    /**
79
     * @var RepositoryInterface
80
     */
81
    private $currencyRepository;
82
83
    /**
84
     * @var CurrencyStorageInterface
85
     */
86
    private $currencyStorage;
87
88
    /**
89
     * @var FactoryInterface
90
     */
91
    private $customerFactory;
92
93
    /**
94
     * @var RepositoryInterface
95
     */
96
    private $customerRepository;
97
98
    /**
99
     * @var ObjectManager
100
     */
101
    private $objectManager;
102
103
    /**
104
     * @var StateMachineFactoryInterface
105
     */
106
    private $stateMachineFactory;
107
108
    /**
109
     * @var ProductVariantResolverInterface
110
     */
111
    private $variantResolver;
112
113
    /**
114
     * @param SharedStorageInterface $sharedStorage
115
     * @param OrderRepositoryInterface $orderRepository
116
     * @param FactoryInterface $orderFactory
117
     * @param OrderProcessorInterface $orderProcessor
118
     * @param FactoryInterface $orderItemFactory
119
     * @param OrderItemQuantityModifierInterface $itemQuantityModifier
120
     * @param RepositoryInterface $currencyRepository
121
     * @param CurrencyStorageInterface $currencyStorage
122
     * @param FactoryInterface $customerFactory
123
     * @param RepositoryInterface $customerRepository
124
     * @param ObjectManager $objectManager
125
     * @param StateMachineFactoryInterface $stateMachineFactory
126
     * @param ProductVariantResolverInterface $variantResolver
127
     */
128
    public function __construct(
129
        SharedStorageInterface $sharedStorage,
130
        OrderRepositoryInterface $orderRepository,
131
        FactoryInterface $orderFactory,
132
        OrderProcessorInterface $orderProcessor,
133
        FactoryInterface $orderItemFactory,
134
        OrderItemQuantityModifierInterface $itemQuantityModifier,
135
        RepositoryInterface $currencyRepository,
136
        CurrencyStorageInterface $currencyStorage,
137
        FactoryInterface $customerFactory,
138
        RepositoryInterface $customerRepository,
139
        ObjectManager $objectManager,
140
        StateMachineFactoryInterface $stateMachineFactory,
141
        ProductVariantResolverInterface $variantResolver
142
    ) {
143
        $this->sharedStorage = $sharedStorage;
144
        $this->orderRepository = $orderRepository;
145
        $this->orderFactory = $orderFactory;
146
        $this->orderProcessor = $orderProcessor;
147
        $this->orderItemFactory = $orderItemFactory;
148
        $this->itemQuantityModifier = $itemQuantityModifier;
149
        $this->currencyRepository = $currencyRepository;
150
        $this->currencyStorage = $currencyStorage;
151
        $this->customerFactory = $customerFactory;
152
        $this->customerRepository = $customerRepository;
153
        $this->objectManager = $objectManager;
154
        $this->stateMachineFactory = $stateMachineFactory;
155
        $this->variantResolver = $variantResolver;
156
    }
157
158
    /**
159
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed an order$/
160
     * @Given /^there is (?:a|another) (customer "[^"]+") that placed (an order "[^"]+")$/
161
     * @Given a customer :customer placed an order :orderNumber
162
     * @Given the customer :customer has already placed an order :orderNumber
163
     */
164
    public function thereIsCustomerThatPlacedOrder(CustomerInterface $customer, $orderNumber = null)
165
    {
166
        $order = $this->createOrder($customer, $orderNumber);
167
168
        $this->sharedStorage->set('order', $order);
169
170
        $this->orderRepository->add($order);
171
    }
172
173
    /**
174
     * @Given a customer :customer added something to cart
175
     */
176
    public function customerStartedCheckout(CustomerInterface $customer)
177
    {
178
        $cart = $this->createCart($customer);
179
180
        $this->sharedStorage->set('cart', $cart);
181
182
        $this->orderRepository->add($cart);
183
    }
184
185
    /**
186
     * @Given /^(I) placed (an order "[^"]+")$/
187
     */
188
    public function iPlacedAnOrder(UserInterface $user, $orderNumber)
189
    {
190
        $customer = $user->getCustomer();
0 ignored issues
show
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...
191
        $order = $this->createOrder($customer, $orderNumber);
192
193
        $this->sharedStorage->set('order', $order);
194
195
        $this->orderRepository->add($order);
196
    }
197
198
    /**
199
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
200
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+"(?:|, "[^"]+"))$/
201
     */
202
    public function theCustomerAddressedItTo(AddressInterface $address)
203
    {
204
        /** @var OrderInterface $order */
205
        $order = $this->sharedStorage->get('order');
206
        $order->setShippingAddress($address);
207
208
        $this->objectManager->flush();
209
    }
210
211
    /**
212
     * @Given /^the customer set the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)")$/
213
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "[^"]+", "[^"]+")$/
214
     * @Given /^for the billing address (of "[^"]+" in the "[^"]+", "[^"]+" "([^"]+)", "[^"]+", "[^"]+")$/
215
     */
216
    public function forTheBillingAddressOf(AddressInterface $address)
217
    {
218
        /** @var OrderInterface $order */
219
        $order = $this->sharedStorage->get('order');
220
221
        $order->setBillingAddress($address);
222
223
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
224
225
        $this->objectManager->flush();
226
    }
227
228
    /**
229
     * @Given /^the customer ("[^"]+" addressed it to "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
230
     * @Given /^I (addressed it to "[^"]+", "[^"]+", "[^"]+" "[^"]+" in the "[^"]+") with identical billing address$/
231
     */
232
    public function theCustomerAddressedItToWithIdenticalBillingAddress(AddressInterface $address)
233
    {
234
        $this->theCustomerAddressedItTo($address);
235
        $this->forTheBillingAddressOf($address);
236
    }
237
238
    /**
239
     * @Given /^the customer chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
240
     * @Given /^I chose ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
241
     */
242
    public function theCustomerChoseShippingToWithPayment(
243
        ShippingMethodInterface $shippingMethod,
244
        AddressInterface $address,
245
        PaymentMethodInterface $paymentMethod
246
    ) {
247
        /** @var OrderInterface $order */
248
        $order = $this->sharedStorage->get('order');
249
250
        $this->checkoutUsing($order, $shippingMethod, $address, $paymentMethod);
251
252
        $this->objectManager->flush();
253
    }
254
255
    /**
256
     * @Given /^the customer chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
257
     * @Given /^I chose ("[^"]+" shipping method) with ("[^"]+" payment)$/
258
     */
259
    public function theCustomerChoseShippingWithPayment(
260
        ShippingMethodInterface $shippingMethod,
261
        PaymentMethodInterface $paymentMethod
262
    ) {
263
        /** @var OrderInterface $order */
264
        $order = $this->sharedStorage->get('order');
265
266
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
267
268
        $this->objectManager->flush();
269
    }
270
271
    /**
272
     * @Given the customer bought a single :product
273
     * @Given I bought a single :product
274
     */
275
    public function theCustomerBoughtSingleProduct(ProductInterface $product)
276
    {
277
        $this->addProductVariantToOrder($this->variantResolver->getVariant($product), 1);
0 ignored issues
show
$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...
278
279
        $this->objectManager->flush();
280
    }
281
282
    /**
283
     * @Given /^the customer bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
284
     * @Given /^I bought ((?:a|an) "[^"]+") and ((?:a|an) "[^"]+")$/
285
     */
286
    public function theCustomerBoughtProductAndProduct(ProductInterface $product, ProductInterface $secondProduct)
287
    {
288
        $this->theCustomerBoughtSingleProduct($product);
289
        $this->theCustomerBoughtSingleProduct($secondProduct);
290
    }
291
292
    /**
293
     * @Given /^the customer bought (\d+) ("[^"]+" products)$/
294
     */
295
    public function theCustomerBoughtSeveralProducts($quantity, ProductInterface $product)
296
    {
297
        $variant = $this->variantResolver->getVariant($product);
298
        $this->addProductVariantToOrder($variant, $quantity);
0 ignored issues
show
$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...
299
300
        $this->objectManager->flush();
301
    }
302
303
    /**
304
     * @Given /^the customer bought ([^"]+) units of ("[^"]+" variant of product "[^"]+")$/
305
     */
306
    public function theCustomerBoughtSeveralVariantsOfProduct($quantity, ProductVariantInterface $variant)
307
    {
308
        $this->addProductVariantToOrder($variant, $quantity);
309
310
        $this->objectManager->flush();
311
    }
312
313
    /**
314
     * @Given /^the customer bought a single ("[^"]+" variant of product "[^"]+")$/
315
     */
316
    public function theCustomerBoughtSingleProductVariant(ProductVariantInterface $productVariant)
317
    {
318
        $this->addProductVariantToOrder($productVariant);
319
320
        $this->objectManager->flush();
321
    }
322
323
    /**
324
     * @Given the customer bought a single :product using :coupon coupon
325
     * @Given I bought a single :product using :coupon coupon
326
     */
327
    public function theCustomerBoughtSingleUsing(ProductInterface $product, PromotionCouponInterface $coupon)
328
    {
329
        $order = $this->addProductVariantToOrder($this->variantResolver->getVariant($product));
0 ignored issues
show
$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...
330
        $order->setPromotionCoupon($coupon);
331
332
        $this->objectManager->flush();
333
    }
334
335
    /**
336
     * @Given I used :coupon coupon
337
     */
338
    public function iUsedCoupon(PromotionCouponInterface $coupon)
339
    {
340
        $order = $this->sharedStorage->get('order');
341
        $order->setPromotionCoupon($coupon);
342
343
        $this->objectManager->flush();
344
    }
345
346
    /**
347
     * @Given /^(I) have already placed (\d+) orders choosing ("[^"]+" shipping method) (to "[^"]+") with ("[^"]+" payment)$/
348
     */
349
    public function iHaveAlreadyPlacedOrderNthTimes(
350
        UserInterface $user,
351
        $numberOfOrders,
352
        ShippingMethodInterface $shippingMethod,
353
        AddressInterface $address,
354
        PaymentMethodInterface $paymentMethod
355
    ) {
356
        $customer = $user->getCustomer();
0 ignored issues
show
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...
357
        for ($i = 0; $i < $numberOfOrders; $i++) {
358
            $order = $this->createOrder($customer, '#00000'.$i);
359
            $this->checkoutUsing($order, $shippingMethod, clone $address, $paymentMethod);
360
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
361
362
            $this->orderRepository->add($order);
363
            $this->sharedStorage->set('order', $order);
364
        }
365
    }
366
367
    /**
368
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
369
     */
370
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
371
    {
372
        $customers = $this->generateCustomers($numberOfCustomers);
373
374
        $sampleProductVariant = $this->sharedStorage->get('variant');
375
        $total = $this->getPriceFromString($total);
376
377
        for ($i = 0; $i < $numberOfCustomers; $i++) {
378
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
379
380
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
381
            $total -= $price;
382
383
            $item = $this->orderItemFactory->createNew();
384
            $item->setVariant($sampleProductVariant);
385
            $item->setUnitPrice($price);
386
387
            $this->itemQuantityModifier->modify($item, 1);
388
389
            $order->addItem($item);
390
391
            $this->orderRepository->add($order);
392
        }
393
    }
394
395
    /**
396
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
397
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
398
     */
399
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
400
    {
401
        $customers = $this->generateCustomers($numberOfCustomers);
402
        $sampleProductVariant = $this->sharedStorage->get('variant');
403
        $total = $this->getPriceFromString($total);
404
405
        for ($i = 0; $i < $numberOfOrders; $i++) {
406
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
407
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
408
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
409
410
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
411
            $total -= $price;
412
413
            $item = $this->orderItemFactory->createNew();
414
            $item->setVariant($sampleProductVariant);
415
            $item->setUnitPrice($price);
416
417
            $this->itemQuantityModifier->modify($item, 1);
418
419
            $order->addItem($item);
420
421
            $this->orderRepository->add($order);
422
            $this->sharedStorage->set('order', $order);
423
        }
424
    }
425
426
    /**
427
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
428
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
429
     */
430
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
431
    {
432
        $customers = $this->generateCustomers($numberOfCustomers);
433
        $sampleProductVariant = $product->getVariants()->first();
434
        $total = $this->getPriceFromString($total);
435
436
        for ($i = 0; $i < $numberOfOrders; $i++) {
437
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
438
            $order->setState(OrderInterface::STATE_NEW);
439
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
440
441
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
442
            $total -= $price;
443
444
            $item = $this->orderItemFactory->createNew();
445
            $item->setVariant($sampleProductVariant);
446
            $item->setUnitPrice($price);
447
448
            $this->itemQuantityModifier->modify($item, 1);
449
450
            $order->addItem($item);
451
452
            $this->orderRepository->add($order);
453
        }
454
    }
455
456
    /**
457
     * @Given /^(this order) is already paid$/
458
     * @Given the order :order is already paid
459
     */
460
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
461
    {
462
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
463
464
        $this->objectManager->flush();
465
    }
466
467
    /**
468
     * @Given /^the customer cancelled (this order)$/
469
     * @Given /^(this order) was cancelled$/
470
     * @Given the order :order was cancelled
471
     * @Given /^I cancelled (this order)$/
472
     */
473
    public function theCustomerCancelledThisOrder(OrderInterface $order)
474
    {
475
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
476
477
        $this->objectManager->flush();
478
    }
479
480
    /**
481
     * @Given /^I cancelled my last order$/
482
     */
483
    public function theCustomerCancelledMyLastOrder()
484
    {
485
        $order = $this->sharedStorage->get('order');
486
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
487
488
        $this->objectManager->flush();
489
    }
490
491
    /**
492
     * @Given /^(this order) has already been shipped$/
493
     */
494
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
495
    {
496
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
497
498
        $this->objectManager->flush();
499
    }
500
501
    /**
502
     * @param OrderInterface $order
503
     * @param string $transition
504
     */
505
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
506
    {
507
        foreach ($order->getShipments() as $shipment) {
508
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
509
        }
510
    }
511
512
    /**
513
     * @param OrderInterface $order
514
     * @param string $transition
515
     */
516
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
517
    {
518
        foreach ($order->getPayments() as $payment) {
519
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
520
        }
521
    }
522
523
    /**
524
     * @param OrderInterface $order
525
     * @param string $transition
526
     */
527
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
528
    {
529
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
530
    }
531
532
    /**
533
     * @param ProductVariantInterface $productVariant
534
     * @param int $quantity
535
     *
536
     * @return OrderInterface
537
     */
538
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
539
    {
540
        $order = $this->sharedStorage->get('order');
541
542
        /** @var OrderItemInterface $item */
543
        $item = $this->orderItemFactory->createNew();
544
        $item->setVariant($productVariant);
545
546
        /** @var ChannelPricingInterface $channelPricing */
547
        $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
548
        $item->setUnitPrice($channelPricing->getPrice());
549
550
        $this->itemQuantityModifier->modify($item, $quantity);
551
552
        $order->addItem($item);
553
554
        return $order;
555
    }
556
557
    /**
558
     * @param CustomerInterface $customer
559
     * @param string $number
560
     * @param ChannelInterface|null $channel
561
     * @param string|null $currencyCode
562
     * @param string|null $localeCode
563
     *
564
     * @return OrderInterface
565
     */
566
    private function createOrder(
567
        CustomerInterface $customer,
568
        $number = null,
569
        ChannelInterface $channel = null,
570
        $currencyCode = null,
571
        $localeCode = null
572
    ) {
573
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
574
575
        if (null !== $number) {
576
            $order->setNumber($number);
577
        }
578
579
        $order->completeCheckout();
580
581
        return $order;
582
    }
583
584
    /**
585
     * @param CustomerInterface $customer
586
     * @param ChannelInterface|null $channel
587
     * @param string|null $currencyCode
588
     * @param string|null $localeCode
589
     *
590
     * @return OrderInterface
591
     */
592
    private function createCart(
593
        CustomerInterface $customer,
594
        ChannelInterface $channel = null,
595
        $currencyCode = null,
596
        $localeCode = null
597
    ) {
598
        /** @var OrderInterface $order */
599
        $order = $this->orderFactory->createNew();
600
601
        $order->setCustomer($customer);
602
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
603
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
604
605
        $currencyCode = $currencyCode ? $currencyCode : $order->getChannel()->getBaseCurrency()->getCode();
0 ignored issues
show
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...
606
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
607
608
        $order->setCurrencyCode($currency->getCode());
609
610
        return $order;
611
    }
612
613
    /**
614
     * @param int $count
615
     *
616
     * @return CustomerInterface[]
617
     */
618
    private function generateCustomers($count)
619
    {
620
        $customers = [];
621
622
        for ($i = 0; $i < $count; $i++) {
623
            $customer = $this->customerFactory->createNew();
624
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
625
            $customer->setFirstname('John');
626
            $customer->setLastname('Doe'.$i);
627
628
            $customers[] = $customer;
629
630
            $this->customerRepository->add($customer);
631
        }
632
633
        return $customers;
634
    }
635
636
    /**
637
     * @param string $price
638
     *
639
     * @return int
640
     */
641
    private function getPriceFromString($price)
642
    {
643
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
644
    }
645
646
    /**
647
     * @param OrderInterface $order
648
     * @param ShippingMethodInterface $shippingMethod
649
     * @param AddressInterface $address
650
     * @param PaymentMethodInterface $paymentMethod
651
     */
652
    private function checkoutUsing(
653
        OrderInterface $order,
654
        ShippingMethodInterface $shippingMethod,
655
        AddressInterface $address,
656
        PaymentMethodInterface $paymentMethod
657
    ) {
658
        $order->setShippingAddress($address);
659
        $order->setBillingAddress(clone $address);
660
661
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
662
663
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
664
    }
665
666
    /**
667
     * @param OrderInterface $order
668
     * @param ShippingMethodInterface $shippingMethod
669
     * @param PaymentMethodInterface $paymentMethod
670
     */
671
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
672
    {
673
        foreach ($order->getShipments() as $shipment) {
674
            $shipment->setMethod($shippingMethod);
675
        }
676
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
677
678
        $payment = $order->getLastNewPayment();
679
        $payment->setMethod($paymentMethod);
680
681
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
682
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
683
    }
684
}
685