Completed
Push — 1.4 ( 4d2cb6...71b698 )
by Kamil
09:52 queued 04:24
created

OrderFixture::generateItems()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 9.36
c 0
b 0
f 0
cc 4
nc 4
nop 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
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\Model\AddressInterface;
20
use Sylius\Component\Core\Model\ChannelInterface;
21
use Sylius\Component\Core\Model\OrderInterface;
22
use Sylius\Component\Core\Model\OrderItemInterface;
23
use Sylius\Component\Core\Model\ProductInterface;
24
use Sylius\Component\Core\OrderCheckoutStates;
25
use Sylius\Component\Core\OrderCheckoutTransitions;
26
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
27
use Sylius\Component\Core\Repository\ShippingMethodRepositoryInterface;
28
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
29
use Sylius\Component\Resource\Factory\FactoryInterface;
30
use Sylius\Component\Resource\Repository\RepositoryInterface;
31
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
32
use Webmozart\Assert\Assert;
33
34
class OrderFixture extends AbstractFixture
35
{
36
    /** @var FactoryInterface */
37
    private $orderFactory;
38
39
    /** @var FactoryInterface */
40
    private $orderItemFactory;
41
42
    /** @var OrderItemQuantityModifierInterface */
43
    private $orderItemQuantityModifier;
44
45
    /** @var ObjectManager */
46
    private $orderManager;
47
48
    /** @var RepositoryInterface */
49
    private $channelRepository;
50
51
    /** @var RepositoryInterface */
52
    private $customerRepository;
53
54
    /** @var RepositoryInterface */
55
    private $productRepository;
56
57
    /** @var RepositoryInterface */
58
    private $countryRepository;
59
60
    /** @var PaymentMethodRepositoryInterface */
61
    private $paymentMethodRepository;
62
63
    /** @var ShippingMethodRepositoryInterface */
64
    private $shippingMethodRepository;
65
66
    /** @var FactoryInterface */
67
    private $addressFactory;
68
69
    /** @var StateMachineFactoryInterface */
70
    private $stateMachineFactory;
71
72
    /** @var \Faker\Generator */
73
    private $faker;
74
75
    public function __construct(
76
        FactoryInterface $orderFactory,
77
        FactoryInterface $orderItemFactory,
78
        OrderItemQuantityModifierInterface $orderItemQuantityModifier,
79
        ObjectManager $orderManager,
80
        RepositoryInterface $channelRepository,
81
        RepositoryInterface $customerRepository,
82
        RepositoryInterface $productRepository,
83
        RepositoryInterface $countryRepository,
84
        PaymentMethodRepositoryInterface $paymentMethodRepository,
85
        ShippingMethodRepositoryInterface $shippingMethodRepository,
86
        FactoryInterface $addressFactory,
87
        StateMachineFactoryInterface $stateMachineFactory
88
    ) {
89
        $this->orderFactory = $orderFactory;
90
        $this->orderItemFactory = $orderItemFactory;
91
        $this->orderItemQuantityModifier = $orderItemQuantityModifier;
92
        $this->orderManager = $orderManager;
93
        $this->channelRepository = $channelRepository;
94
        $this->customerRepository = $customerRepository;
95
        $this->productRepository = $productRepository;
96
        $this->countryRepository = $countryRepository;
97
        $this->paymentMethodRepository = $paymentMethodRepository;
98
        $this->shippingMethodRepository = $shippingMethodRepository;
99
        $this->addressFactory = $addressFactory;
100
        $this->stateMachineFactory = $stateMachineFactory;
101
102
        $this->faker = \Faker\Factory::create();
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function load(array $options): void
109
    {
110
        $channels = $this->channelRepository->findAll();
111
        $customers = $this->customerRepository->findAll();
112
        $countries = $this->countryRepository->findAll();
113
114
        for ($i = 0; $i < $options['amount']; ++$i) {
115
            $channel = $this->faker->randomElement($channels);
116
            $customer = $this->faker->randomElement($customers);
117
            $countryCode = $this->faker->randomElement($countries)->getCode();
118
119
            $currencyCode = $channel->getBaseCurrency()->getCode();
120
            $localeCode = $this->faker->randomElement($channel->getLocales()->toArray())->getCode();
121
122
            /** @var OrderInterface $order */
123
            $order = $this->orderFactory->createNew();
124
            $order->setChannel($channel);
125
            $order->setCustomer($customer);
126
            $order->setCurrencyCode($currencyCode);
127
            $order->setLocaleCode($localeCode);
128
129
            $this->generateItems($order);
130
131
            $this->address($order, $countryCode);
132
            $this->selectShipping($order);
133
            $this->selectPayment($order);
134
            $this->completeCheckout($order);
135
136
            $this->orderManager->persist($order);
137
138
            if (0 === ($i % 50)) {
139
                $this->orderManager->flush();
140
            }
141
        }
142
143
        $this->orderManager->flush();
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function getName(): string
150
    {
151
        return 'order';
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    protected function configureOptionsNode(ArrayNodeDefinition $optionsNode): void
158
    {
159
        $optionsNode
160
            ->children()
161
                ->integerNode('amount')->isRequired()->min(0)->end()
162
        ;
163
    }
164
165
    private function generateItems(OrderInterface $order): void
166
    {
167
        $numberOfItems = random_int(1, 5);
168
        $products = $this->productRepository->findAll();
169
        $generatedItems = [];
170
171
        for ($i = 0; $i < $numberOfItems; ++$i) {
172
            /** @var ProductInterface $product */
173
            $product = $this->faker->randomElement($products);
174
175
            if (!$product->hasChannel($order->getChannel())) {
176
                $i--;
177
                continue;
178
            }
179
180
            $variant = $this->faker->randomElement($product->getVariants()->toArray());
181
182
            if (array_key_exists($variant->getCode(), $generatedItems)) {
183
                /** @var OrderItemInterface $item */
184
                $item = $generatedItems[$variant->getCode()];
185
                $this->orderItemQuantityModifier->modify($item, $item->getQuantity() + random_int(1, 5));
186
187
                continue;
188
            }
189
190
            /** @var OrderItemInterface $item */
191
            $item = $this->orderItemFactory->createNew();
192
193
            $item->setVariant($variant);
194
            $this->orderItemQuantityModifier->modify($item, random_int(1, 5));
195
196
            $generatedItems[$variant->getCode()] = $item;
197
            $order->addItem($item);
198
        }
199
    }
200
201
    private function address(OrderInterface $order, string $countryCode): void
202
    {
203
        /** @var AddressInterface $address */
204
        $address = $this->addressFactory->createNew();
205
        $address->setFirstName($this->faker->firstName);
206
        $address->setLastName($this->faker->lastName);
207
        $address->setStreet($this->faker->streetAddress);
208
        $address->setCountryCode($countryCode);
209
        $address->setCity($this->faker->city);
210
        $address->setPostcode($this->faker->postcode);
211
212
        $order->setShippingAddress($address);
213
        $order->setBillingAddress(clone $address);
214
215
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
216
    }
217
218
    private function selectShipping(OrderInterface $order): void
219
    {
220
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_SHIPPING_SKIPPED) {
221
            return;
222
        }
223
224
        $channel = $order->getChannel();
225
        $shippingMethods = $this->shippingMethodRepository->findEnabledForChannel($channel);
226
227
        if (count($shippingMethods) === 0) {
228
            throw new \InvalidArgumentException(sprintf(
229
                'You have no shipping method available for the channel with code "%s", but they are required to proceed an order',
230
                $channel->getCode()
231
            ));
232
        }
233
234
        $shippingMethod = $this->faker->randomElement($shippingMethods);
235
236
        /** @var ChannelInterface $channel */
237
        $channel = $order->getChannel();
238
        Assert::notNull($shippingMethod, $this->generateInvalidSkipMessage('shipping', $channel->getCode()));
239
240
        foreach ($order->getShipments() as $shipment) {
241
            $shipment->setMethod($shippingMethod);
242
        }
243
244
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
245
    }
246
247
    private function selectPayment(OrderInterface $order): void
248
    {
249
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_PAYMENT_SKIPPED) {
250
            return;
251
        }
252
253
        $paymentMethod = $this
254
            ->faker
255
            ->randomElement($this->paymentMethodRepository->findEnabledForChannel($order->getChannel()))
256
        ;
257
258
        /** @var ChannelInterface $channel */
259
        $channel = $order->getChannel();
260
        Assert::notNull($paymentMethod, $this->generateInvalidSkipMessage('payment', $channel->getCode()));
261
262
        foreach ($order->getPayments() as $payment) {
263
            $payment->setMethod($paymentMethod);
264
        }
265
266
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
267
    }
268
269
    private function completeCheckout(OrderInterface $order): void
270
    {
271
        if ($this->faker->boolean(25)) {
272
            $order->setNotes($this->faker->sentence);
273
        }
274
275
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
276
    }
277
278
    private function applyCheckoutStateTransition(OrderInterface $order, string $transition): void
279
    {
280
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
281
    }
282
283
    private function generateInvalidSkipMessage(string $type, string $channelCode): string
284
    {
285
        return sprintf(
286
            "No enabled %s method was found for the channel '%s'. " .
287
            "Set 'skipping_%s_step_allowed' option to true for this channel if you want to skip %s method selection.",
288
            $type, $channelCode, $type, $type
289
        );
290
    }
291
}
292