Completed
Push — symfony3 ( 405d0c...88ded0 )
by Kamil
32:03 queued 12:32
created

OrderFixture   B

Complexity

Total Complexity 16

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 17
dl 0
loc 271
rs 7.8571
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 1
B load() 0 36 3
A getName() 0 4 1
A configureOptionsNode() 0 7 1
A generateItems() 0 17 2
A address() 0 16 1
A selectShipping() 0 15 2
A selectPayment() 0 15 2
A completeCheckout() 0 8 2
A applyCheckoutStateTransition() 0 4 1
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\Bundle\CoreBundle\Fixture;
13
14
use Doctrine\Common\Persistence\ObjectManager;
15
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
16
use Sylius\Bundle\FixturesBundle\Fixture\AbstractFixture;
17
use Sylius\Component\Core\Model\AddressInterface;
18
use Sylius\Component\Core\Model\OrderInterface;
19
use Sylius\Component\Core\OrderCheckoutTransitions;
20
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
21
use Sylius\Component\Core\Repository\ShippingMethodRepositoryInterface;
22
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
23
use Sylius\Component\Resource\Factory\FactoryInterface;
24
use Sylius\Component\Resource\Repository\RepositoryInterface;
25
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
26
use Webmozart\Assert\Assert;
27
28
/**
29
 * @author Paweł Jędrzejewski <[email protected]>
30
 */
31
final class OrderFixture extends AbstractFixture
32
{
33
    /**
34
     * @var FactoryInterface
35
     */
36
    private $orderFactory;
37
38
    /**
39
     * @var FactoryInterface
40
     */
41
    private $orderItemFactory;
42
43
    /**
44
     * @var OrderItemQuantityModifierInterface
45
     */
46
    private $orderItemQuantityModifier;
47
48
    /**
49
     * @var ObjectManager
50
     */
51
    private $orderManager;
52
53
    /**
54
     * @var RepositoryInterface
55
     */
56
    private $channelRepository;
57
58
    /**
59
     * @var RepositoryInterface
60
     */
61
    private $customerRepository;
62
63
    /**
64
     * @var RepositoryInterface
65
     */
66
    private $productRepository;
67
68
    /**
69
     * @var RepositoryInterface
70
     */
71
    private $countryRepository;
72
73
    /**
74
     * @var PaymentMethodRepositoryInterface
75
     */
76
    private $paymentMethodRepository;
77
78
    /**
79
     * @var ShippingMethodRepositoryInterface
80
     */
81
    private $shippingMethodRepository;
82
83
    /**
84
     * @var FactoryInterface
85
     */
86
    private $addressFactory;
87
88
    /**
89
     * @var StateMachineFactoryInterface
90
     */
91
    private $stateMachineFactory;
92
93
    /**
94
     * @var \Faker\Generator
95
     */
96
    private $faker;
97
98
    /**
99
     * @param FactoryInterface $orderFactory
100
     * @param FactoryInterface $orderItemFactory
101
     * @param OrderItemQuantityModifierInterface $orderItemQuantityModifier
102
     * @param ObjectManager $orderManager
103
     * @param RepositoryInterface $channelRepository
104
     * @param RepositoryInterface $customerRepository
105
     * @param RepositoryInterface $productRepository
106
     * @param RepositoryInterface $countryRepository
107
     * @param PaymentMethodRepositoryInterface $paymentMethodRepository
108
     * @param ShippingMethodRepositoryInterface $shippingMethodRepository
109
     * @param FactoryInterface $addressFactory
110
     * @param StateMachineFactoryInterface $stateMachineFactory
111
     */
112
    public function __construct(
113
        FactoryInterface $orderFactory,
114
        FactoryInterface $orderItemFactory,
115
        OrderItemQuantityModifierInterface $orderItemQuantityModifier,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $orderItemQuantityModifier exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
116
        ObjectManager $orderManager,
117
        RepositoryInterface $channelRepository,
118
        RepositoryInterface $customerRepository,
119
        RepositoryInterface $productRepository,
120
        RepositoryInterface $countryRepository,
121
        PaymentMethodRepositoryInterface $paymentMethodRepository,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $paymentMethodRepository exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
122
        ShippingMethodRepositoryInterface $shippingMethodRepository,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $shippingMethodRepository exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
123
        FactoryInterface $addressFactory,
124
        StateMachineFactoryInterface $stateMachineFactory
125
    ) {
126
        $this->orderFactory = $orderFactory;
127
        $this->orderItemFactory = $orderItemFactory;
128
        $this->orderItemQuantityModifier = $orderItemQuantityModifier;
129
        $this->orderManager = $orderManager;
130
        $this->channelRepository = $channelRepository;
131
        $this->customerRepository = $customerRepository;
132
        $this->productRepository = $productRepository;
133
        $this->countryRepository = $countryRepository;
134
        $this->paymentMethodRepository = $paymentMethodRepository;
135
        $this->shippingMethodRepository = $shippingMethodRepository;
136
        $this->addressFactory = $addressFactory;
137
        $this->stateMachineFactory = $stateMachineFactory;
138
139
        $this->faker = \Faker\Factory::create();
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function load(array $options)
146
    {
147
        $channels = $this->channelRepository->findAll();
148
        $customers = $this->customerRepository->findAll();
149
        $countries = $this->countryRepository->findAll();
150
151
        for ($i = 0; $i < $options['amount']; $i++) {
152
            $channel = $this->faker->randomElement($channels);
153
            $customer = $this->faker->randomElement($customers);
154
            $countryCode = $this->faker->randomElement($countries)->getCode();
155
156
            $currencyCode = $this->faker->randomElement($channel->getCurrencies()->toArray())->getCode();
157
            $localeCode = $this->faker->randomElement($channel->getLocales()->toArray())->getCode();
158
159
            $order = $this->orderFactory->createNew();
160
            $order->setChannel($channel);
161
            $order->setCustomer($customer);
162
            $order->setCurrencyCode($currencyCode);
163
            $order->setLocaleCode($localeCode);
164
165
            $this->generateItems($order);
166
167
            $this->address($order, $countryCode);
168
            $this->selectShipping($order);
169
            $this->selectPayment($order);
170
            $this->completeCheckout($order);
171
172
            $this->orderManager->persist($order);
173
174
            if (0 === ($i % 50)) {
175
                $this->orderManager->flush();
176
            }
177
        }
178
179
        $this->orderManager->flush();
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185
    public function getName()
186
    {
187
        return 'order';
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    protected function configureOptionsNode(ArrayNodeDefinition $optionsNode)
194
    {
195
        $optionsNode
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method min() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\FloatNodeDefinition, Symfony\Component\Config...r\IntegerNodeDefinition, Symfony\Component\Config...r\NumericNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
196
            ->children()
197
                ->integerNode('amount')->isRequired()->min(0)->end()
198
        ;
199
    }
200
201
    /**
202
     * @param OrderInterface $order
203
     */
204
    private function generateItems(OrderInterface $order)
205
    {
206
        $numberOfItems = rand(1, 5);
207
        $products = $this->productRepository->findAll();
208
209
        for ($i = 0; $i < $numberOfItems; $i++) {
210
            $item = $this->orderItemFactory->createNew();
211
212
            $product = $this->faker->randomElement($products);
213
            $variant = $this->faker->randomElement($product->getVariants()->toArray());
214
215
            $item->setVariant($variant);
216
            $this->orderItemQuantityModifier->modify($item, rand(1, 5));
217
218
            $order->addItem($item);
219
        }
220
    }
221
222
    /**
223
     * @param OrderInterface $order
224
     * @param string $countryCode
225
     */
226
    private function address(OrderInterface $order, $countryCode)
227
    {
228
        /** @var AddressInterface $address */
229
        $address = $this->addressFactory->createNew();
230
        $address->setFirstname($this->faker->firstName);
231
        $address->setLastname($this->faker->lastName);
232
        $address->setStreet($this->faker->streetName);
233
        $address->setCountryCode($countryCode);
234
        $address->setCity($this->faker->city);
235
        $address->setPostcode($this->faker->postcode);
236
237
        $order->setShippingAddress($address);
238
        $order->setBillingAddress($address);
239
240
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
241
    }
242
243
    /**
244
     * @param OrderInterface $order
245
     */
246
    private function selectShipping(OrderInterface $order)
247
    {
248
        $shippingMethod = $this
249
            ->faker
250
            ->randomElement($this->shippingMethodRepository->findEnabledForChannel($order->getChannel()))
0 ignored issues
show
Compatibility introduced by
$order->getChannel() of type object<Sylius\Component\...Model\ChannelInterface> is not a sub-type of object<Sylius\Component\...Model\ChannelInterface>. It seems like you assume a child interface of the interface Sylius\Component\Channel\Model\ChannelInterface 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...
251
        ;
252
253
        Assert::notNull($shippingMethod);
254
255
        foreach ($order->getShipments() as $shipment) {
256
            $shipment->setMethod($shippingMethod);
257
        }
258
259
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
260
    }
261
262
    /**
263
     * @param OrderInterface $order
264
     */
265
    private function selectPayment(OrderInterface $order)
266
    {
267
        $paymentMethod = $this
268
            ->faker
269
            ->randomElement($this->paymentMethodRepository->findEnabledForChannel($order->getChannel()))
0 ignored issues
show
Compatibility introduced by
$order->getChannel() of type object<Sylius\Component\...Model\ChannelInterface> is not a sub-type of object<Sylius\Component\...Model\ChannelInterface>. It seems like you assume a child interface of the interface Sylius\Component\Channel\Model\ChannelInterface 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...
270
        ;
271
272
        Assert::notNull($paymentMethod);
273
274
        foreach ($order->getPayments() as $payment) {
275
            $payment->setMethod($paymentMethod);
276
        }
277
278
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
279
    }
280
281
    /**
282
     * @param OrderInterface $order
283
     */
284
    private function completeCheckout(OrderInterface $order)
285
    {
286
        if ($this->faker->boolean(25)) {
287
            $order->setNotes($this->faker->sentence);
288
        }
289
290
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
291
    }
292
293
    /**
294
     * @param OrderInterface $order
295
     * @param string $transition
296
     */
297
    private function applyCheckoutStateTransition(OrderInterface $order, $transition)
298
    {
299
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
300
    }
301
}
302