Completed
Push — default-locale-code ( 1d8918 )
by Kamil
05:05
created

OrderFixture::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 9.392
c 0
b 0
f 0
cc 1
nc 1
nop 14

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Bundle\CoreBundle\Fixture;
15
16
use Doctrine\Common\Persistence\ObjectManager;
17
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
18
use Sylius\Bundle\FixturesBundle\Fixture\AbstractFixture;
19
use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface;
20
use Sylius\Component\Core\Checker\OrderShippingMethodSelectionRequirementCheckerInterface;
21
use Sylius\Component\Core\Model\AddressInterface;
22
use Sylius\Component\Core\Model\ChannelInterface;
23
use Sylius\Component\Core\Model\OrderInterface;
24
use Sylius\Component\Core\Model\OrderItemInterface;
25
use Sylius\Component\Core\Model\ProductInterface;
26
use Sylius\Component\Core\OrderCheckoutStates;
27
use Sylius\Component\Core\OrderCheckoutTransitions;
28
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
29
use Sylius\Component\Core\Repository\ShippingMethodRepositoryInterface;
30
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
31
use Sylius\Component\Resource\Factory\FactoryInterface;
32
use Sylius\Component\Resource\Repository\RepositoryInterface;
33
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
34
use Webmozart\Assert\Assert;
35
36
class OrderFixture extends AbstractFixture
37
{
38
    /** @var FactoryInterface */
39
    private $orderFactory;
40
41
    /** @var FactoryInterface */
42
    private $orderItemFactory;
43
44
    /** @var OrderItemQuantityModifierInterface */
45
    private $orderItemQuantityModifier;
46
47
    /** @var ObjectManager */
48
    private $orderManager;
49
50
    /** @var RepositoryInterface */
51
    private $channelRepository;
52
53
    /** @var RepositoryInterface */
54
    private $customerRepository;
55
56
    /** @var RepositoryInterface */
57
    private $productRepository;
58
59
    /** @var RepositoryInterface */
60
    private $countryRepository;
61
62
    /** @var PaymentMethodRepositoryInterface */
63
    private $paymentMethodRepository;
64
65
    /** @var ShippingMethodRepositoryInterface */
66
    private $shippingMethodRepository;
67
68
    /** @var FactoryInterface */
69
    private $addressFactory;
70
71
    /** @var StateMachineFactoryInterface */
72
    private $stateMachineFactory;
73
74
    /** @var OrderShippingMethodSelectionRequirementCheckerInterface */
75
    private $orderShippingMethodSelectionRequirementChecker;
76
77
    /** @var OrderPaymentMethodSelectionRequirementCheckerInterface */
78
    private $orderPaymentMethodSelectionRequirementChecker;
79
80
    /** @var \Faker\Generator */
81
    private $faker;
82
83
    public function __construct(
84
        FactoryInterface $orderFactory,
85
        FactoryInterface $orderItemFactory,
86
        OrderItemQuantityModifierInterface $orderItemQuantityModifier,
87
        ObjectManager $orderManager,
88
        RepositoryInterface $channelRepository,
89
        RepositoryInterface $customerRepository,
90
        RepositoryInterface $productRepository,
91
        RepositoryInterface $countryRepository,
92
        PaymentMethodRepositoryInterface $paymentMethodRepository,
93
        ShippingMethodRepositoryInterface $shippingMethodRepository,
94
        FactoryInterface $addressFactory,
95
        StateMachineFactoryInterface $stateMachineFactory,
96
        OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker,
97
        OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker
98
    ) {
99
        $this->orderFactory = $orderFactory;
100
        $this->orderItemFactory = $orderItemFactory;
101
        $this->orderItemQuantityModifier = $orderItemQuantityModifier;
102
        $this->orderManager = $orderManager;
103
        $this->channelRepository = $channelRepository;
104
        $this->customerRepository = $customerRepository;
105
        $this->productRepository = $productRepository;
106
        $this->countryRepository = $countryRepository;
107
        $this->paymentMethodRepository = $paymentMethodRepository;
108
        $this->shippingMethodRepository = $shippingMethodRepository;
109
        $this->addressFactory = $addressFactory;
110
        $this->stateMachineFactory = $stateMachineFactory;
111
        $this->orderShippingMethodSelectionRequirementChecker = $orderShippingMethodSelectionRequirementChecker;
112
        $this->orderPaymentMethodSelectionRequirementChecker = $orderPaymentMethodSelectionRequirementChecker;
113
114
        $this->faker = \Faker\Factory::create();
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function load(array $options): void
121
    {
122
        $channels = $this->channelRepository->findAll();
123
        $customers = $this->customerRepository->findAll();
124
        $countries = $this->countryRepository->findAll();
125
126
        $randomDates = $this->generateDates($options['amount']);
127
128
        for ($i = 0; $i < $options['amount']; ++$i) {
129
            $channel = $this->faker->randomElement($channels);
130
            $customer = $this->faker->randomElement($customers);
131
            $countryCode = $this->faker->randomElement($countries)->getCode();
132
133
            $currencyCode = $channel->getBaseCurrency()->getCode();
134
            $localeCode = $this->faker->randomElement($channel->getLocales()->toArray())->getCode();
135
136
            /** @var OrderInterface $order */
137
            $order = $this->orderFactory->createNew();
138
            $order->setChannel($channel);
139
            $order->setCustomer($customer);
140
            $order->setCurrencyCode($currencyCode);
141
            $order->setLocaleCode($localeCode);
142
143
            $this->generateItems($order);
144
145
            $this->address($order, $countryCode);
146
            $this->selectShipping($order);
147
            $this->selectPayment($order);
148
            $this->completeCheckout($order);
149
            $this->setOrderCompletedDate($order, $randomDates[$i]);
150
151
            $this->orderManager->persist($order);
152
153
            if (0 === ($i % 50)) {
154
                $this->orderManager->flush();
155
            }
156
        }
157
158
        $this->orderManager->flush();
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getName(): string
165
    {
166
        return 'order';
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    protected function configureOptionsNode(ArrayNodeDefinition $optionsNode): void
173
    {
174
        $optionsNode
175
            ->children()
176
                ->integerNode('amount')->isRequired()->min(0)->end()
177
        ;
178
    }
179
180
    private function generateItems(OrderInterface $order): void
181
    {
182
        $numberOfItems = random_int(1, 5);
183
        $products = $this->productRepository->findAll();
184
        $generatedItems = [];
185
186
        for ($i = 0; $i < $numberOfItems; ++$i) {
187
            /** @var ProductInterface $product */
188
            $product = $this->faker->randomElement($products);
189
190
            if (!$product->hasChannel($order->getChannel())) {
191
                $i--;
192
                continue;
193
            }
194
195
            $variant = $this->faker->randomElement($product->getVariants()->toArray());
196
197
            if (array_key_exists($variant->getCode(), $generatedItems)) {
198
                /** @var OrderItemInterface $item */
199
                $item = $generatedItems[$variant->getCode()];
200
                $this->orderItemQuantityModifier->modify($item, $item->getQuantity() + random_int(1, 5));
201
202
                continue;
203
            }
204
205
            /** @var OrderItemInterface $item */
206
            $item = $this->orderItemFactory->createNew();
207
208
            $item->setVariant($variant);
209
            $this->orderItemQuantityModifier->modify($item, random_int(1, 5));
210
211
            $generatedItems[$variant->getCode()] = $item;
212
            $order->addItem($item);
213
        }
214
    }
215
216
    private function address(OrderInterface $order, string $countryCode): void
217
    {
218
        /** @var AddressInterface $address */
219
        $address = $this->addressFactory->createNew();
220
        $address->setFirstName($this->faker->firstName);
221
        $address->setLastName($this->faker->lastName);
222
        $address->setStreet($this->faker->streetAddress);
223
        $address->setCountryCode($countryCode);
224
        $address->setCity($this->faker->city);
225
        $address->setPostcode($this->faker->postcode);
226
227
        $order->setShippingAddress($address);
228
        $order->setBillingAddress(clone $address);
229
230
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
231
    }
232
233
    private function selectShipping(OrderInterface $order): void
234
    {
235
        if (!$this->orderShippingMethodSelectionRequirementChecker->isShippingMethodSelectionRequired($order)) {
236
            $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING);
237
238
            return;
239
        }
240
241
        $channel = $order->getChannel();
242
        $shippingMethods = $this->shippingMethodRepository->findEnabledForChannel($channel);
243
244
        if (count($shippingMethods) === 0) {
245
            throw new \InvalidArgumentException(sprintf(
246
                'You have no shipping method available for the channel with code "%s", but they are required to proceed an order',
247
                $channel->getCode()
248
            ));
249
        }
250
251
        $shippingMethod = $this->faker->randomElement($shippingMethods);
252
253
        /** @var ChannelInterface $channel */
254
        $channel = $order->getChannel();
255
        Assert::notNull($shippingMethod, $this->generateInvalidSkipMessage('shipping', $channel->getCode()));
256
257
        foreach ($order->getShipments() as $shipment) {
258
            $shipment->setMethod($shippingMethod);
259
        }
260
261
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
262
    }
263
264
    private function selectPayment(OrderInterface $order): void
265
    {
266
        if (!$this->orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)) {
267
            $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT);
268
269
            return;
270
        }
271
272
        $paymentMethod = $this
273
            ->faker
274
            ->randomElement($this->paymentMethodRepository->findEnabledForChannel($order->getChannel()))
275
        ;
276
277
        /** @var ChannelInterface $channel */
278
        $channel = $order->getChannel();
279
        Assert::notNull($paymentMethod, $this->generateInvalidSkipMessage('payment', $channel->getCode()));
280
281
        foreach ($order->getPayments() as $payment) {
282
            $payment->setMethod($paymentMethod);
283
        }
284
285
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
286
    }
287
288
    private function completeCheckout(OrderInterface $order): void
289
    {
290
        if ($this->faker->boolean(25)) {
291
            $order->setNotes($this->faker->sentence);
292
        }
293
294
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
295
    }
296
297
    private function applyCheckoutStateTransition(OrderInterface $order, string $transition): void
298
    {
299
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
300
    }
301
302
    private function setOrderCompletedDate(OrderInterface $order, \DateTimeInterface $date): void
303
    {
304
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED) {
305
            $order->setCheckoutCompletedAt($date);
306
        }
307
    }
308
309
    private function generateDates(int $amount): array
310
    {
311
        $dates = [];
312
313
        for ($i = 0; $i < $amount; ++$i) {
314
            /** @var \DateTimeInterface|array $dates */
315
            $dates[] = $this->faker->dateTimeBetween('-1 years', 'now');
316
        }
317
        sort($dates);
318
319
        return $dates;
320
    }
321
322
    private function generateInvalidSkipMessage(string $type, string $channelCode): string
323
    {
324
        return sprintf(
325
            "No enabled %s method was found for the channel '%s'. " .
326
            "Set 'skipping_%s_step_allowed' option to true for this channel if you want to skip %s method selection.",
327
            $type, $channelCode, $type, $type
328
        );
329
    }
330
}
331