Completed
Push — master ( 799620...8a5869 )
by Kamil
116:02 queued 102:30
created

ProductExampleFactory   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 14
dl 0
loc 403
rs 9.2
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A createTranslations() 0 12 2
A createRelations() 0 14 4
B createVariants() 0 26 4
A createChannelPricings() 0 9 1
B __construct() 0 36 1
A create() 0 20 1
B configureOptions() 0 73 4
A createImages() 0 16 3
A createProductTaxons() 0 10 2
A getLocales() 0 8 2
D getRandomValueForProductAttribute() 0 30 10
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 Sylius\Bundle\CoreBundle\Fixture\OptionsResolver\LazyOption;
17
use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
18
use Sylius\Component\Core\Formatter\StringInflector;
19
use Sylius\Component\Core\Model\ChannelPricingInterface;
20
use Sylius\Component\Core\Model\ImageInterface;
21
use Sylius\Component\Core\Model\ProductInterface;
22
use Sylius\Component\Core\Model\ProductVariantInterface;
23
use Sylius\Component\Core\Model\TaxonInterface;
24
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
25
use Sylius\Component\Locale\Model\LocaleInterface;
26
use Sylius\Component\Product\Generator\ProductVariantGeneratorInterface;
27
use Sylius\Component\Product\Generator\SlugGeneratorInterface;
28
use Sylius\Component\Product\Model\ProductAttributeInterface;
29
use Sylius\Component\Product\Model\ProductAttributeValueInterface;
30
use Sylius\Component\Resource\Factory\FactoryInterface;
31
use Sylius\Component\Resource\Repository\RepositoryInterface;
32
use Symfony\Component\HttpFoundation\File\UploadedFile;
33
use Symfony\Component\OptionsResolver\Options;
34
use Symfony\Component\OptionsResolver\OptionsResolver;
35
use Webmozart\Assert\Assert;
36
37
class ProductExampleFactory extends AbstractExampleFactory implements ExampleFactoryInterface
38
{
39
    /**
40
     * @var FactoryInterface
41
     */
42
    private $productFactory;
43
44
    /**
45
     * @var FactoryInterface
46
     */
47
    private $productVariantFactory;
48
49
    /**
50
     * @var FactoryInterface
51
     */
52
    private $channelPricingFactory;
53
54
    /**
55
     * @var FactoryInterface
56
     */
57
    private $productTaxonFactory;
58
59
    /**
60
     * @var ProductVariantGeneratorInterface
61
     */
62
    private $variantGenerator;
63
64
    /**
65
     * @var FactoryInterface
66
     */
67
    private $productAttributeValueFactory;
68
69
    /**
70
     * @var FactoryInterface
71
     */
72
    private $productImageFactory;
73
74
    /**
75
     * @var ImageUploaderInterface
76
     */
77
    private $imageUploader;
78
79
    /**
80
     * @var SlugGeneratorInterface
81
     */
82
    private $slugGenerator;
83
84
    /**
85
     * @var RepositoryInterface
86
     */
87
    private $taxonRepository;
88
89
    /**
90
     * @var RepositoryInterface
91
     */
92
    private $productAttributeRepository;
93
94
    /**
95
     * @var RepositoryInterface
96
     */
97
    private $productOptionRepository;
98
99
    /**
100
     * @var RepositoryInterface
101
     */
102
    private $channelRepository;
103
104
    /**
105
     * @var RepositoryInterface
106
     */
107
    private $localeRepository;
108
109
    /**
110
     * @var \Faker\Generator
111
     */
112
    private $faker;
113
114
    /**
115
     * @var OptionsResolver
116
     */
117
    private $optionsResolver;
118
119
    /**
120
     * @param FactoryInterface $productFactory
121
     * @param FactoryInterface $productVariantFactory
122
     * @param FactoryInterface $channelPricing
123
     * @param ProductVariantGeneratorInterface $variantGenerator
124
     * @param FactoryInterface $productAttributeValueFactory
125
     * @param FactoryInterface $productImageFactory
126
     * @param FactoryInterface $productTaxonFactory
127
     * @param ImageUploaderInterface $imageUploader
128
     * @param SlugGeneratorInterface $slugGenerator
129
     * @param RepositoryInterface $taxonRepository
130
     * @param RepositoryInterface $productAttributeRepository
131
     * @param RepositoryInterface $productOptionRepository
132
     * @param RepositoryInterface $channelRepository
133
     * @param RepositoryInterface $localeRepository
134
     */
135
    public function __construct(
136
        FactoryInterface $productFactory,
137
        FactoryInterface $productVariantFactory,
138
        FactoryInterface $channelPricing,
139
        ProductVariantGeneratorInterface $variantGenerator,
140
        FactoryInterface $productAttributeValueFactory,
141
        FactoryInterface $productImageFactory,
142
        FactoryInterface $productTaxonFactory,
143
        ImageUploaderInterface $imageUploader,
144
        SlugGeneratorInterface $slugGenerator,
145
        RepositoryInterface $taxonRepository,
146
        RepositoryInterface $productAttributeRepository,
147
        RepositoryInterface $productOptionRepository,
148
        RepositoryInterface $channelRepository,
149
        RepositoryInterface $localeRepository
150
    ) {
151
        $this->productFactory = $productFactory;
152
        $this->productVariantFactory = $productVariantFactory;
153
        $this->channelPricingFactory = $channelPricing;
154
        $this->variantGenerator = $variantGenerator;
155
        $this->productAttributeValueFactory = $productAttributeValueFactory;
156
        $this->productImageFactory = $productImageFactory;
157
        $this->productTaxonFactory = $productTaxonFactory;
158
        $this->imageUploader = $imageUploader;
159
        $this->slugGenerator = $slugGenerator;
160
        $this->taxonRepository = $taxonRepository;
161
        $this->productAttributeRepository = $productAttributeRepository;
162
        $this->productOptionRepository = $productOptionRepository;
163
        $this->channelRepository = $channelRepository;
164
        $this->localeRepository = $localeRepository;
165
166
        $this->faker = \Faker\Factory::create();
167
        $this->optionsResolver = new OptionsResolver();
168
169
        $this->configureOptions($this->optionsResolver);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function create(array $options = []): ProductInterface
176
    {
177
        $options = $this->optionsResolver->resolve($options);
178
179
        /** @var ProductInterface $product */
180
        $product = $this->productFactory->createNew();
181
        $product->setVariantSelectionMethod($options['variant_selection_method']);
182
        $product->setCode($options['code']);
183
        $product->setEnabled($options['enabled']);
184
        $product->setMainTaxon($options['main_taxon']);
185
        $product->setCreatedAt($this->faker->dateTimeBetween('-1 week', 'now'));
186
187
        $this->createTranslations($product, $options);
188
        $this->createRelations($product, $options);
189
        $this->createVariants($product, $options);
190
        $this->createImages($product, $options);
191
        $this->createProductTaxons($product, $options);
192
193
        return $product;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    protected function configureOptions(OptionsResolver $resolver): void
200
    {
201
        $resolver
202
            ->setDefault('name', function (Options $options): string {
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...
203
                return $this->faker->words(3, true);
204
            })
205
206
            ->setDefault('code', function (Options $options): string {
207
                return StringInflector::nameToCode($options['name']);
208
            })
209
210
            ->setDefault('enabled', function (Options $options): bool {
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...
211
                return $this->faker->boolean(90);
212
            })
213
            ->setAllowedTypes('enabled', 'bool')
214
215
            ->setDefault('short_description', function (Options $options): string {
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...
216
                return $this->faker->paragraph;
217
            })
218
219
            ->setDefault('description', function (Options $options): string {
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...
220
                return $this->faker->paragraphs(3, true);
221
            })
222
223
            ->setDefault('main_taxon', LazyOption::randomOne($this->taxonRepository))
224
            ->setAllowedTypes('main_taxon', ['null', 'string', TaxonInterface::class])
225
            ->setNormalizer('main_taxon', LazyOption::findOneBy($this->taxonRepository, 'code'))
226
227
            ->setDefault('taxons', LazyOption::randomOnes($this->taxonRepository, 3))
228
            ->setAllowedTypes('taxons', 'array')
229
            ->setNormalizer('taxons', LazyOption::findBy($this->taxonRepository, 'code'))
230
231
            ->setDefault('channels', LazyOption::randomOnes($this->channelRepository, 3))
232
            ->setAllowedTypes('channels', 'array')
233
            ->setNormalizer('channels', LazyOption::findBy($this->channelRepository, 'code'))
234
235
            ->setDefault('variant_selection_method', ProductInterface::VARIANT_SELECTION_MATCH)
236
            ->setAllowedValues('variant_selection_method', [ProductInterface::VARIANT_SELECTION_MATCH, ProductInterface::VARIANT_SELECTION_CHOICE])
237
238
            ->setDefault('product_attributes', [])
239
            ->setAllowedTypes('product_attributes', 'array')
240
            ->setNormalizer('product_attributes', function (Options $options, array $productAttributes): array {
241
                $productAttributesValues = [];
242
                foreach ($productAttributes as $code => $value) {
243
                    foreach ($this->getLocales() as $localeCode) {
244
                        /** @var ProductAttributeInterface $productAttribute */
245
                        $productAttribute = $this->productAttributeRepository->findOneBy(['code' => $code]);
246
247
                        Assert::notNull($productAttribute);
248
249
                        /** @var ProductAttributeValueInterface $productAttributeValue */
250
                        $productAttributeValue = $this->productAttributeValueFactory->createNew();
251
                        $productAttributeValue->setAttribute($productAttribute);
252
                        $productAttributeValue->setValue($value ?: $this->getRandomValueForProductAttribute($productAttribute));
253
                        $productAttributeValue->setLocaleCode($localeCode);
254
255
                        $productAttributesValues[] = $productAttributeValue;
256
                    }
257
                }
258
259
                return $productAttributesValues;
260
            })
261
262
            ->setDefault('product_options', [])
263
            ->setAllowedTypes('product_options', 'array')
264
            ->setNormalizer('product_options', LazyOption::findBy($this->productOptionRepository, 'code'))
265
266
            ->setDefault('images', [])
267
            ->setAllowedTypes('images', 'array')
268
269
            ->setDefault('shipping_required', true)
270
        ;
271
    }
272
273
    /**
274
     * @param ProductInterface $product
275
     * @param array $options
276
     */
277
    private function createTranslations(ProductInterface $product, array $options): void
278
    {
279
        foreach ($this->getLocales() as $localeCode) {
280
            $product->setCurrentLocale($localeCode);
281
            $product->setFallbackLocale($localeCode);
282
283
            $product->setName($options['name']);
284
            $product->setSlug($this->slugGenerator->generate($options['name']));
285
            $product->setShortDescription($options['short_description']);
286
            $product->setDescription($options['description']);
287
        }
288
    }
289
290
    /**
291
     * @param ProductInterface $product
292
     * @param array $options
293
     */
294
    private function createRelations(ProductInterface $product, array $options): void
295
    {
296
        foreach ($options['channels'] as $channel) {
297
            $product->addChannel($channel);
298
        }
299
300
        foreach ($options['product_options'] as $option) {
301
            $product->addOption($option);
302
        }
303
304
        foreach ($options['product_attributes'] as $attribute) {
305
            $product->addAttribute($attribute);
306
        }
307
    }
308
309
    /**
310
     * @param ProductInterface $product
311
     * @param array $options
312
     */
313
    private function createVariants(ProductInterface $product, array $options): void
314
    {
315
        try {
316
            $this->variantGenerator->generate($product);
317
        } catch (\InvalidArgumentException $exception) {
318
            /** @var ProductVariantInterface $productVariant */
319
            $productVariant = $this->productVariantFactory->createNew();
320
321
            $product->addVariant($productVariant);
322
        }
323
324
        $i = 0;
325
        /** @var ProductVariantInterface $productVariant */
326
        foreach ($product->getVariants() as $productVariant) {
327
            $productVariant->setName($this->faker->word);
328
            $productVariant->setCode(sprintf('%s-variant-%d', $options['code'], $i));
329
            $productVariant->setOnHand($this->faker->randomNumber(1));
330
            $productVariant->setShippingRequired($options['shipping_required']);
331
332
            foreach ($this->channelRepository->findAll() as $channel) {
333
                $this->createChannelPricings($productVariant, $channel->getCode());
334
            }
335
336
            ++$i;
337
        }
338
    }
339
340
    /**
341
     * @param ProductVariantInterface $productVariant
342
     * @param string $channelCode
343
     */
344
    private function createChannelPricings(ProductVariantInterface $productVariant, string $channelCode): void
345
    {
346
        /** @var ChannelPricingInterface $channelPricing */
347
        $channelPricing = $this->channelPricingFactory->createNew();
348
        $channelPricing->setChannelCode($channelCode);
349
        $channelPricing->setPrice($this->faker->randomNumber(3));
350
351
        $productVariant->addChannelPricing($channelPricing);
352
    }
353
354
    /**
355
     * @param ProductInterface $product
356
     * @param array $options
357
     */
358
    private function createImages(ProductInterface $product, array $options): void
359
    {
360
        foreach ($options['images'] as $image) {
361
            $imagePath = array_shift($image);
362
            $uploadedImage = new UploadedFile($imagePath, basename($imagePath));
363
364
            /** @var ImageInterface $productImage */
365
            $productImage = $this->productImageFactory->createNew();
366
            $productImage->setFile($uploadedImage);
367
            $productImage->setType(end($image) ?: null);
368
369
            $this->imageUploader->upload($productImage);
370
371
            $product->addImage($productImage);
372
        }
373
    }
374
375
    /**
376
     * @param ProductInterface $product
377
     * @param array $options
378
     */
379
    private function createProductTaxons(ProductInterface $product, array $options): void
380
    {
381
        foreach ($options['taxons'] as $taxon) {
382
            $productTaxon = $this->productTaxonFactory->createNew();
383
            $productTaxon->setProduct($product);
384
            $productTaxon->setTaxon($taxon);
385
386
            $product->addProductTaxon($productTaxon);
387
        }
388
    }
389
390
    /**
391
     * @return iterable
392
     */
393
    private function getLocales(): iterable
394
    {
395
        /** @var LocaleInterface[] $locales */
396
        $locales = $this->localeRepository->findAll();
397
        foreach ($locales as $locale) {
398
            yield $locale->getCode();
399
        }
400
    }
401
402
    /**
403
     * @param ProductAttributeInterface $productAttribute
404
     *
405
     * @return mixed
406
     *
407
     * @throws \BadMethodCallException
408
     */
409
    private function getRandomValueForProductAttribute(ProductAttributeInterface $productAttribute)
410
    {
411
        switch ($productAttribute->getStorageType()) {
412
            case ProductAttributeValueInterface::STORAGE_BOOLEAN:
413
                return $this->faker->boolean;
414
            case ProductAttributeValueInterface::STORAGE_INTEGER:
415
                return $this->faker->numberBetween(0, 10000);
416
            case ProductAttributeValueInterface::STORAGE_FLOAT:
417
                return $this->faker->randomFloat(4, 0, 10000);
418
            case ProductAttributeValueInterface::STORAGE_TEXT:
419
                return $this->faker->sentence;
420
            case ProductAttributeValueInterface::STORAGE_DATE:
421
            case ProductAttributeValueInterface::STORAGE_DATETIME:
422
                return $this->faker->dateTimeThisCentury;
423
            case ProductAttributeValueInterface::STORAGE_JSON:
424
                if ($productAttribute->getType() == SelectAttributeType::TYPE) {
425
                    if ($productAttribute->getConfiguration()['multiple']) {
426
                        return $this->faker->randomElements(
427
                            array_keys($productAttribute->getConfiguration()['choices']),
428
                            $this->faker->numberBetween(1, count($productAttribute->getConfiguration()['choices']))
429
                        );
430
                    }
431
432
                    return [$this->faker->randomKey($productAttribute->getConfiguration()['choices'])];
433
                }
434
                // no break
435
            default:
436
                throw new \BadMethodCallException();
437
        }
438
    }
439
}
440