Completed
Push — master ( a7ce67...91da13 )
by Kamil
21:30
created

ProductContext   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 916
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 19
dl 0
loc 916
rs 1.9672
c 0
b 0
f 0

48 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 35 1
A storeHasAProductPricedAt() 0 6 1
A thisProductIsAlsoPricedAtInChannel() 0 10 1
A storeHasProductWithCode() 0 8 1
A storeHasAProductPricedAtAvailableInChannels() 0 15 3
A thisProductIsNamedIn() 0 6 1
A theStoreHasProductNamedInAndIn() 0 11 2
B storeHasAConfigurableProduct() 0 26 5
A theStoreHasProducts() 0 6 2
A thisChannelHasProducts() 0 8 2
A theProductHasVariantPricedAt() 0 14 2
A theProductHasVariants() 0 14 2
A theProductHasNamelessVariantWithCode() 0 6 1
A theProductHasVariantWithCode() 0 6 1
A theProductHasVariantWhichDoesNotRequireShipping() 0 15 1
A theProductHasVariantAtPosition() 0 14 1
A thisVariantIsAlsoPricedAtInChannel() 0 9 1
A itHasVariantNamedInAndIn() 0 17 2
A theProductHasVariantPricedAtIdentifiedBy() 0 8 1
A productOnlyVariantWasRenamed() 0 10 1
A thereIsProductAvailableInGivenChannel() 0 6 1
A productBelongsToTaxCategory() 0 8 1
A itComesInTheFollowingVariations() 0 21 2
A productVariantBelongsToTaxCategory() 0 7 1
A thisProductHasOptionWithValues() 0 21 2
A thereIsQuantityOfProducts() 0 8 1
A theProductIsOutOfStock() 0 9 1
A otherCustomerHasBoughtProductsByThisTime() 0 8 1
A thisProductIsTrackedByTheInventory() 0 8 1
A thisProductIsAvailableInSize() 0 15 1
A thisProductSizeBelongsToShippingCategory() 0 13 1
A thisProductHasThisProductOption() 0 6 1
B thisProductHasAllPossibleVariants() 0 29 5
A thereAreItemsOfProductInVariantAvailableInTheInventory() 0 7 1
A theProductVariantIsTrackedByTheInventory() 0 6 1
A theProductChangedItsPriceTo() 0 9 1
A thisProductHasAnImageWithType() 0 14 1
A thisProductBelongsToShippingCategory() 0 5 1
A thisProductHasBeenDisabled() 0 5 1
A getPriceFromString() 0 4 1
B createProduct() 0 37 6
A addProductOption() 0 13 1
A saveProduct() 0 5 1
A getParameter() 0 4 2
B createProductVariant() 0 28 1
A addProductTranslation() 0 14 2
A addProductVariantTranslation() 0 9 1
A createChannelPricingForChannel() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like ProductContext often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProductContext, and based on these observations, apply Extract Interface, too.

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
namespace Sylius\Behat\Context\Setup;
13
14
use Behat\Behat\Context\Context;
15
use Behat\Gherkin\Node\TableNode;
16
use Behat\Mink\Element\NodeElement;
17
use Doctrine\Common\Persistence\ObjectManager;
18
use Sylius\Behat\Service\SharedStorageInterface;
19
use Sylius\Component\Attribute\Factory\AttributeFactoryInterface;
20
use Sylius\Component\Core\Formatter\StringInflector;
21
use Sylius\Component\Core\Model\ChannelInterface;
22
use Sylius\Component\Core\Model\ChannelPricingInterface;
23
use Sylius\Component\Core\Model\ImageInterface;
24
use Sylius\Component\Core\Model\ProductInterface;
25
use Sylius\Component\Core\Model\ProductTranslationInterface;
26
use Sylius\Component\Core\Model\ProductVariantInterface;
27
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
28
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
29
use Sylius\Component\Product\Factory\ProductFactoryInterface;
30
use Sylius\Component\Product\Generator\ProductVariantGeneratorInterface;
31
use Sylius\Component\Product\Generator\SlugGeneratorInterface;
32
use Sylius\Component\Product\Model\ProductOptionInterface;
33
use Sylius\Component\Product\Model\ProductOptionValueInterface;
34
use Sylius\Component\Product\Model\ProductVariantTranslationInterface;
35
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
36
use Sylius\Component\Resource\Factory\FactoryInterface;
37
use Sylius\Component\Resource\Model\TranslationInterface;
38
use Sylius\Component\Resource\Repository\RepositoryInterface;
39
use Sylius\Component\Shipping\Model\ShippingCategoryInterface;
40
use Sylius\Component\Taxation\Model\TaxCategoryInterface;
41
use Symfony\Component\HttpFoundation\File\UploadedFile;
42
use Webmozart\Assert\Assert;
43
44
/**
45
 * @author Arkadiusz Krakowiak <[email protected]>
46
 * @author Mateusz Zalewski <[email protected]>
47
 * @author Magdalena Banasiak <[email protected]>
48
 */
49
final class ProductContext implements Context
50
{
51
    /**
52
     * @var SharedStorageInterface
53
     */
54
    private $sharedStorage;
55
56
    /**
57
     * @var ProductRepositoryInterface
58
     */
59
    private $productRepository;
60
61
    /**
62
     * @var ProductFactoryInterface
63
     */
64
    private $productFactory;
65
66
    /**
67
     * @var FactoryInterface
68
     */
69
    private $productTranslationFactory;
70
71
    /**
72
     * @var FactoryInterface
73
     */
74
    private $productVariantFactory;
75
76
    /**
77
     * @var FactoryInterface
78
     */
79
    private $productVariantTranslationFactory;
80
81
    /**
82
     * @var FactoryInterface
83
     */
84
    private $channelPricingFactory;
85
86
    /**
87
     * @var FactoryInterface
88
     */
89
    private $productOptionFactory;
90
91
    /**
92
     * @var FactoryInterface
93
     */
94
    private $productOptionValueFactory;
95
96
    /**
97
     * @var FactoryInterface
98
     */
99
    private $productImageFactory;
100
101
    /**
102
     * @var ObjectManager
103
     */
104
    private $objectManager;
105
106
    /**
107
     * @var ProductVariantGeneratorInterface
108
     */
109
    private $productVariantGenerator;
110
111
    /**
112
     * @var ProductVariantResolverInterface
113
     */
114
    private $defaultVariantResolver;
115
116
    /**
117
     * @var ImageUploaderInterface
118
     */
119
    private $imageUploader;
120
121
    /**
122
     * @var SlugGeneratorInterface
123
     */
124
    private $slugGenerator;
125
126
    /**
127
     * @var array
128
     */
129
    private $minkParameters;
130
131
    /**
132
     * @param SharedStorageInterface $sharedStorage
133
     * @param ProductRepositoryInterface $productRepository
134
     * @param ProductFactoryInterface $productFactory
135
     * @param FactoryInterface $productTranslationFactory
136
     * @param FactoryInterface $productVariantFactory
137
     * @param FactoryInterface $productVariantTranslationFactory
138
     * @param FactoryInterface $channelPricingFactory
139
     * @param FactoryInterface $productOptionFactory
140
     * @param FactoryInterface $productOptionValueFactory
141
     * @param FactoryInterface $productImageFactory
142
     * @param ObjectManager $objectManager
143
     * @param ProductVariantGeneratorInterface $productVariantGenerator
144
     * @param ProductVariantResolverInterface $defaultVariantResolver
145
     * @param ImageUploaderInterface $imageUploader
146
     * @param SlugGeneratorInterface $slugGenerator
147
     * @param array $minkParameters
148
     */
149
    public function __construct(
150
        SharedStorageInterface $sharedStorage,
151
        ProductRepositoryInterface $productRepository,
152
        ProductFactoryInterface $productFactory,
153
        FactoryInterface $productTranslationFactory,
154
        FactoryInterface $productVariantFactory,
155
        FactoryInterface $productVariantTranslationFactory,
156
        FactoryInterface $channelPricingFactory,
157
        FactoryInterface $productOptionFactory,
158
        FactoryInterface $productOptionValueFactory,
159
        FactoryInterface $productImageFactory,
160
        ObjectManager $objectManager,
161
        ProductVariantGeneratorInterface $productVariantGenerator,
162
        ProductVariantResolverInterface $defaultVariantResolver,
163
        ImageUploaderInterface $imageUploader,
164
        SlugGeneratorInterface $slugGenerator,
165
        array $minkParameters
166
    ) {
167
        $this->sharedStorage = $sharedStorage;
168
        $this->productRepository = $productRepository;
169
        $this->productFactory = $productFactory;
170
        $this->productTranslationFactory = $productTranslationFactory;
171
        $this->productVariantFactory = $productVariantFactory;
172
        $this->productVariantTranslationFactory = $productVariantTranslationFactory;
173
        $this->channelPricingFactory = $channelPricingFactory;
174
        $this->productOptionFactory = $productOptionFactory;
175
        $this->productOptionValueFactory = $productOptionValueFactory;
176
        $this->productImageFactory = $productImageFactory;
177
        $this->objectManager = $objectManager;
178
        $this->productVariantGenerator = $productVariantGenerator;
179
        $this->defaultVariantResolver = $defaultVariantResolver;
180
        $this->imageUploader = $imageUploader;
181
        $this->slugGenerator = $slugGenerator;
182
        $this->minkParameters = $minkParameters;
183
    }
184
185
    /**
186
     * @Given the store has a product :productName
187
     * @Given the store has a :productName product
188
     * @Given I added a product :productName
189
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+")$/
190
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") in ("[^"]+" channel)$/
191
     */
192
    public function storeHasAProductPricedAt($productName, $price = 100, ChannelInterface $channel = null)
193
    {
194
        $product = $this->createProduct($productName, $price, $channel);
195
196
        $this->saveProduct($product);
197
    }
198
199
    /**
200
     * @Given /^(this product) is also priced at ("[^"]+") in ("[^"]+" channel)$/
201
     */
202
    public function thisProductIsAlsoPricedAtInChannel(ProductInterface $product, $price, ChannelInterface $channel)
203
    {
204
        $product->addChannel($channel);
205
206
        /** @var ProductVariantInterface $productVariant */
207
        $productVariant = $this->defaultVariantResolver->getVariant($product);
208
        $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
209
210
        $this->objectManager->flush();
211
    }
212
213
    /**
214
     * @Given the store( also) has a product :productName with code :code
215
     * @Given the store( also) has a product :productName with code :code, created at :date
216
     */
217
    public function storeHasProductWithCode($productName, $code, $date = null)
218
    {
219
        $product = $this->createProduct($productName);
220
        $product->setCreatedAt(new \DateTime($date));
221
        $product->setCode($code);
222
223
        $this->saveProduct($product);
224
    }
225
226
    /**
227
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") available in (channel "[^"]+") and (channel "[^"]+")$/
228
     */
229
    public function storeHasAProductPricedAtAvailableInChannels($productName, $price = 100, ...$channels)
230
    {
231
        $product = $this->createProduct($productName, $price);
232
        /** @var ProductVariantInterface $productVariant */
233
        $productVariant = $this->defaultVariantResolver->getVariant($product);
234
235
        foreach ($channels as $channel) {
236
            $product->addChannel($channel);
237
            if (!$productVariant->hasChannelPricingForChannel($channel)) {
238
                $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
239
            }
240
        }
241
242
        $this->saveProduct($product);
243
    }
244
245
    /**
246
     * @Given /^(this product) is named "([^"]+)" (in the "([^"]+)" locale)$/
247
     * @Given /^the (product "[^"]+") is named "([^"]+)" (in the "([^"]+)" locale)$/
248
     */
249
    public function thisProductIsNamedIn(ProductInterface $product, $name, $locale)
250
    {
251
        $this->addProductTranslation($product, $name, $locale);
252
253
        $this->objectManager->flush();
254
    }
255
256
    /**
257
     * @Given /^the store has a product named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
258
     */
259
    public function theStoreHasProductNamedInAndIn($firstName, $firstLocale, $secondName, $secondLocale)
260
    {
261
        $product = $this->createProduct($firstName);
262
263
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
264
        foreach ($names as $name => $locale) {
265
            $this->addProductTranslation($product, $name, $locale);
266
        }
267
268
        $this->saveProduct($product);
269
    }
270
271
    /**
272
     * @Given /^the store has(?:| a| an) "([^"]+)" configurable product$/
273
     * @Given /^the store has(?:| a| an) "([^"]+)" configurable product with "([^"]+)" slug$/
274
     */
275
    public function storeHasAConfigurableProduct($productName, $slug = null)
276
    {
277
        /** @var ChannelInterface|null $channel */
278
        $channel = null;
279
        if ($this->sharedStorage->has('channel')) {
280
            $channel = $this->sharedStorage->get('channel');
281
        }
282
283
        /** @var ProductInterface $product */
284
        $product = $this->productFactory->createNew();
285
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
286
287
        if (null !== $channel) {
288
            $product->addChannel($channel);
289
290
            foreach ($channel->getLocales() as $locale) {
291
                $product->setFallbackLocale($locale->getCode());
292
                $product->setCurrentLocale($locale->getCode());
293
294
                $product->setName($productName);
295
                $product->setSlug($slug ?: $this->slugGenerator->generate($productName));
296
            }
297
        }
298
299
        $this->saveProduct($product);
300
    }
301
302
    /**
303
     * @Given the store has( also) :firstProductName and :secondProductName products
304
     * @Given the store has( also) :firstProductName, :secondProductName and :thirdProductName products
305
     * @Given the store has( also) :firstProductName, :secondProductName, :thirdProductName and :fourthProductName products
306
     */
307
    public function theStoreHasProducts(...$productsNames)
308
    {
309
        foreach ($productsNames as $productName) {
310
            $this->saveProduct($this->createProduct($productName));
311
        }
312
    }
313
314
    /**
315
     * @Given /^(this channel) has "([^"]+)", "([^"]+)", "([^"]+)" and "([^"]+)" products$/
316
     */
317
    public function thisChannelHasProducts(ChannelInterface $channel, ...$productsNames)
318
    {
319
        foreach ($productsNames as $productName) {
320
            $product = $this->createProduct($productName, 0, $channel);
321
322
            $this->saveProduct($product);
323
        }
324
    }
325
326
    /**
327
     * @Given /^the (product "[^"]+") has(?:| a) "([^"]+)" variant priced at ("[^"]+")$/
328
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+")$/
329
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") in ("([^"]+)" channel)$/
330
     */
331
    public function theProductHasVariantPricedAt(
332
        ProductInterface $product,
333
        $productVariantName,
334
        $price,
335
        ChannelInterface $channel = null
336
    ) {
337
        $this->createProductVariant(
338
            $product,
339
            $productVariantName,
340
            $price,
341
            StringInflector::nameToUppercaseCode($productVariantName),
342
            (null !== $channel) ? $channel : $this->sharedStorage->get('channel')
343
        );
344
    }
345
346
    /**
347
     * @Given /^the (product "[^"]+") has(?:| a| an) "([^"]+)" variant$/
348
     * @Given /^(this product) has(?:| a| an) "([^"]+)" variant$/
349
     * @Given /^(this product) has "([^"]+)", "([^"]+)" and "([^"]+)" variants$/
350
     */
351
    public function theProductHasVariants(ProductInterface $product, ...$variantNames)
352
    {
353
        $channel = $this->sharedStorage->get('channel');
354
355
        foreach ($variantNames as $name) {
356
            $this->createProductVariant(
357
                $product,
358
                $name,
359
                0,
360
                StringInflector::nameToUppercaseCode($name),
361
                $channel
362
            );
363
        }
364
    }
365
366
    /**
367
     * @Given /^the (product "[^"]+")(?:| also) has a nameless variant with code "([^"]+)"$/
368
     * @Given /^(this product)(?:| also) has a nameless variant with code "([^"]+)"$/
369
     * @Given /^(it)(?:| also) has a nameless variant with code "([^"]+)"$/
370
     */
371
    public function theProductHasNamelessVariantWithCode(ProductInterface $product, $variantCode)
372
    {
373
        $channel = $this->sharedStorage->get('channel');
374
375
        $this->createProductVariant($product, null, 0, $variantCode, $channel);
376
    }
377
378
    /**
379
     * @Given /^the (product "[^"]+")(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
380
     * @Given /^(this product)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
381
     * @Given /^(it)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
382
     */
383
    public function theProductHasVariantWithCode(ProductInterface $product, $variantName, $variantCode)
384
    {
385
        $channel = $this->sharedStorage->get('channel');
386
387
        $this->createProductVariant($product, $variantName, 0, $variantCode, $channel);
388
    }
389
390
    /**
391
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") which does not require shipping$/
392
     */
393
    public function theProductHasVariantWhichDoesNotRequireShipping(
394
        ProductInterface $product,
395
        $productVariantName,
396
        $price
397
    ) {
398
        $this->createProductVariant(
399
            $product,
400
            $productVariantName,
401
            $price,
402
            StringInflector::nameToUppercaseCode($productVariantName),
403
            $this->sharedStorage->get('channel'),
404
            null,
405
            false
406
        );
407
    }
408
409
    /**
410
     * @Given /^the (product "[^"]+") has(?:| also)(?:| a| an) "([^"]+)" variant$/
411
     * @Given /^the (product "[^"]+") has(?:| also)(?:| a| an) "([^"]+)" variant at position ([^"]+)$/
412
     * @Given /^(this product) has(?:| also)(?:| a| an) "([^"]+)" variant at position ([^"]+)$/
413
     */
414
    public function theProductHasVariantAtPosition(
415
        ProductInterface $product,
416
        $productVariantName,
417
        $position = null
418
    ) {
419
        $this->createProductVariant(
420
            $product,
421
            $productVariantName,
422
            0,
423
            StringInflector::nameToUppercaseCode($productVariantName),
424
            $this->sharedStorage->get('channel'),
425
            $position
426
        );
427
    }
428
429
    /**
430
     * @Given /^(this variant) is also priced at ("[^"]+") in ("([^"]+)" channel)$/
431
     */
432
    public function thisVariantIsAlsoPricedAtInChannel(ProductVariantInterface $productVariant, $price, ChannelInterface $channel)
433
    {
434
        $productVariant->addChannelPricing($this->createChannelPricingForChannel(
435
            $this->getPriceFromString(str_replace(['$', '€', '£'], '', $price)),
436
            $channel
437
        ));
438
439
        $this->objectManager->flush();
440
    }
441
442
    /**
443
     * @Given /^(it|this product) has(?:| also) variant named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
444
     */
445
    public function itHasVariantNamedInAndIn(ProductInterface $product, $firstName, $firstLocale, $secondName, $secondLocale)
446
    {
447
        $productVariant = $this->createProductVariant(
448
            $product,
449
            $firstName,
450
            100,
451
            StringInflector::nameToUppercaseCode($firstName),
452
            $this->sharedStorage->get('channel')
453
        );
454
455
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
456
        foreach ($names as $name => $locale) {
457
            $this->addProductVariantTranslation($productVariant, $name, $locale);
458
        }
459
460
        $this->objectManager->flush();
461
    }
462
463
    /**
464
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") identified by "([^"]+)"$/
465
     */
466
    public function theProductHasVariantPricedAtIdentifiedBy(
467
        ProductInterface $product,
468
        $productVariantName,
469
        $price,
470
        $code
471
    ) {
472
        $this->createProductVariant($product, $productVariantName, $price, $code, $this->sharedStorage->get('channel'));
473
    }
474
475
    /**
476
     * @Given /^(this product) only variant was renamed to "([^"]+)"$/
477
     */
478
    public function productOnlyVariantWasRenamed(ProductInterface $product, $variantName)
479
    {
480
        Assert::true($product->isSimple());
481
482
        /** @var ProductVariantInterface $productVariant */
483
        $productVariant = $product->getVariants()->first();
484
        $productVariant->setName($variantName);
485
486
        $this->objectManager->flush();
487
    }
488
489
    /**
490
     * @Given /^there is product "([^"]+)" available in ((?:this|that|"[^"]+") channel)$/
491
     * @Given /^the store has a product "([^"]+)" available in ("([^"]+)" channel)$/
492
     */
493
    public function thereIsProductAvailableInGivenChannel($productName, ChannelInterface $channel)
494
    {
495
        $product = $this->createProduct($productName, 0, $channel);
496
497
        $this->saveProduct($product);
498
    }
499
500
    /**
501
     * @Given /^([^"]+) belongs to ("[^"]+" tax category)$/
502
     */
503
    public function productBelongsToTaxCategory(ProductInterface $product, TaxCategoryInterface $taxCategory)
504
    {
505
        /** @var ProductVariantInterface $variant */
506
        $variant = $this->defaultVariantResolver->getVariant($product);
507
        $variant->setTaxCategory($taxCategory);
508
509
        $this->objectManager->flush();
510
    }
511
512
    /**
513
     * @Given /^(it) comes in the following variations:$/
514
     */
515
    public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)
516
    {
517
        $channel = $this->sharedStorage->get('channel');
518
519
        foreach ($table->getHash() as $variantHash) {
520
            /** @var ProductVariantInterface $variant */
521
            $variant = $this->productVariantFactory->createNew();
522
523
            $variant->setName($variantHash['name']);
524
            $variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name']));
525
            $variant->addChannelPricing($this->createChannelPricingForChannel(
526
                $this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])),
527
                $channel
528
            ));
529
530
            $variant->setProduct($product);
531
            $product->addVariant($variant);
532
        }
533
534
        $this->objectManager->flush();
535
    }
536
537
    /**
538
     * @Given /^("[^"]+" variant of product "[^"]+") belongs to ("[^"]+" tax category)$/
539
     */
540
    public function productVariantBelongsToTaxCategory(
541
        ProductVariantInterface $productVariant,
542
        TaxCategoryInterface $taxCategory
543
    ) {
544
        $productVariant->setTaxCategory($taxCategory);
545
        $this->objectManager->flush($productVariant);
546
    }
547
548
    /**
549
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)" and "([^"]+)"$/
550
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)", "([^"]+)" and "([^"]+)"$/
551
     */
552
    public function thisProductHasOptionWithValues(ProductInterface $product, $optionName, ...$values)
553
    {
554
        /** @var ProductOptionInterface $option */
555
        $option = $this->productOptionFactory->createNew();
556
557
        $option->setName($optionName);
558
        $option->setCode(StringInflector::nameToUppercaseCode($optionName));
559
560
        $this->sharedStorage->set(sprintf('%s_option', $optionName), $option);
561
562
        foreach ($values as $key => $value) {
563
            $optionValue = $this->addProductOption($option, $value, StringInflector::nameToUppercaseCode($value));
564
            $this->sharedStorage->set(sprintf('%s_option_%s_value', $value, strtolower($optionName)), $optionValue);
565
        }
566
567
        $product->addOption($option);
568
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_MATCH);
569
570
        $this->objectManager->persist($option);
571
        $this->objectManager->flush();
572
    }
573
574
    /**
575
     * @Given /^there (?:is|are) (\d+) unit(?:|s) of (product "([^"]+)") available in the inventory$/
576
     */
577
    public function thereIsQuantityOfProducts($quantity, ProductInterface $product)
578
    {
579
        /** @var ProductVariantInterface $productVariant */
580
        $productVariant = $this->defaultVariantResolver->getVariant($product);
581
        $productVariant->setOnHand($quantity);
582
583
        $this->objectManager->flush();
584
    }
585
586
    /**
587
     * @Given /^the (product "([^"]+)") is out of stock$/
588
     */
589
    public function theProductIsOutOfStock(ProductInterface $product)
590
    {
591
        /** @var ProductVariantInterface $productVariant */
592
        $productVariant = $this->defaultVariantResolver->getVariant($product);
593
        $productVariant->setTracked(true);
594
        $productVariant->setOnHand(0);
595
596
        $this->objectManager->flush();
597
    }
598
599
    /**
600
     * @When other customer has bought :quantity :product products by this time
601
     */
602
    public function otherCustomerHasBoughtProductsByThisTime($quantity, ProductInterface $product)
603
    {
604
        /** @var ProductVariantInterface $productVariant */
605
        $productVariant = $this->defaultVariantResolver->getVariant($product);
606
        $productVariant->setOnHand($productVariant->getOnHand() - $quantity);
607
608
        $this->objectManager->flush();
609
    }
610
611
    /**
612
     * @Given /^(this product) is tracked by the inventory$/
613
     * @Given /^(?:|the )("[^"]+" product) is(?:| also) tracked by the inventory$/
614
     */
615
    public function thisProductIsTrackedByTheInventory(ProductInterface $product)
616
    {
617
        /** @var ProductVariantInterface $productVariant */
618
        $productVariant = $this->defaultVariantResolver->getVariant($product);
619
        $productVariant->setTracked(true);
620
621
        $this->objectManager->flush();
622
    }
623
624
    /**
625
     * @Given /^(this product) is available in "([^"]+)" ([^"]+) priced at ("[^"]+")$/
626
     */
627
    public function thisProductIsAvailableInSize(ProductInterface $product, $optionValueName, $optionName, $price)
628
    {
629
        /** @var ProductVariantInterface $variant */
630
        $variant = $this->productVariantFactory->createNew();
631
632
        $optionValue = $this->sharedStorage->get(sprintf('%s_option_%s_value', $optionValueName, $optionName));
633
634
        $variant->addOptionValue($optionValue);
635
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $this->sharedStorage->get('channel')));
636
        $variant->setCode(sprintf('%s_%s', $product->getCode(), $optionValueName));
637
        $variant->setName($product->getName());
638
639
        $product->addVariant($variant);
640
        $this->objectManager->flush();
641
    }
642
643
    /**
644
     * @Given the :product product's :optionValueName size belongs to :shippingCategory shipping category
645
     */
646
    public function thisProductSizeBelongsToShippingCategory(ProductInterface $product, $optionValueName, ShippingCategoryInterface $shippingCategory)
647
    {
648
        $code = sprintf('%s_%s', $product->getCode(), $optionValueName);
649
        /** @var ProductVariantInterface $productVariant */
650
        $productVariant = $product->getVariants()->filter(function ($variant) use ($code) {
651
            return $code === $variant->getCode();
652
        })->first();
653
654
        Assert::notNull($productVariant, sprintf('Product variant with given code %s not exists!', $code));
655
656
        $productVariant->setShippingCategory($shippingCategory);
657
        $this->objectManager->flush();
658
    }
659
660
    /**
661
     * @Given /^(this product) has (this product option)$/
662
     * @Given /^(this product) has (?:a|an) ("[^"]+" option)$/
663
     */
664
    public function thisProductHasThisProductOption(ProductInterface $product, ProductOptionInterface $option)
665
    {
666
        $product->addOption($option);
667
668
        $this->objectManager->flush();
669
    }
670
671
    /**
672
     * @Given /^(this product) has all possible variants$/
673
     */
674
    public function thisProductHasAllPossibleVariants(ProductInterface $product)
675
    {
676
        try {
677
            foreach ($product->getVariants() as $productVariant) {
678
                $product->removeVariant($productVariant);
679
            }
680
681
            $this->productVariantGenerator->generate($product);
682
        } catch (\InvalidArgumentException $exception) {
683
            /** @var ProductVariantInterface $productVariant */
684
            $productVariant = $this->productVariantFactory->createNew();
685
686
            $product->addVariant($productVariant);
687
        }
688
689
        $i = 0;
690
        /** @var ProductVariantInterface $productVariant */
691
        foreach ($product->getVariants() as $productVariant) {
692
            $productVariant->setCode(sprintf('%s-variant-%d', $product->getCode(), $i));
693
694
            foreach ($product->getChannels() as $channel) {
695
                $productVariant->addChannelPricing($this->createChannelPricingForChannel(1000, $channel));
696
            }
697
698
            ++$i;
699
        }
700
701
        $this->objectManager->flush();
702
    }
703
704
    /**
705
     * @Given /^there are ([^"]+) units of ("[^"]+" variant of product "[^"]+") available in the inventory$/
706
     */
707
    public function thereAreItemsOfProductInVariantAvailableInTheInventory($quantity, ProductVariantInterface $productVariant)
708
    {
709
        $productVariant->setTracked(true);
710
        $productVariant->setOnHand($quantity);
711
712
        $this->objectManager->flush();
713
    }
714
715
    /**
716
     * @Given /^the ("[^"]+" product variant) is tracked by the inventory$/
717
     */
718
    public function theProductVariantIsTrackedByTheInventory(ProductVariantInterface $productVariant)
719
    {
720
        $productVariant->setTracked(true);
721
722
        $this->objectManager->flush();
723
    }
724
725
    /**
726
     * @Given /^(this product)'s price is ("[^"]+")$/
727
     * @Given /^the (product "[^"]+") changed its price to ("[^"]+")$/
728
     * @Given /^(this product) price has been changed to ("[^"]+")$/
729
     */
730
    public function theProductChangedItsPriceTo(ProductInterface $product, $price)
731
    {
732
        /** @var ProductVariantInterface $productVariant */
733
        $productVariant = $this->defaultVariantResolver->getVariant($product);
734
        $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
735
        $channelPricing->setPrice($price);
736
737
        $this->objectManager->flush();
738
    }
739
740
    /**
741
     * @Given /^(this product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
742
     * @Given /^the ("[^"]+" product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
743
     * @Given /^(it)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
744
     */
745
    public function thisProductHasAnImageWithType(ProductInterface $product, $imagePath, $imageType)
746
    {
747
        $filesPath = $this->getParameter('files_path');
748
749
        /** @var ImageInterface $productImage */
750
        $productImage = $this->productImageFactory->createNew();
751
        $productImage->setFile(new UploadedFile($filesPath.$imagePath, basename($imagePath)));
752
        $productImage->setType($imageType);
753
        $this->imageUploader->upload($productImage);
754
755
        $product->addImage($productImage);
756
757
        $this->objectManager->flush($product);
758
    }
759
760
    /**
761
     * @Given /^(this product) belongs to ("([^"]+)" shipping category)$/
762
     * @Given product :product shipping category has been changed to :shippingCategory
763
     */
764
    public function thisProductBelongsToShippingCategory(ProductInterface $product, ShippingCategoryInterface $shippingCategory)
765
    {
766
        $product->getVariants()->first()->setShippingCategory($shippingCategory);
767
        $this->objectManager->flush();
768
    }
769
770
    /**
771
     * @Given /^(this product) has been disabled$/
772
     */
773
    public function thisProductHasBeenDisabled(ProductInterface $product)
774
    {
775
        $product->disable();
776
        $this->objectManager->flush();
777
    }
778
779
    /**
780
     * @param string $price
781
     *
782
     * @return int
783
     */
784
    private function getPriceFromString($price)
785
    {
786
        return (int) round($price * 100, 2);
787
    }
788
789
    /**
790
     * @param string $productName
791
     * @param int $price
792
     * @param ChannelInterface|null $channel
793
     *
794
     * @return ProductInterface
795
     */
796
    private function createProduct($productName, $price = 100, ChannelInterface $channel = null)
797
    {
798
        if (null === $channel && $this->sharedStorage->has('channel')) {
799
            $channel = $this->sharedStorage->get('channel');
800
        }
801
802
        /** @var ProductInterface $product */
803
        $product = $this->productFactory->createWithVariant();
804
805
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
806
        $product->setName($productName);
807
        $product->setSlug($this->slugGenerator->generate($productName));
808
809
        if (null !== $channel) {
810
            $product->addChannel($channel);
811
812
            foreach ($channel->getLocales() as $locale) {
813
                $product->setFallbackLocale($locale->getCode());
814
                $product->setCurrentLocale($locale->getCode());
815
816
                $product->setName($productName);
817
                $product->setSlug($this->slugGenerator->generate($productName));
818
            }
819
        }
820
821
        /** @var ProductVariantInterface $productVariant */
822
        $productVariant = $this->defaultVariantResolver->getVariant($product);
823
824
        if (null !== $channel) {
825
            $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
826
        }
827
828
        $productVariant->setCode($product->getCode());
829
        $productVariant->setName($product->getName());
830
831
        return $product;
832
    }
833
834
    /**
835
     * @param ProductOptionInterface $option
836
     * @param string $value
837
     * @param string $code
838
     *
839
     * @return ProductOptionValueInterface
840
     */
841
    private function addProductOption(ProductOptionInterface $option, $value, $code)
842
    {
843
        /** @var ProductOptionValueInterface $optionValue */
844
        $optionValue = $this->productOptionValueFactory->createNew();
845
846
        $optionValue->setValue($value);
847
        $optionValue->setCode($code);
848
        $optionValue->setOption($option);
849
850
        $option->addValue($optionValue);
851
852
        return $optionValue;
853
    }
854
855
    /**
856
     * @param ProductInterface $product
857
     */
858
    private function saveProduct(ProductInterface $product)
859
    {
860
        $this->productRepository->add($product);
861
        $this->sharedStorage->set('product', $product);
862
    }
863
864
    /**
865
     * @param string $name
866
     *
867
     * @return NodeElement
868
     */
869
    private function getParameter($name)
870
    {
871
        return isset($this->minkParameters[$name]) ? $this->minkParameters[$name] : null;
872
    }
873
874
    /**
875
     * @param ProductInterface $product
876
     * @param $productVariantName
877
     * @param int $price
878
     * @param string $code
879
     * @param ChannelInterface $channel
0 ignored issues
show
Documentation introduced by
Should the type for parameter $channel not be null|ChannelInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
880
     * @param int $position
0 ignored issues
show
Documentation introduced by
Should the type for parameter $position not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
881
     * @param bool $shippingRequired
882
     *
883
     * @return ProductVariantInterface
884
     */
885
    private function createProductVariant(
886
        ProductInterface $product,
887
        $productVariantName,
888
        $price,
889
        $code,
890
        ChannelInterface $channel = null,
891
        $position = null,
892
        $shippingRequired = true
893
    ) {
894
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);
895
896
        /** @var ProductVariantInterface $variant */
897
        $variant = $this->productVariantFactory->createNew();
898
899
        $variant->setName($productVariantName);
900
        $variant->setCode($code);
901
        $variant->setProduct($product);
902
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
903
        $variant->setPosition($position);
904
        $variant->setShippingRequired($shippingRequired);
905
906
        $product->addVariant($variant);
907
908
        $this->objectManager->flush();
909
        $this->sharedStorage->set('variant', $variant);
910
911
        return $variant;
912
    }
913
914
    /**
915
     * @param ProductInterface $product
916
     * @param string $name
917
     * @param string $locale
918
     */
919
    private function addProductTranslation(ProductInterface $product, $name, $locale)
920
    {
921
        /** @var ProductTranslationInterface|TranslationInterface $translation */
922
        $translation = $product->getTranslation($locale);
923
        if ($translation->getLocale() !== $locale) {
0 ignored issues
show
Bug introduced by
The method getLocale does only exist in Sylius\Component\Resourc...el\TranslationInterface, but not in Sylius\Component\Core\Mo...uctTranslationInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
924
            $translation = $this->productTranslationFactory->createNew();
925
        }
926
927
        $translation->setLocale($locale);
928
        $translation->setName($name);
929
        $translation->setSlug($this->slugGenerator->generate($name));
930
931
        $product->addTranslation($translation);
932
    }
933
934
    /**
935
     * @param ProductVariantInterface $productVariant
936
     * @param string $name
937
     * @param string $locale
938
     */
939
    private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)
940
    {
941
        /** @var ProductVariantTranslationInterface|TranslationInterface $translation */
942
        $translation = $this->productVariantTranslationFactory->createNew();
943
        $translation->setLocale($locale);
0 ignored issues
show
Bug introduced by
The method setLocale does only exist in Sylius\Component\Resourc...el\TranslationInterface, but not in Sylius\Component\Product...antTranslationInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
944
        $translation->setName($name);
0 ignored issues
show
Bug introduced by
The method setName does only exist in Sylius\Component\Product...antTranslationInterface, but not in Sylius\Component\Resourc...el\TranslationInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
945
946
        $productVariant->addTranslation($translation);
0 ignored issues
show
Bug introduced by
It seems like $translation defined by $this->productVariantTra...ionFactory->createNew() on line 942 can also be of type object<Sylius\Component\...ntTranslationInterface>; however, Sylius\Component\Resourc...rface::addTranslation() does only seem to accept object<Sylius\Component\...l\TranslationInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
947
    }
948
949
    /**
950
     * @param int $price
951
     * @param ChannelInterface|null $channel
952
     *
953
     * @return ChannelPricingInterface
954
     */
955
    private function createChannelPricingForChannel($price, ChannelInterface $channel = null)
956
    {
957
        /** @var ChannelPricingInterface $channelPricing */
958
        $channelPricing = $this->channelPricingFactory->createNew();
959
        $channelPricing->setPrice($price);
960
        $channelPricing->setChannelCode($channel->getCode());
0 ignored issues
show
Bug introduced by
It seems like $channel is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
961
962
        return $channelPricing;
963
    }
964
}
965