Completed
Push — master ( 8da333...8f94e6 )
by Kamil
05:27 queued 12s
created

ProductContext   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 1067
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 94
lcom 1
cbo 16
dl 0
loc 1067
rs 1.332
c 0
b 0
f 0

66 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 43 3
A storeHasAProductPricedAt() 0 6 1
A thisProductIsAlsoPricedAtInChannel() 0 10 1
A thisProductIsAlsoAvailableInChannel() 0 4 1
A thisProductIsAlsoUnavailableInChannel() 0 5 1
A storeHasProductWithCode() 0 8 1
A storeHasAProductPricedAtAvailableInChannels() 0 15 3
A thisProductIsNamedIn() 0 6 1
A theStoreHasProductNamedInAndIn() 0 11 2
A storeHasAConfigurableProduct() 0 26 5
A theStoreHasProducts() 0 6 2
A thisChannelHasProducts() 0 8 2
A theProductHasVariantPricedAt() 0 14 1
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 9 1
A thisProductHasOptionWithValues() 0 4 1
A thisProductHasAnOptionWithoutAnyValues() 0 4 1
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
A thisProductHasAllPossibleVariants() 0 29 5
A thereAreItemsOfProductInVariantAvailableInTheInventory() 0 7 1
A theProductVariantIsTrackedByTheInventory() 0 6 1
A theProductChangedItsPriceTo() 0 9 1
A thisProductHasAnImageWithType() 0 4 1
A thisProductHasAnImageWithTypeForVariant() 0 8 1
A thisProductBelongsToShippingCategory() 0 5 1
A thisProductHasBeenDisabled() 0 5 1
A theProductWasRenamedTo() 0 6 1
A thisProductDoesNotRequireShipping() 0 9 2
A productCodeIs() 0 6 1
A productHasDimensions() 0 11 1
A productHasSlug() 0 6 1
A descriptionOfProductIs() 0 6 1
A metaKeywordsOfProductIs() 0 6 1
A shortDescriptionOfProductIs() 0 6 1
A theProductHasOriginalPrice() 0 11 1
A productHasOption() 0 16 1
A productHasVariant() 0 8 1
A theStoreHasAProductWithChannel() 0 6 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 1
A createProductVariant() 0 30 2
A addProductTranslation() 0 15 2
A addProductVariantTranslation() 0 9 1
A createChannelPricingForChannel() 0 9 1
A addOptionToProduct() 0 21 2
A createProductImage() 0 24 2

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
declare(strict_types=1);
13
14
namespace Sylius\Behat\Context\Setup;
15
16
use Behat\Behat\Context\Context;
17
use Behat\Gherkin\Node\TableNode;
18
use Behat\Mink\Element\NodeElement;
19
use Doctrine\Common\Persistence\ObjectManager;
20
use Sylius\Behat\Service\SharedStorageInterface;
21
use Sylius\Component\Core\Formatter\StringInflector;
22
use Sylius\Component\Core\Model\ChannelInterface;
23
use Sylius\Component\Core\Model\ChannelPricingInterface;
24
use Sylius\Component\Core\Model\ProductImageInterface;
25
use Sylius\Component\Core\Model\ProductInterface;
26
use Sylius\Component\Core\Model\ProductTranslationInterface;
27
use Sylius\Component\Core\Model\ProductVariantInterface;
28
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
29
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
30
use Sylius\Component\Product\Factory\ProductFactoryInterface;
31
use Sylius\Component\Product\Generator\ProductVariantGeneratorInterface;
32
use Sylius\Component\Product\Generator\SlugGeneratorInterface;
33
use Sylius\Component\Product\Model\ProductOption;
34
use Sylius\Component\Product\Model\ProductOptionInterface;
35
use Sylius\Component\Product\Model\ProductOptionValueInterface;
36
use Sylius\Component\Product\Model\ProductVariantTranslationInterface;
37
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
38
use Sylius\Component\Resource\Factory\FactoryInterface;
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
final class ProductContext implements Context
45
{
46
    /** @var SharedStorageInterface */
47
    private $sharedStorage;
48
49
    /** @var ProductRepositoryInterface */
50
    private $productRepository;
51
52
    /** @var ProductFactoryInterface */
53
    private $productFactory;
54
55
    /** @var FactoryInterface */
56
    private $productTranslationFactory;
57
58
    /** @var FactoryInterface */
59
    private $productVariantFactory;
60
61
    /** @var FactoryInterface */
62
    private $productVariantTranslationFactory;
63
64
    /** @var FactoryInterface */
65
    private $channelPricingFactory;
66
67
    /** @var FactoryInterface */
68
    private $productOptionFactory;
69
70
    /** @var FactoryInterface */
71
    private $productOptionValueFactory;
72
73
    /** @var FactoryInterface */
74
    private $productImageFactory;
75
76
    /** @var ObjectManager */
77
    private $objectManager;
78
79
    /** @var ProductVariantGeneratorInterface */
80
    private $productVariantGenerator;
81
82
    /** @var ProductVariantResolverInterface */
83
    private $defaultVariantResolver;
84
85
    /** @var ImageUploaderInterface */
86
    private $imageUploader;
87
88
    /** @var SlugGeneratorInterface */
89
    private $slugGenerator;
90
91
    /** @var array */
92
    private $minkParameters;
93
94
    public function __construct(
95
        SharedStorageInterface $sharedStorage,
96
        ProductRepositoryInterface $productRepository,
97
        ProductFactoryInterface $productFactory,
98
        FactoryInterface $productTranslationFactory,
99
        FactoryInterface $productVariantFactory,
100
        FactoryInterface $productVariantTranslationFactory,
101
        FactoryInterface $channelPricingFactory,
102
        FactoryInterface $productOptionFactory,
103
        FactoryInterface $productOptionValueFactory,
104
        FactoryInterface $productImageFactory,
105
        ObjectManager $objectManager,
106
        ProductVariantGeneratorInterface $productVariantGenerator,
107
        ProductVariantResolverInterface $defaultVariantResolver,
108
        ImageUploaderInterface $imageUploader,
109
        SlugGeneratorInterface $slugGenerator,
110
        $minkParameters
111
    ) {
112
        if (!is_array($minkParameters) && !$minkParameters instanceof \ArrayAccess) {
113
            throw new \InvalidArgumentException(sprintf(
114
                '"$minkParameters" passed to "%s" has to be an array or implement "%s".',
115
                self::class,
116
                \ArrayAccess::class
117
            ));
118
        }
119
120
        $this->sharedStorage = $sharedStorage;
121
        $this->productRepository = $productRepository;
122
        $this->productFactory = $productFactory;
123
        $this->productTranslationFactory = $productTranslationFactory;
124
        $this->productVariantFactory = $productVariantFactory;
125
        $this->productVariantTranslationFactory = $productVariantTranslationFactory;
126
        $this->channelPricingFactory = $channelPricingFactory;
127
        $this->productOptionFactory = $productOptionFactory;
128
        $this->productOptionValueFactory = $productOptionValueFactory;
129
        $this->productImageFactory = $productImageFactory;
130
        $this->objectManager = $objectManager;
131
        $this->productVariantGenerator = $productVariantGenerator;
132
        $this->defaultVariantResolver = $defaultVariantResolver;
133
        $this->imageUploader = $imageUploader;
134
        $this->slugGenerator = $slugGenerator;
135
        $this->minkParameters = $minkParameters;
0 ignored issues
show
Documentation Bug introduced by
It seems like $minkParameters can also be of type object<ArrayAccess>. However, the property $minkParameters is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
136
    }
137
138
    /**
139
     * @Given the store has a product :productName
140
     * @Given the store has a :productName product
141
     * @Given I added a product :productName
142
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+")$/
143
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") in ("[^"]+" channel)$/
144
     */
145
    public function storeHasAProductPricedAt($productName, int $price = 100, ChannelInterface $channel = null)
146
    {
147
        $product = $this->createProduct($productName, $price, $channel);
148
149
        $this->saveProduct($product);
150
    }
151
152
    /**
153
     * @Given /^(this product) is(?:| also) priced at ("[^"]+") in ("[^"]+" channel)$/
154
     */
155
    public function thisProductIsAlsoPricedAtInChannel(ProductInterface $product, int $price, ChannelInterface $channel)
156
    {
157
        $product->addChannel($channel);
158
159
        /** @var ProductVariantInterface $productVariant */
160
        $productVariant = $this->defaultVariantResolver->getVariant($product);
161
        $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
162
163
        $this->objectManager->flush();
164
    }
165
166
    /**
167
     * @Given /^(this product) is(?:| also) available in ("[^"]+" channel)$/
168
     * @Given /^(this product) is(?:| also) available in the ("[^"]+" channel)$/
169
     */
170
    public function thisProductIsAlsoAvailableInChannel(ProductInterface $product, ChannelInterface $channel): void
171
    {
172
        $this->thisProductIsAlsoPricedAtInChannel($product, 0, $channel);
173
    }
174
175
    /**
176
     * @Given /^(this product) is(?:| also) unavailable in ("[^"]+" channel)$/
177
     */
178
    public function thisProductIsAlsoUnavailableInChannel(ProductInterface $product, ChannelInterface $channel): void
179
    {
180
        $product->removeChannel($channel);
181
        $this->objectManager->flush();
182
    }
183
184
    /**
185
     * @Given the store( also) has a product :productName with code :code
186
     * @Given the store( also) has a product :productName with code :code, created at :date
187
     */
188
    public function storeHasProductWithCode($productName, $code, $date = 'now')
189
    {
190
        $product = $this->createProduct($productName);
191
        $product->setCreatedAt(new \DateTime($date));
192
        $product->setCode($code);
193
194
        $this->saveProduct($product);
195
    }
196
197
    /**
198
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") available in (channel "[^"]+") and (channel "[^"]+")$/
199
     */
200
    public function storeHasAProductPricedAtAvailableInChannels($productName, int $price = 100, ...$channels)
201
    {
202
        $product = $this->createProduct($productName, $price);
203
        /** @var ProductVariantInterface $productVariant */
204
        $productVariant = $this->defaultVariantResolver->getVariant($product);
205
206
        foreach ($channels as $channel) {
207
            $product->addChannel($channel);
208
            if (!$productVariant->hasChannelPricingForChannel($channel)) {
209
                $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
210
            }
211
        }
212
213
        $this->saveProduct($product);
214
    }
215
216
    /**
217
     * @Given /^(this product) is named "([^"]+)" (in the "([^"]+)" locale)$/
218
     * @Given /^the (product "[^"]+") is named "([^"]+)" (in the "([^"]+)" locale)$/
219
     */
220
    public function thisProductIsNamedIn(ProductInterface $product, $name, $locale)
221
    {
222
        $this->addProductTranslation($product, $name, $locale);
223
224
        $this->objectManager->flush();
225
    }
226
227
    /**
228
     * @Given /^the store has a product named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
229
     */
230
    public function theStoreHasProductNamedInAndIn($firstName, $firstLocale, $secondName, $secondLocale)
231
    {
232
        $product = $this->createProduct($firstName);
233
234
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
235
        foreach ($names as $name => $locale) {
236
            $this->addProductTranslation($product, $name, $locale);
237
        }
238
239
        $this->saveProduct($product);
240
    }
241
242
    /**
243
     * @Given /^the store has(?:| a| an) "([^"]+)" configurable product$/
244
     * @Given /^the store has(?:| a| an) "([^"]+)" configurable product with "([^"]+)" slug$/
245
     */
246
    public function storeHasAConfigurableProduct($productName, $slug = null)
247
    {
248
        /** @var ChannelInterface|null $channel */
249
        $channel = null;
250
        if ($this->sharedStorage->has('channel')) {
251
            $channel = $this->sharedStorage->get('channel');
252
        }
253
254
        /** @var ProductInterface $product */
255
        $product = $this->productFactory->createNew();
256
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
257
258
        if (null !== $channel) {
259
            $product->addChannel($channel);
260
261
            foreach ($channel->getLocales() as $locale) {
262
                $product->setFallbackLocale($locale->getCode());
263
                $product->setCurrentLocale($locale->getCode());
264
265
                $product->setName($productName);
266
                $product->setSlug($slug ?: $this->slugGenerator->generate($productName));
267
            }
268
        }
269
270
        $this->saveProduct($product);
271
    }
272
273
    /**
274
     * @Given the store has( also) :firstProductName and :secondProductName products
275
     * @Given the store has( also) :firstProductName, :secondProductName and :thirdProductName products
276
     * @Given the store has( also) :firstProductName, :secondProductName, :thirdProductName and :fourthProductName products
277
     */
278
    public function theStoreHasProducts(...$productsNames)
279
    {
280
        foreach ($productsNames as $productName) {
281
            $this->saveProduct($this->createProduct($productName));
282
        }
283
    }
284
285
    /**
286
     * @Given /^(this channel) has "([^"]+)", "([^"]+)", "([^"]+)" and "([^"]+)" products$/
287
     */
288
    public function thisChannelHasProducts(ChannelInterface $channel, ...$productsNames)
289
    {
290
        foreach ($productsNames as $productName) {
291
            $product = $this->createProduct($productName, 0, $channel);
292
293
            $this->saveProduct($product);
294
        }
295
    }
296
297
    /**
298
     * @Given /^the (product "[^"]+") has(?:| a) "([^"]+)" variant priced at ("[^"]+")$/
299
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+")$/
300
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") in ("([^"]+)" channel)$/
301
     */
302
    public function theProductHasVariantPricedAt(
303
        ProductInterface $product,
304
        $productVariantName,
305
        $price,
306
        ChannelInterface $channel = null
307
    ) {
308
        $this->createProductVariant(
309
            $product,
310
            $productVariantName,
311
            $price,
312
            StringInflector::nameToUppercaseCode($productVariantName),
313
            $channel ?? $this->sharedStorage->get('channel')
314
        );
315
    }
316
317
    /**
318
     * @Given /^the (product "[^"]+") has(?:| a| an) "([^"]+)" variant$/
319
     * @Given /^(this product) has(?:| a| an) "([^"]+)" variant$/
320
     * @Given /^(this product) has "([^"]+)", "([^"]+)" and "([^"]+)" variants$/
321
     */
322
    public function theProductHasVariants(ProductInterface $product, ...$variantNames)
323
    {
324
        $channel = $this->sharedStorage->get('channel');
325
326
        foreach ($variantNames as $name) {
327
            $this->createProductVariant(
328
                $product,
329
                $name,
330
                0,
331
                StringInflector::nameToUppercaseCode($name),
332
                $channel
333
            );
334
        }
335
    }
336
337
    /**
338
     * @Given /^the (product "[^"]+")(?:| also) has a nameless variant with code "([^"]+)"$/
339
     * @Given /^(this product)(?:| also) has a nameless variant with code "([^"]+)"$/
340
     * @Given /^(it)(?:| also) has a nameless variant with code "([^"]+)"$/
341
     */
342
    public function theProductHasNamelessVariantWithCode(ProductInterface $product, $variantCode)
343
    {
344
        $channel = $this->sharedStorage->get('channel');
345
346
        $this->createProductVariant($product, null, 0, $variantCode, $channel);
347
    }
348
349
    /**
350
     * @Given /^the (product "[^"]+")(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
351
     * @Given /^(this product)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
352
     * @Given /^(it)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
353
     */
354
    public function theProductHasVariantWithCode(ProductInterface $product, $variantName, $variantCode)
355
    {
356
        $channel = $this->sharedStorage->get('channel');
357
358
        $this->createProductVariant($product, $variantName, 0, $variantCode, $channel);
359
    }
360
361
    /**
362
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") which does not require shipping$/
363
     */
364
    public function theProductHasVariantWhichDoesNotRequireShipping(
365
        ProductInterface $product,
366
        $productVariantName,
367
        $price
368
    ) {
369
        $this->createProductVariant(
370
            $product,
371
            $productVariantName,
372
            $price,
373
            StringInflector::nameToUppercaseCode($productVariantName),
374
            $this->sharedStorage->get('channel'),
375
            null,
376
            false
377
        );
378
    }
379
380
    /**
381
     * @Given /^the (product "[^"]+") has(?:| also)(?:| a| an) "([^"]+)" variant$/
382
     * @Given /^the (product "[^"]+") has(?:| also)(?:| a| an) "([^"]+)" variant at position ([^"]+)$/
383
     * @Given /^(this product) has(?:| also)(?:| a| an) "([^"]+)" variant at position ([^"]+)$/
384
     */
385
    public function theProductHasVariantAtPosition(
386
        ProductInterface $product,
387
        $productVariantName,
388
        $position = null
389
    ) {
390
        $this->createProductVariant(
391
            $product,
392
            $productVariantName,
393
            0,
394
            StringInflector::nameToUppercaseCode($productVariantName),
395
            $this->sharedStorage->get('channel'),
396
            $position
397
        );
398
    }
399
400
    /**
401
     * @Given /^(this variant) is also priced at ("[^"]+") in ("([^"]+)" channel)$/
402
     */
403
    public function thisVariantIsAlsoPricedAtInChannel(ProductVariantInterface $productVariant, string $price, ChannelInterface $channel)
404
    {
405
        $productVariant->addChannelPricing($this->createChannelPricingForChannel(
406
            $this->getPriceFromString(str_replace(['$', '€', '£'], '', $price)),
407
            $channel
408
        ));
409
410
        $this->objectManager->flush();
411
    }
412
413
    /**
414
     * @Given /^(it|this product) has(?:| also) variant named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
415
     */
416
    public function itHasVariantNamedInAndIn(ProductInterface $product, $firstName, $firstLocale, $secondName, $secondLocale)
417
    {
418
        $productVariant = $this->createProductVariant(
419
            $product,
420
            $firstName,
421
            100,
422
            StringInflector::nameToUppercaseCode($firstName),
423
            $this->sharedStorage->get('channel')
424
        );
425
426
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
427
        foreach ($names as $name => $locale) {
428
            $this->addProductVariantTranslation($productVariant, $name, $locale);
429
        }
430
431
        $this->objectManager->flush();
432
    }
433
434
    /**
435
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") identified by "([^"]+)"$/
436
     */
437
    public function theProductHasVariantPricedAtIdentifiedBy(
438
        ProductInterface $product,
439
        $productVariantName,
440
        $price,
441
        $code
442
    ) {
443
        $this->createProductVariant($product, $productVariantName, $price, $code, $this->sharedStorage->get('channel'));
444
    }
445
446
    /**
447
     * @Given /^(this product) only variant was renamed to "([^"]+)"$/
448
     */
449
    public function productOnlyVariantWasRenamed(ProductInterface $product, $variantName)
450
    {
451
        Assert::true($product->isSimple());
452
453
        /** @var ProductVariantInterface $productVariant */
454
        $productVariant = $product->getVariants()->first();
455
        $productVariant->setName($variantName);
456
457
        $this->objectManager->flush();
458
    }
459
460
    /**
461
     * @Given /^there is product "([^"]+)" available in ((?:this|that|"[^"]+") channel)$/
462
     * @Given /^the store has a product "([^"]+)" available in ("([^"]+)" channel)$/
463
     */
464
    public function thereIsProductAvailableInGivenChannel($productName, ChannelInterface $channel)
465
    {
466
        $product = $this->createProduct($productName, 0, $channel);
467
468
        $this->saveProduct($product);
469
    }
470
471
    /**
472
     * @Given /^([^"]+) belongs to ("[^"]+" tax category)$/
473
     * @Given the product :product belongs to :taxCategory tax category
474
     */
475
    public function productBelongsToTaxCategory(ProductInterface $product, TaxCategoryInterface $taxCategory)
476
    {
477
        /** @var ProductVariantInterface $variant */
478
        $variant = $this->defaultVariantResolver->getVariant($product);
479
        $variant->setTaxCategory($taxCategory);
480
481
        $this->objectManager->flush();
482
    }
483
484
    /**
485
     * @Given /^(it) comes in the following variations:$/
486
     */
487
    public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)
488
    {
489
        $channel = $this->sharedStorage->get('channel');
490
491
        foreach ($table->getHash() as $variantHash) {
492
            /** @var ProductVariantInterface $variant */
493
            $variant = $this->productVariantFactory->createNew();
494
495
            $variant->setName($variantHash['name']);
496
            $variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name']));
497
            $variant->addChannelPricing($this->createChannelPricingForChannel(
498
                $this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])),
499
                $channel
500
            ));
501
502
            $variant->setProduct($product);
503
            $product->addVariant($variant);
504
        }
505
506
        $this->objectManager->flush();
507
    }
508
509
    /**
510
     * @Given /^("[^"]+" variant of product "[^"]+") belongs to ("[^"]+" tax category)$/
511
     */
512
    public function productVariantBelongsToTaxCategory(
513
        ProductVariantInterface $productVariant,
514
        TaxCategoryInterface $taxCategory
515
    ) {
516
        $productVariant->setTaxCategory($taxCategory);
517
518
        $this->objectManager->persist($productVariant);
519
        $this->objectManager->flush();
520
    }
521
522
    /**
523
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)" and "([^"]+)"$/
524
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)", "([^"]+)" and "([^"]+)"$/
525
     */
526
    public function thisProductHasOptionWithValues(ProductInterface $product, $optionName, ...$values): void
527
    {
528
        $this->addOptionToProduct($product, $optionName, $values);
529
    }
530
531
    /**
532
     * @Given /^(this product) has an option "([^"]*)" without any values$/
533
     */
534
    public function thisProductHasAnOptionWithoutAnyValues(ProductInterface $product, string $optionName): void
535
    {
536
        $this->addOptionToProduct($product, $optionName, []);
537
    }
538
539
    /**
540
     * @Given /^there (?:is|are) (\d+) unit(?:|s) of (product "([^"]+)") available in the inventory$/
541
     */
542
    public function thereIsQuantityOfProducts($quantity, ProductInterface $product)
543
    {
544
        /** @var ProductVariantInterface $productVariant */
545
        $productVariant = $this->defaultVariantResolver->getVariant($product);
546
        $productVariant->setOnHand((int) $quantity);
547
548
        $this->objectManager->flush();
549
    }
550
551
    /**
552
     * @Given /^the (product "([^"]+)") is out of stock$/
553
     */
554
    public function theProductIsOutOfStock(ProductInterface $product)
555
    {
556
        /** @var ProductVariantInterface $productVariant */
557
        $productVariant = $this->defaultVariantResolver->getVariant($product);
558
        $productVariant->setTracked(true);
559
        $productVariant->setOnHand(0);
560
561
        $this->objectManager->flush();
562
    }
563
564
    /**
565
     * @When other customer has bought :quantity :product products by this time
566
     */
567
    public function otherCustomerHasBoughtProductsByThisTime($quantity, ProductInterface $product)
568
    {
569
        /** @var ProductVariantInterface $productVariant */
570
        $productVariant = $this->defaultVariantResolver->getVariant($product);
571
        $productVariant->setOnHand($productVariant->getOnHand() - $quantity);
572
573
        $this->objectManager->flush();
574
    }
575
576
    /**
577
     * @Given /^(this product) is tracked by the inventory$/
578
     * @Given /^(?:|the )("[^"]+" product) is(?:| also) tracked by the inventory$/
579
     */
580
    public function thisProductIsTrackedByTheInventory(ProductInterface $product)
581
    {
582
        /** @var ProductVariantInterface $productVariant */
583
        $productVariant = $this->defaultVariantResolver->getVariant($product);
584
        $productVariant->setTracked(true);
585
586
        $this->objectManager->flush();
587
    }
588
589
    /**
590
     * @Given /^(this product) is available in "([^"]+)" ([^"]+) priced at ("[^"]+")$/
591
     */
592
    public function thisProductIsAvailableInSize(ProductInterface $product, $optionValueName, $optionName, int $price)
593
    {
594
        /** @var ProductVariantInterface $variant */
595
        $variant = $this->productVariantFactory->createNew();
596
597
        $optionValue = $this->sharedStorage->get(sprintf('%s_option_%s_value', $optionValueName, $optionName));
598
599
        $variant->addOptionValue($optionValue);
600
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $this->sharedStorage->get('channel')));
601
        $variant->setCode(sprintf('%s_%s', $product->getCode(), $optionValueName));
602
        $variant->setName($product->getName());
603
604
        $product->addVariant($variant);
605
        $this->objectManager->flush();
606
    }
607
608
    /**
609
     * @Given the :product product's :optionValueName size belongs to :shippingCategory shipping category
610
     */
611
    public function thisProductSizeBelongsToShippingCategory(ProductInterface $product, $optionValueName, ShippingCategoryInterface $shippingCategory)
612
    {
613
        $code = sprintf('%s_%s', $product->getCode(), $optionValueName);
614
        /** @var ProductVariantInterface $productVariant */
615
        $productVariant = $product->getVariants()->filter(function ($variant) use ($code) {
616
            return $code === $variant->getCode();
617
        })->first();
618
619
        Assert::notNull($productVariant, sprintf('Product variant with given code %s not exists!', $code));
620
621
        $productVariant->setShippingCategory($shippingCategory);
622
        $this->objectManager->flush();
623
    }
624
625
    /**
626
     * @Given /^(this product) has (this product option)$/
627
     * @Given /^(this product) has (?:a|an) ("[^"]+" option)$/
628
     */
629
    public function thisProductHasThisProductOption(ProductInterface $product, ProductOptionInterface $option)
630
    {
631
        $product->addOption($option);
632
633
        $this->objectManager->flush();
634
    }
635
636
    /**
637
     * @Given /^(this product) has all possible variants$/
638
     */
639
    public function thisProductHasAllPossibleVariants(ProductInterface $product)
640
    {
641
        try {
642
            foreach ($product->getVariants() as $productVariant) {
643
                $product->removeVariant($productVariant);
644
            }
645
646
            $this->productVariantGenerator->generate($product);
647
        } catch (\InvalidArgumentException $exception) {
648
            /** @var ProductVariantInterface $productVariant */
649
            $productVariant = $this->productVariantFactory->createNew();
650
651
            $product->addVariant($productVariant);
652
        }
653
654
        $i = 0;
655
        /** @var ProductVariantInterface $productVariant */
656
        foreach ($product->getVariants() as $productVariant) {
657
            $productVariant->setCode(sprintf('%s-variant-%d', $product->getCode(), $i));
658
659
            foreach ($product->getChannels() as $channel) {
660
                $productVariant->addChannelPricing($this->createChannelPricingForChannel(1000, $channel));
661
            }
662
663
            ++$i;
664
        }
665
666
        $this->objectManager->flush();
667
    }
668
669
    /**
670
     * @Given /^there are ([^"]+) units of ("[^"]+" variant of product "[^"]+") available in the inventory$/
671
     */
672
    public function thereAreItemsOfProductInVariantAvailableInTheInventory($quantity, ProductVariantInterface $productVariant)
673
    {
674
        $productVariant->setTracked(true);
675
        $productVariant->setOnHand((int) $quantity);
676
677
        $this->objectManager->flush();
678
    }
679
680
    /**
681
     * @Given /^the ("[^"]+" product variant) is tracked by the inventory$/
682
     */
683
    public function theProductVariantIsTrackedByTheInventory(ProductVariantInterface $productVariant)
684
    {
685
        $productVariant->setTracked(true);
686
687
        $this->objectManager->flush();
688
    }
689
690
    /**
691
     * @Given /^(this product)'s price is ("[^"]+")$/
692
     * @Given /^the (product "[^"]+") changed its price to ("[^"]+")$/
693
     * @Given /^(this product) price has been changed to ("[^"]+")$/
694
     */
695
    public function theProductChangedItsPriceTo(ProductInterface $product, int $price)
696
    {
697
        /** @var ProductVariantInterface $productVariant */
698
        $productVariant = $this->defaultVariantResolver->getVariant($product);
699
        $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel'));
700
        $channelPricing->setPrice($price);
701
702
        $this->objectManager->flush();
703
    }
704
705
    /**
706
     * @Given /^(this product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
707
     * @Given /^the ("[^"]+" product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
708
     * @Given /^(it)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/
709
     */
710
    public function thisProductHasAnImageWithType(ProductInterface $product, $imagePath, $imageType)
711
    {
712
        $this->createProductImage($product, $imagePath, $imageType);
713
    }
714
715
    /**
716
     * @Given /^(this product) has an image "([^"]+)" with "([^"]+)" type for ("[^"]+" variant)$/
717
     */
718
    public function thisProductHasAnImageWithTypeForVariant(
719
        ProductInterface $product,
720
        string $imagePath,
721
        string $imageType,
722
        ProductVariantInterface $variant
723
    ): void {
724
        $this->createProductImage($product, $imagePath, $imageType, $variant);
725
    }
726
727
    /**
728
     * @Given /^(this product) belongs to ("([^"]+)" shipping category)$/
729
     * @Given product :product shipping category has been changed to :shippingCategory
730
     */
731
    public function thisProductBelongsToShippingCategory(ProductInterface $product, ShippingCategoryInterface $shippingCategory)
732
    {
733
        $product->getVariants()->first()->setShippingCategory($shippingCategory);
734
        $this->objectManager->flush();
735
    }
736
737
    /**
738
     * @Given /^(this product) has been disabled$/
739
     */
740
    public function thisProductHasBeenDisabled(ProductInterface $product)
741
    {
742
        $product->disable();
743
        $this->objectManager->flush();
744
    }
745
746
    /**
747
     * @Given the product :product was renamed to :productName
748
     */
749
    public function theProductWasRenamedTo(ProductInterface $product, string $productName): void
750
    {
751
        $product->setName($productName);
752
753
        $this->objectManager->flush();
754
    }
755
756
    /**
757
     * @Given /^(this product) does not require shipping$/
758
     */
759
    public function thisProductDoesNotRequireShipping(ProductInterface $product): void
760
    {
761
        /** @var ProductVariantInterface $variant */
762
        foreach ($product->getVariants() as $variant) {
763
            $variant->setShippingRequired(false);
764
        }
765
766
        $this->objectManager->flush();
767
    }
768
769
    /**
770
     * @Given product's :product code is :code
771
     */
772
    public function productCodeIs(ProductInterface $product, string $code): void
773
    {
774
        $product->setCode($code);
775
776
        $this->objectManager->flush();
777
    }
778
779
    /**
780
     * @Given the product :product has height :height, width :width, depth :depth, weight :weight
781
     */
782
    public function productHasDimensions(ProductInterface $product, float $height, float $width, float $depth, float $weight): void
783
    {
784
        /** @var ProductVariantInterface $productVariant */
785
        $productVariant = $this->defaultVariantResolver->getVariant($product);
786
        $productVariant->setWidth($width);
787
        $productVariant->setHeight($height);
788
        $productVariant->setDepth($depth);
789
        $productVariant->setWeight($weight);
790
791
        $this->objectManager->flush();
792
    }
793
794
    /**
795
     * @Given the product :product has the slug :slug
796
     */
797
    public function productHasSlug(ProductInterface $product, string $slug): void
798
    {
799
        $product->setSlug($slug);
800
801
        $this->objectManager->flush();
802
    }
803
804
    /**
805
     * @Given the description of product :product is :description
806
     */
807
    public function descriptionOfProductIs(ProductInterface $product, string $description): void
808
    {
809
        $product->setDescription($description);
810
811
        $this->objectManager->flush();
812
    }
813
814
    /**
815
     * @Given the meta keywords of product :product is :metaKeywords
816
     */
817
    public function metaKeywordsOfProductIs(ProductInterface $product, string $metaKeywords): void
818
    {
819
        $product->getTranslation()->setMetaKeywords($metaKeywords);
820
821
        $this->objectManager->flush();
822
    }
823
824
    /**
825
     * @Given the short description of product :product is :shortDescription
826
     */
827
    public function shortDescriptionOfProductIs(ProductInterface $product, string $shortDescription): void
828
    {
829
        $product->getTranslation()->setShortDescription($shortDescription);
830
831
        $this->objectManager->flush();
832
    }
833
834
    /**
835
     * @Given the product :product has original price :originalPrice
836
     */
837
    public function theProductHasOriginalPrice(ProductInterface $product, string $originalPrice): void
838
    {
839
        /** @var ProductVariantInterface $productVariant */
840
        $productVariant = $this->defaultVariantResolver->getVariant($product);
841
842
        /** @var ChannelPricingInterface $channelPricing */
843
        $channelPricing = $productVariant->getChannelPricings()->first();
844
        $channelPricing->setOriginalPrice($this->getPriceFromString($originalPrice));
845
846
        $this->objectManager->flush();
847
    }
848
849
    /**
850
     * @Given the product :product has option :productOption named :optionValue with code :optionCode
851
     */
852
    public function productHasOption(
853
        ProductInterface $product,
854
        ProductOption $productOption,
855
        string $optionValue,
856
        string $optionCode
857
    ): void {
858
        /** @var ProductOptionValueInterface $productOptionValue */
859
        $productOptionValue = $this->productOptionValueFactory->createNew();
860
        $productOptionValue->setCode($optionCode);
861
        $productOptionValue->setOption($productOption);
862
        $productOptionValue->setValue($optionValue);
863
        $productOption->addValue($productOptionValue);
864
        $product->addOption($productOption);
865
866
        $this->objectManager->flush();
867
    }
868
869
    /**
870
     * @Given the product :product has :productVariantName variant with code :code, price :price, current stock :currentStock
871
     */
872
    public function productHasVariant(ProductInterface $product, string $productVariantName, string $code, string $price, int $currentStock): void
873
    {
874
        /** @var ChannelInterface $channel */
875
        $channel = $this->sharedStorage->get('channel');
876
877
        $priceValue = $this->getPriceFromString($price);
878
        $this->createProductVariant($product, $productVariantName, $priceValue, $code, $channel, null, true, $currentStock);
879
    }
880
881
    /**
882
     * @Given the store has a product :productName in channel :channel
883
     * @Given the store also has a product :productName in channel :channel
884
     */
885
    public function theStoreHasAProductWithChannel(string $productName, ChannelInterface $channel): void
886
    {
887
        $product = $this->createProduct($productName, 0, $channel);
888
889
        $this->saveProduct($product);
890
    }
891
892
    private function getPriceFromString(string $price): int
893
    {
894
        return (int) round((float) str_replace(['€', '£', '$'], '', $price) * 100, 2);
895
    }
896
897
    /**
898
     * @param string $productName
899
     *
900
     * @return ProductInterface
901
     */
902
    private function createProduct($productName, int $price = 100, ChannelInterface $channel = null)
903
    {
904
        if (null === $channel && $this->sharedStorage->has('channel')) {
905
            $channel = $this->sharedStorage->get('channel');
906
        }
907
908
        /** @var ProductInterface $product */
909
        $product = $this->productFactory->createWithVariant();
910
911
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
912
        $product->setName($productName);
913
        $product->setSlug($this->slugGenerator->generate($productName));
914
915
        if (null !== $channel) {
916
            $product->addChannel($channel);
917
918
            foreach ($channel->getLocales() as $locale) {
919
                $product->setFallbackLocale($locale->getCode());
920
                $product->setCurrentLocale($locale->getCode());
921
922
                $product->setName($productName);
923
                $product->setSlug($this->slugGenerator->generate($productName));
924
            }
925
        }
926
927
        /** @var ProductVariantInterface $productVariant */
928
        $productVariant = $this->defaultVariantResolver->getVariant($product);
929
930
        if (null !== $channel) {
931
            $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
932
        }
933
934
        $productVariant->setCode($product->getCode());
935
        $productVariant->setName($product->getName());
936
937
        return $product;
938
    }
939
940
    /**
941
     * @param string $value
942
     * @param string $code
943
     *
944
     * @return ProductOptionValueInterface
945
     */
946
    private function addProductOption(ProductOptionInterface $option, $value, $code)
947
    {
948
        /** @var ProductOptionValueInterface $optionValue */
949
        $optionValue = $this->productOptionValueFactory->createNew();
950
951
        $optionValue->setValue($value);
952
        $optionValue->setCode($code);
953
        $optionValue->setOption($option);
954
955
        $option->addValue($optionValue);
956
957
        return $optionValue;
958
    }
959
960
    private function saveProduct(ProductInterface $product)
961
    {
962
        $this->productRepository->add($product);
963
        $this->sharedStorage->set('product', $product);
964
    }
965
966
    /**
967
     * @param string $name
968
     *
969
     * @return NodeElement
970
     */
971
    private function getParameter($name)
972
    {
973
        return $this->minkParameters[$name] ?? null;
974
    }
975
976
    /**
977
     * @param string $productVariantName
978
     * @param int $price
979
     * @param string $code
980
     * @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...
981
     * @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...
982
     * @param bool $shippingRequired
983
     *
984
     * @return ProductVariantInterface
985
     */
986
    private function createProductVariant(
987
        ProductInterface $product,
988
        $productVariantName,
989
        $price,
990
        $code,
991
        ChannelInterface $channel = null,
992
        $position = null,
993
        $shippingRequired = true,
994
        int $currentStock = 0
995
    ) {
996
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);
997
998
        /** @var ProductVariantInterface $variant */
999
        $variant = $this->productVariantFactory->createNew();
1000
1001
        $variant->setName($productVariantName);
1002
        $variant->setCode($code);
1003
        $variant->setProduct($product);
1004
        $variant->setOnHand($currentStock);
1005
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
1006
        $variant->setPosition((null === $position) ? null : (int) $position);
1007
        $variant->setShippingRequired($shippingRequired);
1008
1009
        $product->addVariant($variant);
1010
1011
        $this->objectManager->flush();
1012
        $this->sharedStorage->set('variant', $variant);
1013
1014
        return $variant;
1015
    }
1016
1017
    /**
1018
     * @param string $name
1019
     * @param string $locale
1020
     */
1021
    private function addProductTranslation(ProductInterface $product, $name, $locale)
1022
    {
1023
        /** @var ProductTranslationInterface $translation */
1024
        $translation = $product->getTranslation($locale);
1025
        if ($translation->getLocale() !== $locale) {
1026
            /** @var ProductTranslationInterface $translation */
1027
            $translation = $this->productTranslationFactory->createNew();
1028
        }
1029
1030
        $translation->setLocale($locale);
1031
        $translation->setName($name);
1032
        $translation->setSlug($this->slugGenerator->generate($name));
1033
1034
        $product->addTranslation($translation);
1035
    }
1036
1037
    /**
1038
     * @param string $name
1039
     * @param string $locale
1040
     */
1041
    private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)
1042
    {
1043
        /** @var ProductVariantTranslationInterface $translation */
1044
        $translation = $this->productVariantTranslationFactory->createNew();
1045
        $translation->setLocale($locale);
1046
        $translation->setName($name);
1047
1048
        $productVariant->addTranslation($translation);
1049
    }
1050
1051
    /**
1052
     * @return ChannelPricingInterface
1053
     */
1054
    private function createChannelPricingForChannel(int $price, ChannelInterface $channel = null)
1055
    {
1056
        /** @var ChannelPricingInterface $channelPricing */
1057
        $channelPricing = $this->channelPricingFactory->createNew();
1058
        $channelPricing->setPrice($price);
1059
        $channelPricing->setChannelCode($channel->getCode());
1060
1061
        return $channelPricing;
1062
    }
1063
1064
    private function addOptionToProduct(ProductInterface $product, string $optionName, array $values): void
1065
    {
1066
        /** @var ProductOptionInterface $option */
1067
        $option = $this->productOptionFactory->createNew();
1068
1069
        $option->setName($optionName);
1070
        $option->setCode(StringInflector::nameToUppercaseCode($optionName));
1071
1072
        $this->sharedStorage->set(sprintf('%s_option', $optionName), $option);
1073
1074
        foreach ($values as $value) {
1075
            $optionValue = $this->addProductOption($option, $value, StringInflector::nameToUppercaseCode($value));
1076
            $this->sharedStorage->set(sprintf('%s_option_%s_value', $value, strtolower($optionName)), $optionValue);
1077
        }
1078
1079
        $product->addOption($option);
1080
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_MATCH);
1081
1082
        $this->objectManager->persist($option);
1083
        $this->objectManager->flush();
1084
    }
1085
1086
    private function createProductImage(
1087
        ProductInterface $product,
1088
        string $imagePath,
1089
        string $imageType,
1090
        ?ProductVariantInterface $variant = null
1091
    ): void {
1092
        $filesPath = $this->getParameter('files_path');
1093
1094
        /** @var ProductImageInterface $productImage */
1095
        $productImage = $this->productImageFactory->createNew();
1096
        $productImage->setFile(new UploadedFile($filesPath . $imagePath, basename($imagePath)));
1097
        $productImage->setType($imageType);
1098
1099
        if (null !== $variant) {
1100
            $productImage->addProductVariant($variant);
1101
        }
1102
1103
        $this->imageUploader->upload($productImage);
1104
1105
        $product->addImage($productImage);
1106
1107
        $this->objectManager->persist($product);
1108
        $this->objectManager->flush();
1109
    }
1110
}
1111