Completed
Push — master ( 0a1951...1a638f )
by Paweł
22:32 queued 13:24
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 /^(this customer) has(?:| also) placed (an order "[^"]+") at "([^"]+)"$/
369
     */
370
    public function thisCustomerHasPlacedAnOrderAtDate(CustomerInterface $customer, $number, $checkoutCompletedAt)
371
    {
372
        $order = $this->createOrder($customer, $number);
373
        $order->setCheckoutCompletedAt(new \DateTime($checkoutCompletedAt));
374
        $order->setState(OrderInterface::STATE_NEW);
375
376
        $this->orderRepository->add($order);
377
    }
378
379
    /**
380
     * @Given :numberOfCustomers customers have added products to the cart for total of :total
381
     */
382
    public function customersHaveAddedProductsToTheCartForTotalOf($numberOfCustomers, $total)
383
    {
384
        $customers = $this->generateCustomers($numberOfCustomers);
385
386
        $sampleProductVariant = $this->sharedStorage->get('variant');
387
        $total = $this->getPriceFromString($total);
388
389
        for ($i = 0; $i < $numberOfCustomers; $i++) {
390
            $order = $this->createCart($customers[rand(0, $numberOfCustomers - 1)]);
391
392
            $price = $i === ($numberOfCustomers - 1) ? $total : rand(1, $total);
393
            $total -= $price;
394
395
            $item = $this->orderItemFactory->createNew();
396
            $item->setVariant($sampleProductVariant);
397
            $item->setUnitPrice($price);
398
399
            $this->itemQuantityModifier->modify($item, 1);
400
401
            $order->addItem($item);
402
403
            $this->orderRepository->add($order);
404
        }
405
    }
406
407
    /**
408
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total
409
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total
410
     */
411
    public function customersHavePlacedOrdersForTotalOf($numberOfCustomers, $numberOfOrders, $total)
412
    {
413
        $customers = $this->generateCustomers($numberOfCustomers);
414
        $sampleProductVariant = $this->sharedStorage->get('variant');
415
        $total = $this->getPriceFromString($total);
416
417
        for ($i = 0; $i < $numberOfOrders; $i++) {
418
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid());
419
            $order->setState(OrderInterface::STATE_NEW); // Temporary, we should use checkout to place these orders.
420
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
421
422
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
423
            $total -= $price;
424
425
            $item = $this->orderItemFactory->createNew();
426
            $item->setVariant($sampleProductVariant);
427
            $item->setUnitPrice($price);
428
429
            $this->itemQuantityModifier->modify($item, 1);
430
431
            $order->addItem($item);
432
433
            $this->orderRepository->add($order);
434
            $this->sharedStorage->set('order', $order);
435
        }
436
    }
437
438
    /**
439
     * @Given :numberOfCustomers customers have placed :numberOfOrders orders for total of :total mostly :product product
440
     * @Given then :numberOfCustomers more customers have placed :numberOfOrders orders for total of :total mostly :product product
441
     */
442
    public function customersHavePlacedOrdersForTotalOfMostlyProduct($numberOfCustomers, $numberOfOrders, $total, ProductInterface $product)
443
    {
444
        $customers = $this->generateCustomers($numberOfCustomers);
445
        $sampleProductVariant = $product->getVariants()->first();
446
        $total = $this->getPriceFromString($total);
447
448
        for ($i = 0; $i < $numberOfOrders; $i++) {
449
            $order = $this->createOrder($customers[rand(0, $numberOfCustomers - 1)], '#'.uniqid(), $product->getChannels()->first());
450
            $order->setState(OrderInterface::STATE_NEW);
451
            $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
452
453
            $price = $i === ($numberOfOrders - 1) ? $total : rand(1, $total);
454
            $total -= $price;
455
456
            $item = $this->orderItemFactory->createNew();
457
            $item->setVariant($sampleProductVariant);
458
            $item->setUnitPrice($price);
459
460
            $this->itemQuantityModifier->modify($item, 1);
461
462
            $order->addItem($item);
463
464
            $this->orderRepository->add($order);
465
        }
466
    }
467
468
    /**
469
     * @Given /^(this order) is already paid$/
470
     * @Given the order :order is already paid
471
     */
472
    public function thisOrderIsAlreadyPaid(OrderInterface $order)
473
    {
474
        $this->applyPaymentTransitionOnOrder($order, PaymentTransitions::TRANSITION_COMPLETE);
475
476
        $this->objectManager->flush();
477
    }
478
479
    /**
480
     * @Given /^the customer cancelled (this order)$/
481
     * @Given /^(this order) was cancelled$/
482
     * @Given the order :order was cancelled
483
     * @Given /^I cancelled (this order)$/
484
     */
485
    public function theCustomerCancelledThisOrder(OrderInterface $order)
486
    {
487
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
488
489
        $this->objectManager->flush();
490
    }
491
492
    /**
493
     * @Given /^I cancelled my last order$/
494
     */
495
    public function theCustomerCancelledMyLastOrder()
496
    {
497
        $order = $this->sharedStorage->get('order');
498
        $this->stateMachineFactory->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::TRANSITION_CANCEL);
499
500
        $this->objectManager->flush();
501
    }
502
503
    /**
504
     * @Given /^(this order) has already been shipped$/
505
     */
506
    public function thisOrderHasAlreadyBeenShipped(OrderInterface $order)
507
    {
508
        $this->applyShipmentTransitionOnOrder($order, ShipmentTransitions::TRANSITION_SHIP);
509
510
        $this->objectManager->flush();
511
    }
512
513
    /**
514
     * @param OrderInterface $order
515
     * @param string $transition
516
     */
517
    private function applyShipmentTransitionOnOrder(OrderInterface $order, $transition)
518
    {
519
        foreach ($order->getShipments() as $shipment) {
520
            $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH)->apply($transition);
521
        }
522
    }
523
524
    /**
525
     * @param OrderInterface $order
526
     * @param string $transition
527
     */
528
    private function applyPaymentTransitionOnOrder(OrderInterface $order, $transition)
529
    {
530
        foreach ($order->getPayments() as $payment) {
531
            $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->apply($transition);
532
        }
533
    }
534
535
    /**
536
     * @param OrderInterface $order
537
     * @param string $transition
538
     */
539
    private function applyTransitionOnOrderCheckout(OrderInterface $order, $transition)
540
    {
541
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
542
    }
543
544
    /**
545
     * @param ProductVariantInterface $productVariant
546
     * @param int $quantity
547
     *
548
     * @return OrderInterface
549
     */
550
    private function addProductVariantToOrder(ProductVariantInterface $productVariant, $quantity = 1)
551
    {
552
        $order = $this->sharedStorage->get('order');
553
554
        /** @var OrderItemInterface $item */
555
        $item = $this->orderItemFactory->createNew();
556
        $item->setVariant($productVariant);
557
558
        /** @var ChannelPricingInterface $channelPricing */
559
        $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
560
        $item->setUnitPrice($channelPricing->getPrice());
561
562
        $this->itemQuantityModifier->modify($item, $quantity);
563
564
        $order->addItem($item);
565
566
        return $order;
567
    }
568
569
    /**
570
     * @param CustomerInterface $customer
571
     * @param string $number
572
     * @param ChannelInterface|null $channel
573
     * @param string|null $currencyCode
574
     * @param string|null $localeCode
575
     *
576
     * @return OrderInterface
577
     */
578
    private function createOrder(
579
        CustomerInterface $customer,
580
        $number = null,
581
        ChannelInterface $channel = null,
582
        $currencyCode = null,
583
        $localeCode = null
584
    ) {
585
        $order = $this->createCart($customer, $channel, $currencyCode, $localeCode);
586
587
        if (null !== $number) {
588
            $order->setNumber($number);
589
        }
590
591
        $order->completeCheckout();
592
593
        return $order;
594
    }
595
596
    /**
597
     * @param CustomerInterface $customer
598
     * @param ChannelInterface|null $channel
599
     * @param string|null $currencyCode
600
     * @param string|null $localeCode
601
     *
602
     * @return OrderInterface
603
     */
604
    private function createCart(
605
        CustomerInterface $customer,
606
        ChannelInterface $channel = null,
607
        $currencyCode = null,
608
        $localeCode = null
609
    ) {
610
        /** @var OrderInterface $order */
611
        $order = $this->orderFactory->createNew();
612
613
        $order->setCustomer($customer);
614
        $order->setChannel((null !== $channel) ? $channel : $this->sharedStorage->get('channel'));
615
        $order->setLocaleCode((null !== $localeCode) ? $localeCode : $this->sharedStorage->get('locale')->getCode());
616
617
        $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...
618
        $currency = $this->currencyRepository->findOneBy(['code' => $currencyCode]);
619
620
        $order->setCurrencyCode($currency->getCode());
621
622
        return $order;
623
    }
624
625
    /**
626
     * @param int $count
627
     *
628
     * @return CustomerInterface[]
629
     */
630
    private function generateCustomers($count)
631
    {
632
        $customers = [];
633
634
        for ($i = 0; $i < $count; $i++) {
635
            $customer = $this->customerFactory->createNew();
636
            $customer->setEmail(sprintf('john%[email protected]', uniqid()));
637
            $customer->setFirstname('John');
638
            $customer->setLastname('Doe'.$i);
639
640
            $customers[] = $customer;
641
642
            $this->customerRepository->add($customer);
643
        }
644
645
        return $customers;
646
    }
647
648
    /**
649
     * @param string $price
650
     *
651
     * @return int
652
     */
653
    private function getPriceFromString($price)
654
    {
655
        return (int) round((str_replace(['€', '£', '$'], '', $price) * 100), 2);
656
    }
657
658
    /**
659
     * @param OrderInterface $order
660
     * @param ShippingMethodInterface $shippingMethod
661
     * @param AddressInterface $address
662
     * @param PaymentMethodInterface $paymentMethod
663
     */
664
    private function checkoutUsing(
665
        OrderInterface $order,
666
        ShippingMethodInterface $shippingMethod,
667
        AddressInterface $address,
668
        PaymentMethodInterface $paymentMethod
669
    ) {
670
        $order->setShippingAddress($address);
671
        $order->setBillingAddress(clone $address);
672
673
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
674
675
        $this->proceedSelectingShippingAndPaymentMethod($order, $shippingMethod, $paymentMethod);
676
    }
677
678
    /**
679
     * @param OrderInterface $order
680
     * @param ShippingMethodInterface $shippingMethod
681
     * @param PaymentMethodInterface $paymentMethod
682
     */
683
    private function proceedSelectingShippingAndPaymentMethod(OrderInterface $order, ShippingMethodInterface $shippingMethod, PaymentMethodInterface $paymentMethod)
684
    {
685
        foreach ($order->getShipments() as $shipment) {
686
            $shipment->setMethod($shippingMethod);
687
        }
688
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
689
690
        $payment = $order->getLastNewPayment();
691
        $payment->setMethod($paymentMethod);
692
693
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
694
        $this->applyTransitionOnOrderCheckout($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
695
    }
696
}
697