Completed
Push — allow-no-default-tax-zone-in-c... ( 67cea0 )
by Kamil
06:23
created

ProductContext::createProductImage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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