Completed
Push — taxon-parent-form ( 63b2c2 )
by Kamil
21:22
created

ProductContext::theProductHasVariantWithCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
868
            $translation = $this->productTranslationFactory->createNew();
869
        }
870
871
        $translation->setLocale($locale);
872
        $translation->setName($name);
873
        $translation->setSlug($this->slugGenerator->generate($name));
874
875
        $product->addTranslation($translation);
876
    }
877
878
    /**
879
     * @param ProductVariantInterface $productVariant
880
     * @param string $name
881
     * @param string $locale
882
     */
883
    private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)
884
    {
885
        /** @var ProductVariantTranslationInterface|TranslationInterface $translation */
886
        $translation = $this->productVariantTranslationFactory->createNew();
887
        $translation->setLocale($locale);
0 ignored issues
show
Bug introduced by
The method setLocale does only exist in Sylius\Component\Resourc...el\TranslationInterface, but not in Sylius\Component\Product...antTranslationInterface.

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

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

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

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

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

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

    return array();
}

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

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

Loading history...
891
    }
892
893
    /**
894
     * @param int $price
895
     * @param ChannelInterface|null $channel
896
     *
897
     * @return ChannelPricingInterface
898
     */
899
    private function createChannelPricingForChannel($price, ChannelInterface $channel = null)
900
    {
901
        /** @var ChannelPricingInterface $channelPricing */
902
        $channelPricing = $this->channelPricingFactory->createNew();
903
        $channelPricing->setPrice($price);
904
        $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...
905
906
        return $channelPricing;
907
    }
908
}
909