Completed
Push — master ( 2960e8...660e14 )
by Kamil
05:23 queued 10s
created

OrderExampleFactory   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 9
dl 0
loc 263
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 35 1
A create() 0 9 1
A configureOptions() 0 23 1
A createOrder() 0 23 1
A generateItems() 0 35 4
A address() 0 16 1
A selectShipping() 0 28 4
A selectPayment() 0 6 2
A completeCheckout() 0 8 2
A applyCheckoutStateTransition() 0 4 1
A generateInvalidSkipMessage() 0 8 1
A setOrderCompletedDate() 0 6 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\Bundle\CoreBundle\Fixture\Factory;
15
16
use Doctrine\Common\Persistence\ObjectManager;
17
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
18
use Sylius\Bundle\CoreBundle\Fixture\OptionsResolver\LazyOption;
19
use Sylius\Component\Addressing\Model\CountryInterface;
20
use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface;
21
use Sylius\Component\Core\Checker\OrderShippingMethodSelectionRequirementCheckerInterface;
22
use Sylius\Component\Core\Model\AddressInterface;
23
use Sylius\Component\Core\Model\ChannelInterface;
24
use Sylius\Component\Core\Model\CustomerInterface;
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\OrderCheckoutStates;
29
use Sylius\Component\Core\OrderCheckoutTransitions;
30
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
31
use Sylius\Component\Core\Repository\ShippingMethodRepositoryInterface;
32
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
33
use Sylius\Component\Resource\Factory\FactoryInterface;
34
use Sylius\Component\Resource\Repository\RepositoryInterface;
35
use Symfony\Component\OptionsResolver\Options;
36
use Symfony\Component\OptionsResolver\OptionsResolver;
37
use Webmozart\Assert\Assert;
38
39
class OrderExampleFactory extends AbstractExampleFactory implements ExampleFactoryInterface
40
{
41
    /** @var FactoryInterface */
42
    protected $orderFactory;
43
44
    /** @var FactoryInterface */
45
    protected $orderItemFactory;
46
47
    /** @var OrderItemQuantityModifierInterface */
48
    protected $orderItemQuantityModifier;
49
50
    /** @var ObjectManager */
51
    protected $orderManager;
52
53
    /** @var RepositoryInterface */
54
    protected $channelRepository;
55
56
    /** @var RepositoryInterface */
57
    protected $customerRepository;
58
59
    /** @var RepositoryInterface */
60
    protected $productRepository;
61
62
    /** @var RepositoryInterface */
63
    protected $countryRepository;
64
65
    /** @var PaymentMethodRepositoryInterface */
66
    protected $paymentMethodRepository;
67
68
    /** @var ShippingMethodRepositoryInterface */
69
    protected $shippingMethodRepository;
70
71
    /** @var FactoryInterface */
72
    protected $addressFactory;
73
74
    /** @var StateMachineFactoryInterface */
75
    protected $stateMachineFactory;
76
77
    /** @var OrderShippingMethodSelectionRequirementCheckerInterface */
78
    protected $orderShippingMethodSelectionRequirementChecker;
79
80
    /** @var OrderPaymentMethodSelectionRequirementCheckerInterface */
81
    protected $orderPaymentMethodSelectionRequirementChecker;
82
83
    /** @var OptionsResolver */
84
    private $optionsResolver;
85
86
    /** @var \Faker\Generator */
87
    protected $faker;
88
89
    public function __construct(
90
        FactoryInterface $orderFactory,
91
        FactoryInterface $orderItemFactory,
92
        OrderItemQuantityModifierInterface $orderItemQuantityModifier,
93
        ObjectManager $orderManager,
94
        RepositoryInterface $channelRepository,
95
        RepositoryInterface $customerRepository,
96
        RepositoryInterface $productRepository,
97
        RepositoryInterface $countryRepository,
98
        PaymentMethodRepositoryInterface $paymentMethodRepository,
99
        ShippingMethodRepositoryInterface $shippingMethodRepository,
100
        FactoryInterface $addressFactory,
101
        StateMachineFactoryInterface $stateMachineFactory,
102
        OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker,
103
        OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker
104
    ) {
105
        $this->orderFactory = $orderFactory;
106
        $this->orderItemFactory = $orderItemFactory;
107
        $this->orderItemQuantityModifier = $orderItemQuantityModifier;
108
        $this->orderManager = $orderManager;
109
        $this->channelRepository = $channelRepository;
110
        $this->customerRepository = $customerRepository;
111
        $this->productRepository = $productRepository;
112
        $this->countryRepository = $countryRepository;
113
        $this->paymentMethodRepository = $paymentMethodRepository;
114
        $this->shippingMethodRepository = $shippingMethodRepository;
115
        $this->addressFactory = $addressFactory;
116
        $this->stateMachineFactory = $stateMachineFactory;
117
        $this->orderShippingMethodSelectionRequirementChecker = $orderShippingMethodSelectionRequirementChecker;
118
        $this->orderPaymentMethodSelectionRequirementChecker = $orderPaymentMethodSelectionRequirementChecker;
119
120
        $this->optionsResolver = new OptionsResolver();
121
        $this->faker = \Faker\Factory::create();
122
        $this->configureOptions($this->optionsResolver);
123
    }
124
125
    public function create(array $options = []): OrderInterface
126
    {
127
        $options = $this->optionsResolver->resolve($options);
128
129
        $order = $this->createOrder($options['channel'], $options['customer'], $options['country']);
130
        $this->setOrderCompletedDate($order, $options['complete_date']);
131
132
        return $order;
133
    }
134
135
    protected function configureOptions(OptionsResolver $resolver): void
136
    {
137
        $resolver
138
            ->setDefault('amount', 20)
139
140
            ->setDefault('channel', LazyOption::randomOne($this->channelRepository))
141
            ->setAllowedTypes('channel', ['null', 'string', ChannelInterface::class])
142
            ->setNormalizer('channel', LazyOption::findOneBy($this->channelRepository, 'code'))
143
144
            ->setDefault('customer', LazyOption::randomOne($this->customerRepository))
145
            ->setAllowedTypes('customer', ['null', 'string', CustomerInterface::class])
146
            ->setNormalizer('customer', LazyOption::findOneBy($this->customerRepository, 'email'))
147
148
            ->setDefault('country', LazyOption::randomOne($this->countryRepository))
149
            ->setAllowedTypes('country', ['null', 'string', CountryInterface::class])
150
            ->setNormalizer('country', LazyOption::findOneBy($this->countryRepository, 'code'))
151
152
            ->setDefault('complete_date', function (Options $options): \DateTimeInterface {
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
153
                return $this->faker->dateTimeBetween('-1 years', 'now');
154
            })
155
            ->setAllowedTypes('complete_date', ['null', \DateTime::class])
156
        ;
157
    }
158
159
    protected function createOrder(ChannelInterface $channel, CustomerInterface $customer, CountryInterface $country): OrderInterface
160
    {
161
        $countryCode = $country->getCode();
162
163
        $currencyCode = $channel->getBaseCurrency()->getCode();
164
        $localeCode = $this->faker->randomElement($channel->getLocales()->toArray())->getCode();
165
166
        /** @var OrderInterface $order */
167
        $order = $this->orderFactory->createNew();
168
        $order->setChannel($channel);
169
        $order->setCustomer($customer);
170
        $order->setCurrencyCode($currencyCode);
171
        $order->setLocaleCode($localeCode);
172
173
        $this->generateItems($order);
174
175
        $this->address($order, $countryCode);
176
        $this->selectShipping($order);
177
        $this->selectPayment($order);
178
        $this->completeCheckout($order);
179
180
        return $order;
181
    }
182
183
    protected function generateItems(OrderInterface $order): void
184
    {
185
        $numberOfItems = random_int(1, 5);
186
        $products = $this->productRepository->findAll();
187
        $generatedItems = [];
188
189
        for ($i = 0; $i < $numberOfItems; ++$i) {
190
            /** @var ProductInterface $product */
191
            $product = $this->faker->randomElement($products);
192
193
            if (!$product->hasChannel($order->getChannel())) {
194
                $i--;
195
                continue;
196
            }
197
198
            $variant = $this->faker->randomElement($product->getVariants()->toArray());
199
200
            if (array_key_exists($variant->getCode(), $generatedItems)) {
201
                /** @var OrderItemInterface $item */
202
                $item = $generatedItems[$variant->getCode()];
203
                $this->orderItemQuantityModifier->modify($item, $item->getQuantity() + random_int(1, 5));
204
205
                continue;
206
            }
207
208
            /** @var OrderItemInterface $item */
209
            $item = $this->orderItemFactory->createNew();
210
211
            $item->setVariant($variant);
212
            $this->orderItemQuantityModifier->modify($item, random_int(1, 5));
213
214
            $generatedItems[$variant->getCode()] = $item;
215
            $order->addItem($item);
216
        }
217
    }
218
219
    protected function address(OrderInterface $order, string $countryCode): void
220
    {
221
        /** @var AddressInterface $address */
222
        $address = $this->addressFactory->createNew();
223
        $address->setFirstName($this->faker->firstName);
224
        $address->setLastName($this->faker->lastName);
225
        $address->setStreet($this->faker->streetAddress);
226
        $address->setCountryCode($countryCode);
227
        $address->setCity($this->faker->city);
228
        $address->setPostcode($this->faker->postcode);
229
230
        $order->setShippingAddress($address);
231
        $order->setBillingAddress(clone $address);
232
233
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
234
    }
235
236
    protected function selectShipping(OrderInterface $order): void
237
    {
238
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_SHIPPING_SKIPPED) {
239
            return;
240
        }
241
242
        $channel = $order->getChannel();
243
        $shippingMethods = $this->shippingMethodRepository->findEnabledForChannel($channel);
244
245
        if (count($shippingMethods) === 0) {
246
            throw new \InvalidArgumentException(sprintf(
247
                'You have no shipping method available for the channel with code "%s", but they are required to proceed an order',
248
                $channel->getCode()
249
            ));
250
        }
251
252
        $shippingMethod = $this->faker->randomElement($shippingMethods);
253
254
        /** @var ChannelInterface $channel */
255
        $channel = $order->getChannel();
256
        Assert::notNull($shippingMethod, $this->generateInvalidSkipMessage('shipping', $channel->getCode()));
257
258
        foreach ($order->getShipments() as $shipment) {
259
            $shipment->setMethod($shippingMethod);
260
        }
261
262
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
263
    }
264
265
    private function selectPayment(OrderInterface $order): void
266
    {
267
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_PAYMENT_SKIPPED) {
268
            return;
269
        }
270
    }
271
272
    protected function completeCheckout(OrderInterface $order): void
273
    {
274
        if ($this->faker->boolean(25)) {
275
            $order->setNotes($this->faker->sentence);
276
        }
277
278
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
279
    }
280
281
    protected function applyCheckoutStateTransition(OrderInterface $order, string $transition): void
282
    {
283
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
284
    }
285
286
    private function generateInvalidSkipMessage(string $type, string $channelCode): string
287
    {
288
        return sprintf(
289
            "No enabled %s method was found for the channel '%s'. " .
290
            "Set 'skipping_%s_step_allowed' option to true for this channel if you want to skip %s method selection.",
291
            $type, $channelCode, $type, $type
292
        );
293
    }
294
295
    private function setOrderCompletedDate(OrderInterface $order, \DateTimeInterface $date): void
296
    {
297
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED) {
298
            $order->setCheckoutCompletedAt($date);
299
        }
300
    }
301
}
302