Completed
Push — 1.7 ( 0983e5...497e44 )
by Kamil
05:26
created

ProductContext::shortDescriptionOfProductIs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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