Completed
Push — master ( 97ef27...8cdac9 )
by Kamil
17:01 queued 07:38
created

OrderShipmentProcessor::processShipmentUnits()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 6
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Component\Core\OrderProcessing;
15
16
use Sylius\Component\Core\Model\OrderInterface;
17
use Sylius\Component\Core\Model\ShipmentInterface;
18
use Sylius\Component\Order\Model\OrderInterface as BaseOrderInterface;
19
use Sylius\Component\Order\Processor\OrderProcessorInterface;
20
use Sylius\Component\Resource\Factory\FactoryInterface;
21
use Sylius\Component\Shipping\Exception\UnresolvedDefaultShippingMethodException;
22
use Sylius\Component\Shipping\Resolver\DefaultShippingMethodResolverInterface;
23
use Sylius\Component\Shipping\Resolver\ShippingMethodsResolverInterface;
24
use Webmozart\Assert\Assert;
25
26
final class OrderShipmentProcessor implements OrderProcessorInterface
27
{
28
    /**
29
     * @var DefaultShippingMethodResolverInterface
30
     */
31
    private $defaultShippingMethodResolver;
32
33
    /**
34
     * @var FactoryInterface
35
     */
36
    private $shipmentFactory;
37
38
    /**
39
     * @var ShippingMethodsResolverInterface|null
40
     */
41
    private $shippingMethodsResolver;
42
43
    /**
44
     * @param DefaultShippingMethodResolverInterface $defaultShippingMethodResolver
45
     * @param FactoryInterface $shipmentFactory
46
     * @param ShippingMethodsResolverInterface|null $shippingMethodsResolver
47
     */
48
    public function __construct(
49
        DefaultShippingMethodResolverInterface $defaultShippingMethodResolver,
50
        FactoryInterface $shipmentFactory,
51
        ?ShippingMethodsResolverInterface $shippingMethodsResolver = null
52
    ) {
53
        $this->defaultShippingMethodResolver = $defaultShippingMethodResolver;
54
        $this->shipmentFactory = $shipmentFactory;
55
        $this->shippingMethodsResolver = $shippingMethodsResolver;
56
57
        if (2 === func_num_args() || null === $shippingMethodsResolver) {
58
            @trigger_error(
59
                'Not passing ShippingMethodsResolverInterface explicitly is deprecated since 1.2 and will be prohibited in 2.0',
60
                E_USER_DEPRECATED
61
            );
62
        }
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function process(BaseOrderInterface $order): void
69
    {
70
        /** @var OrderInterface $order */
71
        Assert::isInstanceOf($order, OrderInterface::class);
72
73
        if ($order->isEmpty() || !$order->isShippingRequired()) {
74
            $order->removeShipments();
75
76
            return;
77
        }
78
79
        if ($order->hasShipments()) {
80
            $shipment = $this->getExistingShipmentWithProperMethod($order);
81
82
            if (null === $shipment) {
83
                return;
84
            }
85
86
            $this->processShipmentUnits($order, $shipment);
87
88
            return;
89
        }
90
91
        $this->createNewOrderShipment($order);
92
    }
93
94
    /**
95
     * @param OrderInterface $order
96
     */
97
    private function createNewOrderShipment(OrderInterface $order): void
98
    {
99
        try {
100
            /** @var ShipmentInterface $shipment */
101
            $shipment = $this->shipmentFactory->createNew();
102
            $shipment->setOrder($order);
103
104
            $this->processShipmentUnits($order, $shipment);
105
106
            $shipment->setMethod($this->defaultShippingMethodResolver->getDefaultShippingMethod($shipment));
107
108
            $order->addShipment($shipment);
109
        } catch (UnresolvedDefaultShippingMethodException $exception) {
110
            foreach ($shipment->getUnits() as $unit) {
111
                $shipment->removeUnit($unit);
112
            }
113
        }
114
    }
115
116
    /**
117
     * @param BaseOrderInterface $order
118
     * @param ShipmentInterface $shipment
119
     */
120
    private function processShipmentUnits(BaseOrderInterface $order, ShipmentInterface $shipment): void
121
    {
122
        foreach ($shipment->getUnits() as $unit) {
123
            $shipment->removeUnit($unit);
124
        }
125
126
        foreach ($order->getItemUnits() as $itemUnit) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Order\Model\OrderInterface as the method getItemUnits() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\Order.

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...
127
            if (null === $itemUnit->getShipment()) {
128
                $shipment->addUnit($itemUnit);
129
            }
130
        }
131
    }
132
    /**
133
     * @param OrderInterface $order
134
     *
135
     * @return ShipmentInterface|null
136
     */
137
    private function getExistingShipmentWithProperMethod(OrderInterface $order): ?ShipmentInterface
138
    {
139
        /** @var ShipmentInterface $shipment */
140
        $shipment = $order->getShipments()->first();
141
142
        if (null === $this->shippingMethodsResolver) {
143
            return $shipment;
144
        }
145
146
        if (!in_array($shipment->getMethod(), $this->shippingMethodsResolver->getSupportedMethods($shipment), true)) {
147
            try {
148
                $shipment->setMethod($this->defaultShippingMethodResolver->getDefaultShippingMethod($shipment));
149
            } catch (UnresolvedDefaultShippingMethodException $exception) {
150
                return null;
151
            }
152
        }
153
154
        return $shipment;
155
    }
156
}
157