Completed
Push — travis-xenial ( 84a270...92e80e )
by Kamil
26:32 queued 04:30
created

ProductContext::productVariantHasOriginalPrice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\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 ("[^"]+" product variant) has original price at ("[^"]+")$/
883
     */
884
    public function productVariantHasOriginalPrice(ProductVariantInterface $productVariant, int $price): void
885
    {
886
        /** @var ChannelInterface $channel */
887
        $channel = $this->sharedStorage->get('channel');
888
889
        $productVariant->getChannelPricingForChannel($channel)->setOriginalPrice($price);
890
        $this->objectManager->flush();
891
    }
892
893
    /**
894
     * @Given the store has a product :productName in channel :channel
895
     * @Given the store also has a product :productName in channel :channel
896
     */
897
    public function theStoreHasAProductWithChannel(string $productName, ChannelInterface $channel): void
898
    {
899
        $product = $this->createProduct($productName, 0, $channel);
900
901
        $this->saveProduct($product);
902
    }
903
904
    /**
905
     * @Given /^the ("[^"]+" product variant) is enabled$/
906
     */
907
    public function theProductVariantIsEnabled(ProductVariantInterface $productVariant): void
908
    {
909
        $productVariant->setEnabled(true);
910
911
        $this->objectManager->flush();
912
    }
913
914
    /**
915
     * @Given /^the ("([^"]*)" product variant) is disabled$/
916
     */
917
    public function theProductVariantIsDisabled(ProductVariantInterface $productVariant): void
918
    {
919
        $productVariant->setEnabled(false);
920
921
        $this->objectManager->flush();
922
    }
923
924
    /**
925
     * @Given /^the ("([^"]*)" product) is enabled$/
926
     */
927
    public function theProductIsEnabled(ProductInterface $product): void
928
    {
929
        $product->setEnabled(true);
930
        Assert::count($product->getVariants(), 1);
931
932
        /** @var ProductVariantInterface $variant */
933
        $variant = $product->getVariants()->first();
934
        $variant->setEnabled(true);
935
936
        $this->objectManager->flush();
937
    }
938
939
    /**
940
     * @Given /^the ("([^"]*)" product) is disabled$/
941
     */
942
    public function theProductIsDisabled(ProductInterface $product): void
943
    {
944
        $product->setEnabled(false);
945
        Assert::count($product->getVariants(), 1);
946
947
        /** @var ProductVariantInterface $variant */
948
        $variant = $product->getVariants()->first();
949
        $variant->setEnabled(false);
950
951
        $this->objectManager->flush();
952
    }
953
954
    /**
955
     * @Given /^all (the product) variants with the "([^"]*)" ([^\s]+) are disabled$/
956
     */
957
    public function allTheProductVariantsWithTheColorAreDisabled(
958
        ProductInterface $product,
959
        string $optionValue,
960
        string $optionName
961
    ): void {
962
        foreach ($product->getVariants() as $variant) {
963
            foreach ($variant->getOptionValues() as $variantOptionValue) {
964
                if (
965
                    $variantOptionValue->getValue() === $optionValue &&
966
                    $variantOptionValue->getOption()->getCode() === StringInflector::nameToUppercaseCode($optionName)
967
                ) {
968
                    $variant->setEnabled(false);
969
                }
970
            }
971
        }
972
973
        $this->objectManager->flush();
974
    }
975
976
    /**
977
     * @Given /^the ("[^"]*" \w+ \/ "[^"]*" \w+ variant of product "[^"]*") is disabled$/
978
     * @Given /^(this variant) has been disabled$/
979
     */
980
    public function theSizeColorVariantOfThisProductIsDisabled(ProductVariantInterface $productVariant): void
981
    {
982
        $productVariant->setEnabled(false);
983
984
        $this->objectManager->flush();
985
    }
986
987
    /**
988
     * @Given /^all variants of (this product) are disabled$/
989
     */
990
    public function allVariantsOfThisProductAreDisabled(ProductInterface $product): void
991
    {
992
        foreach ($product->getVariants() as $variant) {
993
            $variant->setEnabled(false);
994
        }
995
996
        $this->objectManager->flush();
997
    }
998
999
    private function getPriceFromString(string $price): int
1000
    {
1001
        return (int) round((float) str_replace(['€', '£', '$'], '', $price) * 100, 2);
1002
    }
1003
1004
    /**
1005
     * @param string $productName
1006
     *
1007
     * @return ProductInterface
1008
     */
1009
    private function createProduct($productName, int $price = 100, ChannelInterface $channel = null)
1010
    {
1011
        if (null === $channel && $this->sharedStorage->has('channel')) {
1012
            $channel = $this->sharedStorage->get('channel');
1013
        }
1014
1015
        /** @var ProductInterface $product */
1016
        $product = $this->productFactory->createWithVariant();
1017
1018
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
1019
        $product->setName($productName);
1020
        $product->setSlug($this->slugGenerator->generate($productName));
1021
1022
        if (null !== $channel) {
1023
            $product->addChannel($channel);
1024
1025
            foreach ($channel->getLocales() as $locale) {
1026
                $product->setFallbackLocale($locale->getCode());
1027
                $product->setCurrentLocale($locale->getCode());
1028
1029
                $product->setName($productName);
1030
                $product->setSlug($this->slugGenerator->generate($productName));
1031
            }
1032
        }
1033
1034
        /** @var ProductVariantInterface $productVariant */
1035
        $productVariant = $this->defaultVariantResolver->getVariant($product);
1036
1037
        if (null !== $channel) {
1038
            $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
1039
        }
1040
1041
        $productVariant->setCode($product->getCode());
1042
        $productVariant->setName($product->getName());
1043
1044
        return $product;
1045
    }
1046
1047
    /**
1048
     * @param string $value
1049
     * @param string $code
1050
     *
1051
     * @return ProductOptionValueInterface
1052
     */
1053
    private function addProductOption(ProductOptionInterface $option, $value, $code)
1054
    {
1055
        /** @var ProductOptionValueInterface $optionValue */
1056
        $optionValue = $this->productOptionValueFactory->createNew();
1057
1058
        $optionValue->setValue($value);
1059
        $optionValue->setCode($code);
1060
        $optionValue->setOption($option);
1061
1062
        $option->addValue($optionValue);
1063
1064
        return $optionValue;
1065
    }
1066
1067
    private function saveProduct(ProductInterface $product)
1068
    {
1069
        $this->productRepository->add($product);
1070
        $this->sharedStorage->set('product', $product);
1071
    }
1072
1073
    /**
1074
     * @param string $name
1075
     *
1076
     * @return NodeElement
1077
     */
1078
    private function getParameter($name)
1079
    {
1080
        return $this->minkParameters[$name] ?? null;
1081
    }
1082
1083
    /**
1084
     * @param string $productVariantName
1085
     * @param int $price
1086
     * @param string $code
1087
     * @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...
1088
     * @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...
1089
     * @param bool $shippingRequired
1090
     *
1091
     * @return ProductVariantInterface
1092
     */
1093
    private function createProductVariant(
1094
        ProductInterface $product,
1095
        $productVariantName,
1096
        $price,
1097
        $code,
1098
        ChannelInterface $channel = null,
1099
        $position = null,
1100
        $shippingRequired = true,
1101
        int $currentStock = 0
1102
    ) {
1103
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);
1104
1105
        /** @var ProductVariantInterface $variant */
1106
        $variant = $this->productVariantFactory->createNew();
1107
1108
        $variant->setName($productVariantName);
1109
        $variant->setCode($code);
1110
        $variant->setProduct($product);
1111
        $variant->setOnHand($currentStock);
1112
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
1113
        $variant->setPosition((null === $position) ? null : (int) $position);
1114
        $variant->setShippingRequired($shippingRequired);
1115
1116
        $product->addVariant($variant);
1117
1118
        $this->objectManager->flush();
1119
        $this->sharedStorage->set('variant', $variant);
1120
1121
        return $variant;
1122
    }
1123
1124
    /**
1125
     * @param string $name
1126
     * @param string $locale
1127
     */
1128
    private function addProductTranslation(ProductInterface $product, $name, $locale)
1129
    {
1130
        /** @var ProductTranslationInterface $translation */
1131
        $translation = $product->getTranslation($locale);
1132
        if ($translation->getLocale() !== $locale) {
1133
            /** @var ProductTranslationInterface $translation */
1134
            $translation = $this->productTranslationFactory->createNew();
1135
        }
1136
1137
        $translation->setLocale($locale);
1138
        $translation->setName($name);
1139
        $translation->setSlug($this->slugGenerator->generate($name));
1140
1141
        $product->addTranslation($translation);
1142
    }
1143
1144
    /**
1145
     * @param string $name
1146
     * @param string $locale
1147
     */
1148
    private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)
1149
    {
1150
        /** @var ProductVariantTranslationInterface $translation */
1151
        $translation = $this->productVariantTranslationFactory->createNew();
1152
        $translation->setLocale($locale);
1153
        $translation->setName($name);
1154
1155
        $productVariant->addTranslation($translation);
1156
    }
1157
1158
    /**
1159
     * @return ChannelPricingInterface
1160
     */
1161
    private function createChannelPricingForChannel(int $price, ChannelInterface $channel = null)
1162
    {
1163
        /** @var ChannelPricingInterface $channelPricing */
1164
        $channelPricing = $this->channelPricingFactory->createNew();
1165
        $channelPricing->setPrice($price);
1166
        $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...
1167
1168
        return $channelPricing;
1169
    }
1170
1171
    private function addOptionToProduct(ProductInterface $product, string $optionName, array $values): void
1172
    {
1173
        /** @var ProductOptionInterface $option */
1174
        $option = $this->productOptionFactory->createNew();
1175
1176
        $option->setName($optionName);
1177
        $option->setCode(StringInflector::nameToUppercaseCode($optionName));
1178
1179
        $this->sharedStorage->set(sprintf('%s_option', $optionName), $option);
1180
1181
        foreach ($values as $value) {
1182
            $optionValue = $this->addProductOption($option, $value, StringInflector::nameToUppercaseCode($value));
1183
            $this->sharedStorage->set(sprintf('%s_option_%s_value', $value, strtolower($optionName)), $optionValue);
1184
        }
1185
1186
        $product->addOption($option);
1187
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_MATCH);
1188
1189
        $this->objectManager->persist($option);
1190
        $this->objectManager->flush();
1191
    }
1192
1193
    private function createProductImage(
1194
        ProductInterface $product,
1195
        string $imagePath,
1196
        string $imageType,
1197
        ?ProductVariantInterface $variant = null
1198
    ): void {
1199
        $filesPath = $this->getParameter('files_path');
1200
1201
        /** @var ProductImageInterface $productImage */
1202
        $productImage = $this->productImageFactory->createNew();
1203
        $productImage->setFile(new UploadedFile($filesPath . $imagePath, basename($imagePath)));
1204
        $productImage->setType($imageType);
1205
1206
        if (null !== $variant) {
1207
            $productImage->addProductVariant($variant);
1208
        }
1209
1210
        $this->imageUploader->upload($productImage);
1211
1212
        $product->addImage($productImage);
1213
1214
        $this->objectManager->persist($product);
1215
        $this->objectManager->flush();
1216
    }
1217
}
1218