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

OrderExampleFactory::__construct()   A

Complexity

Conditions 1
Paths 1

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 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\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