Completed
Push — pagerfanta-fix ( 187c46...923c07 )
by Kamil
25:06 queued 03:45
created

ProductContext::createProductVariant()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 1
eloc 16
nc 1
nop 5
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\Component\Attribute\Factory\AttributeFactoryInterface;
19
use Sylius\Component\Core\Formatter\StringInflector;
20
use Sylius\Component\Core\Model\ChannelInterface;
21
use Sylius\Component\Core\Model\ChannelPricingInterface;
22
use Sylius\Component\Core\Model\ImageInterface;
23
use Sylius\Component\Core\Model\ProductInterface;
24
use Sylius\Component\Core\Model\ProductTranslationInterface;
25
use Sylius\Component\Core\Model\ProductVariantInterface;
26
use Sylius\Component\Core\Pricing\Calculators;
27
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
28
use Sylius\Behat\Service\SharedStorageInterface;
29
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
30
use Sylius\Component\Product\Factory\ProductFactoryInterface;
31
use Sylius\Component\Product\Generator\SlugGeneratorInterface;
32
use Sylius\Component\Product\Model\ProductAttributeInterface;
33
use Sylius\Component\Product\Model\ProductAttributeValueInterface;
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\Resource\Factory\FactoryInterface;
38
use Sylius\Component\Shipping\Model\ShippingCategoryInterface;
39
use Sylius\Component\Resource\Model\TranslationInterface;
40
use Sylius\Component\Taxation\Model\TaxCategoryInterface;
41
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
42
use Symfony\Component\HttpFoundation\File\UploadedFile;
43
use Webmozart\Assert\Assert;
44
45
/**
46
 * @author Arkadiusz Krakowiak <[email protected]>
47
 * @author Mateusz Zalewski <[email protected]>
48
 * @author Magdalena Banasiak <[email protected]>
49
 */
50
final class ProductContext implements Context
51
{
52
    /**
53
     * @var SharedStorageInterface
54
     */
55
    private $sharedStorage;
56
57
    /**
58
     * @var ProductRepositoryInterface
59
     */
60
    private $productRepository;
61
62
    /**
63
     * @var ProductFactoryInterface
64
     */
65
    private $productFactory;
66
67
    /**
68
     * @var FactoryInterface
69
     */
70
    private $productTranslationFactory;
71
72
    /**
73
     * @var AttributeFactoryInterface
74
     */
75
    private $productAttributeFactory;
76
77
    /**
78
     * @var FactoryInterface
79
     */
80
    private $productVariantFactory;
81
82
    /**
83
     * @var FactoryInterface
84
     */
85
    private $productVariantTranslationFactory;
86
87
    /**
88
     * @var FactoryInterface
89
     */
90
    private $channelPricingFactory;
91
92
    /**
93
     * @var FactoryInterface
94
     */
95
    private $attributeValueFactory;
96
97
    /**
98
     * @var FactoryInterface
99
     */
100
    private $productOptionFactory;
101
102
    /**
103
     * @var FactoryInterface
104
     */
105
    private $productOptionValueFactory;
106
107
    /**
108
     * @var FactoryInterface
109
     */
110
    private $productImageFactory;
111
112
    /**
113
     * @var ObjectManager
114
     */
115
    private $objectManager;
116
117
    /**
118
     * @var ProductVariantResolverInterface
119
     */
120
    private $defaultVariantResolver;
121
122
    /**
123
     * @var ImageUploaderInterface
124
     */
125
    private $imageUploader;
126
127
    /**
128
     * @var SlugGeneratorInterface
129
     */
130
    private $slugGenerator;
131
132
    /**
133
     * @var array
134
     */
135
    private $minkParameters;
136
137
    /**
138
     * @param SharedStorageInterface $sharedStorage
139
     * @param ProductRepositoryInterface $productRepository
140
     * @param ProductFactoryInterface $productFactory
141
     * @param FactoryInterface $productTranslationFactory
142
     * @param AttributeFactoryInterface $productAttributeFactory
143
     * @param FactoryInterface $attributeValueFactory
144
     * @param FactoryInterface $productVariantFactory
145
     * @param FactoryInterface $productVariantTranslationFactory
146
     * @param FactoryInterface $channelPricingFactory
147
     * @param FactoryInterface $productOptionFactory
148
     * @param FactoryInterface $productOptionValueFactory
149
     * @param FactoryInterface $productImageFactory
150
     * @param ObjectManager $objectManager
151
     * @param ProductVariantResolverInterface $defaultVariantResolver
152
     * @param ImageUploaderInterface $imageUploader
153
     * @param SlugGeneratorInterface $slugGenerator
154
     * @param array $minkParameters
155
     */
156
    public function __construct(
157
        SharedStorageInterface $sharedStorage,
158
        ProductRepositoryInterface $productRepository,
159
        ProductFactoryInterface $productFactory,
160
        FactoryInterface $productTranslationFactory,
161
        AttributeFactoryInterface $productAttributeFactory,
162
        FactoryInterface $attributeValueFactory,
163
        FactoryInterface $productVariantFactory,
164
        FactoryInterface $productVariantTranslationFactory,
165
        FactoryInterface $channelPricingFactory,
166
        FactoryInterface $productOptionFactory,
167
        FactoryInterface $productOptionValueFactory,
168
        FactoryInterface $productImageFactory,
169
        ObjectManager $objectManager,
170
        ProductVariantResolverInterface $defaultVariantResolver,
171
        ImageUploaderInterface $imageUploader,
172
        SlugGeneratorInterface $slugGenerator,
173
        array $minkParameters
174
    ) {
175
        $this->sharedStorage = $sharedStorage;
176
        $this->productRepository = $productRepository;
177
        $this->productFactory = $productFactory;
178
        $this->productTranslationFactory = $productTranslationFactory;
179
        $this->productAttributeFactory = $productAttributeFactory;
180
        $this->attributeValueFactory = $attributeValueFactory;
181
        $this->productVariantFactory = $productVariantFactory;
182
        $this->productVariantTranslationFactory = $productVariantTranslationFactory;
183
        $this->channelPricingFactory = $channelPricingFactory;
184
        $this->productOptionFactory = $productOptionFactory;
185
        $this->productOptionValueFactory = $productOptionValueFactory;
186
        $this->productImageFactory = $productImageFactory;
187
        $this->objectManager = $objectManager;
188
        $this->defaultVariantResolver = $defaultVariantResolver;
189
        $this->imageUploader = $imageUploader;
190
        $this->slugGenerator = $slugGenerator;
191
        $this->minkParameters = $minkParameters;
192
    }
193
194
    /**
195
     * @Given the store has a product :productName
196
     * @Given the store has a :productName product
197
     * @Given I added a product :productName
198
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+")$/
199
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") in ("[^"]+" channel)$/
200
     */
201
    public function storeHasAProductPricedAt($productName, $price = 100, ChannelInterface $channel = null)
202
    {
203
        if (null === $channel && $this->sharedStorage->has('channel')) {
204
            $channel = $this->sharedStorage->get('channel');
205
        }
206
        $product = $this->createProduct($productName, $price, null, $channel);
207
        $product->setDescription('Awesome '.$productName);
208
209
        if (null !== $channel) {
210
            $product->addChannel($channel);
211
        }
212
213
        $this->saveProduct($product);
214
    }
215
216
    /**
217
     * @Given /^(this product) is also priced at ("[^"]+") in ("[^"]+" channel)$/
218
     */
219
    public function thisProductIsAlsoPricedAtInChannel(ProductInterface $product, $price, ChannelInterface $channel)
220
    {
221
        $product->addChannel($channel);
222
223
        /** @var ProductVariantInterface $productVariant */
224
        $productVariant = $this->defaultVariantResolver->getVariant($product);
225
226
        /** @var ChannelPricingInterface $channelPricing */
227
        $channelPricing = $this->channelPricingFactory->createNew();
228
        $channelPricing->setPrice($price);
229
        $channelPricing->setChannel($channel);
230
231
        $productVariant->addChannelPricing($channelPricing);
232
233
        $this->objectManager->flush();
234
    }
235
236
    /**
237
     * @Given the store( also) has a product :productName with code :code
238
     * @Given the store( also) has a product :productName with code :code, created at :date
239
     */
240
    public function storeHasProductWithCode($productName, $code, $date = null)
241
    {
242
        $product = $this->createProduct($productName, 0, $date);
243
244
        $product->setCode($code);
245
246
        if ($this->sharedStorage->has('channel')) {
247
            $product->addChannel($this->sharedStorage->get('channel'));
248
        }
249
250
        $this->saveProduct($product);
251
    }
252
253
    /**
254
     * @Given /^the store(?:| also) has a product "([^"]+)" priced at ("[^"]+") available in (channel "[^"]+") and (channel "[^"]+")$/
255
     */
256
    public function storeHasAProductPricedAtAvailableInChannels($productName, $price = 100, ...$channels)
257
    {
258
        $product = $this->createProduct($productName, $price);
259
        /** @var ProductVariantInterface $productVariant */
260
        $productVariant = $this->defaultVariantResolver->getVariant($product);
261
262
        $product->setDescription('Awesome '.$productName);
263
264
        foreach ($channels as $channel) {
265
            $product->addChannel($channel);
266
            if (!$productVariant->hasChannelPricingForChannel($channel)) {
267
                $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
268
            }
269
        }
270
271
        $this->saveProduct($product);
272
    }
273
274
    /**
275
     * @Given /^(this product) is named "([^"]+)" (in the "([^"]+)" locale)$/
276
     * @Given /^the (product "[^"]+") is named "([^"]+)" (in the "([^"]+)" locale)$/
277
     */
278
    public function thisProductIsNamedIn(ProductInterface $product, $name, $locale)
279
    {
280
        $this->addProductTranslation($product, $name, $locale);
281
282
        $this->objectManager->flush();
283
    }
284
285
    /**
286
     * @Given /^the store has a product named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
287
     */
288
    public function theStoreHasProductNamedInAndIn($firstName, $firstLocale, $secondName, $secondLocale)
289
    {
290
        $product = $this->createProduct($firstName);
291
292
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
293
        foreach ($names as $name => $locale) {
294
            $this->addProductTranslation($product, $name, $locale);
295
        }
296
297
        $this->saveProduct($product);
298
    }
299
300
    /**
301
     * @Given the store has a :productName configurable product
302
     */
303
    public function storeHasAConfigurableProduct($productName)
304
    {
305
        /** @var ProductInterface $product */
306
        $product = $this->productFactory->createNew();
307
308
        $product->setName($productName);
309
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
310
        $product->setSlug($this->slugGenerator->generate($productName));
311
312
        $product->setDescription('Awesome '.$productName);
313
314
        if ($this->sharedStorage->has('channel')) {
315
            $channel = $this->sharedStorage->get('channel');
316
            $product->addChannel($channel);
317
        }
318
319
        $this->saveProduct($product);
320
    }
321
322
    /**
323
     * @Given the store has( also) :firstProductName and :secondProductName products
324
     * @Given the store has( also) :firstProductName, :secondProductName and :thirdProductName products
325
     * @Given the store has( also) :firstProductName, :secondProductName, :thirdProductName and :fourthProductName products
326
     */
327
    public function theStoreHasProducts(...$productsNames)
328
    {
329
        foreach ($productsNames as $productName) {
330
            $this->saveProduct($this->createProduct($productName));
331
        }
332
    }
333
334
    /**
335
     * @Given /^(this channel) has "([^"]+)", "([^"]+)", "([^"]+)" and "([^"]+)" products$/
336
     */
337
    public function thisChannelHasProducts(ChannelInterface $channel, ...$productsNames)
338
    {
339
        foreach ($productsNames as $productName) {
340
            $product = $this->createProduct($productName);
341
            $product->addChannel($channel);
342
343
            $this->saveProduct($product);
344
        }
345
    }
346
347
    /**
348
     * @Given /^the (product "[^"]+") has(?:| a) "([^"]+)" variant priced at ("[^"]+")$/
349
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+")$/
350
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") in ("[^"]+" channel)$/
351
     */
352
    public function theProductHasVariantPricedAt(
353
        ProductInterface $product,
354
        $productVariantName,
355
        $price,
356
        ChannelInterface $channel = null
357
    ) {
358
        $this->createProductVariant(
359
            $product,
360
            $productVariantName,
361
            $price,
362
            StringInflector::nameToUppercaseCode($productVariantName),
363
            (null !== $channel) ? $channel : $this->sharedStorage->get('channel')
364
        );
365
    }
366
367
    /**
368
     * @Given /^(it|this product) has(?:| also) variant named "([^"]+)" in ("[^"]+" locale) and "([^"]+)" in ("[^"]+" locale)$/
369
     */
370
    public function itHasVariantNamedInAndIn(ProductInterface $product, $firstName, $firstLocale, $secondName, $secondLocale)
371
    {
372
        $productVariant = $this->createProductVariant(
373
            $product,
374
            $firstName,
375
            100,
376
            StringInflector::nameToUppercaseCode($firstName),
377
            $this->sharedStorage->get('channel')
378
        );
379
380
        $names = [$firstName => $firstLocale, $secondName => $secondLocale];
381
        foreach ($names as $name => $locale) {
382
            $this->addProductVariantTranslation($productVariant, $name, $locale);
383
        }
384
385
        $this->objectManager->flush();
386
    }
387
388
    /**
389
     * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") identified by "([^"]+)"$/
390
     */
391
    public function theProductHasVariantPricedAtIdentifiedBy(
392
        ProductInterface $product,
393
        $productVariantName,
394
        $price,
395
        $code
396
    ) {
397
        $this->createProductVariant($product, $productVariantName, $price, $code, $this->sharedStorage->get('channel'));
398
    }
399
400
    /**
401
     * @Given /^there is product "([^"]+)" available in ((?:this|that|"[^"]+") channel)$/
402
     * @Given /^the store has a product "([^"]+)" available in ("([^"]+)" channel)$/
403
     */
404
    public function thereIsProductAvailableInGivenChannel($productName, ChannelInterface $channel)
405
    {
406
        $product = $this->createProduct($productName, 0, null, $channel);
407
408
        $product->setDescription('Awesome ' . $productName);
409
        $product->addChannel($channel);
410
411
        $this->saveProduct($product);
412
    }
413
414
    /**
415
     * @Given /^([^"]+) belongs to ("[^"]+" tax category)$/
416
     */
417
    public function productBelongsToTaxCategory(ProductInterface $product, TaxCategoryInterface $taxCategory)
418
    {
419
        /** @var ProductVariantInterface $variant */
420
        $variant = $this->defaultVariantResolver->getVariant($product);
421
        $variant->setTaxCategory($taxCategory);
422
423
        $this->objectManager->flush();
424
    }
425
426
    /**
427
     * @Given /^(it) comes in the following variations:$/
428
     */
429
    public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)
430
    {
431
        foreach ($table->getHash() as $variantHash) {
432
            /** @var ProductVariantInterface $variant */
433
            $variant = $this->productVariantFactory->createNew();
434
435
            $variant->setName($variantHash['name']);
436
            $variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name']));
437
            $variant->addChannelPricing($this->createChannelPricingForChannel(
438
                $this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])),
439
                $this->sharedStorage->get('channel')
440
            ));
441
            $variant->setProduct($product);
442
            $product->addVariant($variant);
443
        }
444
445
        $this->objectManager->flush();
446
    }
447
448
    /**
449
     * @Given /^("[^"]+" variant of product "[^"]+") belongs to ("[^"]+" tax category)$/
450
     */
451
    public function productVariantBelongsToTaxCategory(
452
        ProductVariantInterface $productVariant,
453
        TaxCategoryInterface $taxCategory
454
    ) {
455
        $productVariant->setTaxCategory($taxCategory);
456
        $this->objectManager->flush($productVariant);
457
    }
458
459
    /**
460
     * @Given /^(this product) has ([^"]+) attribute "([^"]+)" with value "([^"]+)"$/
461
     */
462
    public function thisProductHasAttributeWithValue(ProductInterface $product, $productAttributeType, $productAttributeName, $value)
463
    {
464
        $attribute = $this->createProductAttribute($productAttributeType, $productAttributeName);
465
        $attributeValue = $this->createProductAttributeValue($value, $attribute);
466
        $product->addAttribute($attributeValue);
467
468
        $this->objectManager->flush();
469
    }
470
471
    /**
472
     * @Given /^(this product) has percent attribute "([^"]+)" with value ([^"]+)%$/
473
     */
474
    public function thisProductHasPercentAttributeWithValue(ProductInterface $product, $productAttributeName, $value)
475
    {
476
        $attribute = $this->createProductAttribute('percent', $productAttributeName);
477
        $attributeValue = $this->createProductAttributeValue($value/100, $attribute);
478
        $product->addAttribute($attributeValue);
479
480
        $this->objectManager->flush();
481
    }
482
483
    /**
484
     * @Given /^(this product) has ([^"]+) attribute "([^"]+)" set to "([^"]+)"$/
485
     */
486
    public function thisProductHasCheckboxAttributeWithValue(ProductInterface $product, $productAttributeType, $productAttributeName, $value)
487
    {
488
        $attribute = $this->createProductAttribute($productAttributeType, $productAttributeName);
489
        $booleanValue = ('Yes' === $value);
490
        $attributeValue = $this->createProductAttributeValue($booleanValue, $attribute);
0 ignored issues
show
Documentation introduced by
$booleanValue is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
491
        $product->addAttribute($attributeValue);
492
493
        $this->objectManager->flush();
494
    }
495
496
    /**
497
     * @Given /^(this product) has percent attribute "([^"]+)" at position (\d+)$/
498
     */
499
    public function thisProductHasPercentAttributeWithValueAtPosition(ProductInterface $product, $productAttributeName, $position)
500
    {
501
        $attribute = $this->createProductAttribute('percent', $productAttributeName);
502
        $attribute->setPosition($position);
503
        $attributeValue = $this->createProductAttributeValue(rand(1, 100)/100, $attribute);
504
505
        $product->addAttribute($attributeValue);
506
507
        $this->objectManager->flush();
508
    }
509
510
    /**
511
     * @Given /^(this product) has ([^"]+) attribute "([^"]+)" with date "([^"]+)"$/
512
     */
513
    public function thisProductHasDateTimeAttributeWithDate(ProductInterface $product, $productAttributeType, $productAttributeName, $date)
514
    {
515
        $attribute = $this->createProductAttribute($productAttributeType, $productAttributeName);
516
        $attributeValue = $this->createProductAttributeValue(new \DateTime($date), $attribute);
0 ignored issues
show
Documentation introduced by
new \DateTime($date) is of type object<DateTime>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
517
518
        $product->addAttribute($attributeValue);
519
520
        $this->objectManager->flush();
521
    }
522
523
    /**
524
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)" and "([^"]+)"$/
525
     * @Given /^(this product) has option "([^"]+)" with values "([^"]+)", "([^"]+)" and "([^"]+)"$/
526
     */
527
    public function thisProductHasOptionWithValues(ProductInterface $product, $optionName, ...$values)
528
    {
529
        /** @var ProductOptionInterface $option */
530
        $option = $this->productOptionFactory->createNew();
531
532
        $option->setName($optionName);
533
        $option->setCode(StringInflector::nameToUppercaseCode($optionName));
534
535
        $this->sharedStorage->set(sprintf('%s_option', $optionName), $option);
536
537
        foreach ($values as $key => $value) {
538
            $optionValue = $this->addProductOption($option, $value, StringInflector::nameToUppercaseCode($value));
539
            $this->sharedStorage->set(sprintf('%s_option_%s_value', $value, strtolower($optionName)), $optionValue);
540
        }
541
542
        $product->addOption($option);
543
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_MATCH);
544
545
        $this->objectManager->persist($option);
546
        $this->objectManager->flush();
547
    }
548
549
    /**
550
     * @Given /^there (?:is|are) (\d+) unit(?:|s) of (product "([^"]+)") available in the inventory$/
551
     * @When product :product quantity is changed to :quantity
552
     */
553
    public function thereIsQuantityOfProducts($quantity, ProductInterface $product)
554
    {
555
        /** @var ProductVariantInterface $productVariant */
556
        $productVariant = $this->defaultVariantResolver->getVariant($product);
557
        $productVariant->setOnHand($quantity);
558
559
        $this->objectManager->flush();
560
    }
561
562
    /**
563
     * @Given /^the (product "([^"]+)") is out of stock$/
564
     */
565
    public function theProductIsOutOfStock(ProductInterface $product)
566
    {
567
        /** @var ProductVariantInterface $productVariant */
568
        $productVariant = $this->defaultVariantResolver->getVariant($product);
569
        $productVariant->setTracked(true);
570
        $productVariant->setOnHand(0);
571
572
        $this->objectManager->flush();
573
    }
574
575
    /**
576
     * @When other customer has bought :quantity :product products by this time
577
     */
578
    public function otherCustomerHasBoughtProductsByThisTime($quantity, ProductInterface $product)
579
    {
580
        /** @var ProductVariantInterface $productVariant */
581
        $productVariant = $this->defaultVariantResolver->getVariant($product);
582
        $productVariant->setOnHand($productVariant->getOnHand() - $quantity);
583
584
        $this->objectManager->flush();
585
    }
586
587
    /**
588
     * @Given /^(this product) is tracked by the inventory$/
589
     * @Given /^(?:|the )("[^"]+" product) is(?:| also) tracked by the inventory$/
590
     */
591
    public function thisProductIsTrackedByTheInventory(ProductInterface $product)
592
    {
593
        /** @var ProductVariantInterface $productVariant */
594
        $productVariant = $this->defaultVariantResolver->getVariant($product);
595
        $productVariant->setTracked(true);
596
597
        $this->objectManager->flush();
598
    }
599
600
    /**
601
     * @Given /^(this product) is available in "([^"]+)" ([^"]+) priced at ("[^"]+")$/
602
     */
603
    public function thisProductIsAvailableInSize(ProductInterface $product, $optionValueName, $optionName, $price)
604
    {
605
        /** @var ProductVariantInterface $variant */
606
        $variant = $this->productVariantFactory->createNew();
607
608
        $optionValue = $this->sharedStorage->get(sprintf('%s_option_%s_value', $optionValueName, $optionName));
609
610
        $variant->addOptionValue($optionValue);
611
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $this->sharedStorage->get('channel')));
612
        $variant->setCode(sprintf("%s_%s", $product->getCode(), $optionValueName));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal %s_%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
613
        $variant->setName($product->getName());
614
615
        $product->addVariant($variant);
616
        $this->objectManager->flush();
617
    }
618
619
    /**
620
     * @Given the :product product's :optionValueName size belongs to :shippingCategory shipping category
621
     * @Given /^(this product) "([^"]+)" size belongs to ("([^"]+)" shipping category)$/
622
     */
623
    public function thisProductSizeBelongsToShippingCategory(ProductInterface $product, $optionValueName, ShippingCategoryInterface $shippingCategory)
624
    {
625
        $code = sprintf("%s_%s", $product->getCode(), $optionValueName);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal %s_%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
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 ("[^"]+" option)$/
640
     * @Given /^(this product) has an ("[^"]+" option)$/
641
     */
642
    public function thisProductHasThisProductOption(ProductInterface $product, ProductOptionInterface $option)
643
    {
644
        $product->addOption($option);
645
646
        $this->objectManager->flush();
647
    }
648
649
    /**
650
     * @Given /^there are ([^"]+) units of ("[^"]+" variant of product "[^"]+") available in the inventory$/
651
     */
652
    public function thereAreItemsOfProductInVariantAvailableInTheInventory($quantity, ProductVariantInterface $productVariant)
653
    {
654
        $productVariant->setTracked(true);
655
        $productVariant->setOnHand($quantity);
656
657
        $this->objectManager->flush();
658
    }
659
660
    /**
661
     * @Given /^the ("[^"]+" product variant) is tracked by the inventory$/
662
     */
663
    public function theProductVariantIsTrackedByTheInventory(ProductVariantInterface $productVariant)
664
    {
665
        $productVariant->setTracked(true);
666
667
        $this->objectManager->flush();
668
    }
669
670
    /**
671
     * @Given /^(this product)'s price is ("[^"]+")$/
672
     * @Given /^the (product "[^"]+") changed its price 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) has(?:| also) an image "([^"]+)" with a code "([^"]+)"$/
686
     * @Given /^the ("[^"]+" product) has(?:| also) an image "([^"]+)" with a code "([^"]+)"$/
687
     */
688
    public function thisProductHasAnImageWithACode(ProductInterface $product, $imagePath, $imageCode)
689
    {
690
        $filesPath = $this->getParameter('files_path');
691
692
        /** @var ImageInterface $productImage */
693
        $productImage = $this->productImageFactory->createNew();
694
        $productImage->setFile(new UploadedFile($filesPath.$imagePath, basename($imagePath)));
695
        $productImage->setCode($imageCode);
696
        $this->imageUploader->upload($productImage);
697
698
        $product->addImage($productImage);
699
700
        $this->objectManager->flush($product);
701
    }
702
703
    /**
704
     * @Given /^(it) has different prices for different channels and currencies$/
705
     */
706
    public function itHasDifferentPricesForDifferentChannelsAndCurrencies(ProductInterface $product)
707
    {
708
        /** @var ProductVariantInterface $variant */
709
        $variant = $this->defaultVariantResolver->getVariant($product);
710
711
        $variant->setPricingCalculator(Calculators::CHANNEL_AND_CURRENCY_BASED);
0 ignored issues
show
Bug introduced by
The method setPricingCalculator() does not seem to exist on object<Sylius\Component\...roductVariantInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
712
    }
713
714
    /**
715
     * @Given /^(it) has price ("[^"]+") for ("[^"]+" channel) and "([^"]+)" currency$/
716
     */
717
    public function itHasPriceForChannelAndCurrency(
718
        ProductInterface $product,
719
        $price,
720
        ChannelInterface $channel,
721
        $currency
722
    ) {
723
        /** @var ProductVariantInterface $variant */
724
        $variant = $this->defaultVariantResolver->getVariant($product);
725
726
        $pricingConfiguration = $variant->getPricingConfiguration();
0 ignored issues
show
Bug introduced by
The method getPricingConfiguration() does not seem to exist on object<Sylius\Component\...roductVariantInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
727
        $pricingConfiguration[$channel->getCode()][$currency] = $price;
728
729
        $variant->setPricingConfiguration($pricingConfiguration);
0 ignored issues
show
Bug introduced by
The method setPricingConfiguration() does not seem to exist on object<Sylius\Component\...roductVariantInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
730
731
        $this->objectManager->flush();
732
    }
733
734
    /**
735
     * @Given /^(this product) belongs to ("([^"]+)" shipping category)$/
736
     */
737
    public function thisProductBelongsToShippingCategory(ProductInterface $product, ShippingCategoryInterface $shippingCategory)
738
    {
739
        $product->getVariants()->first()->setShippingCategory($shippingCategory);
740
        $this->objectManager->flush();
741
    }
742
743
    /**
744
     * @param string $type
745
     * @param string $name
746
     * @param string|null $code
747
     *
748
     * @return ProductAttributeInterface
749
     */
750
    private function createProductAttribute($type, $name, $code = null)
751
    {
752
        $productAttribute = $this->productAttributeFactory->createTyped($type);
753
754
        if (null === $code) {
755
            $code = StringInflector::nameToCode($name);
756
        }
757
758
        $productAttribute->setCode($code);
759
        $productAttribute->setName($name);
760
761
        $this->objectManager->persist($productAttribute);
762
763
        return $productAttribute;
764
    }
765
766
    /**
767
     * @param string $value
768
     * @param ProductAttributeInterface $attribute
769
     *
770
     * @return ProductAttributeValueInterface
771
     */
772
    private function createProductAttributeValue($value, ProductAttributeInterface $attribute)
773
    {
774
        /** @var ProductAttributeValueInterface $attributeValue */
775
        $attributeValue = $this->attributeValueFactory->createNew();
776
        $attributeValue->setAttribute($attribute);
777
        $attributeValue->setValue($value);
778
779
        $this->objectManager->persist($attributeValue);
780
781
        return $attributeValue;
782
    }
783
784
    /**
785
     * @param string $price
786
     *
787
     * @return int
788
     */
789
    private function getPriceFromString($price)
790
    {
791
        return (int) round(($price * 100), 2);
792
    }
793
794
    /**
795
     * @param string $productName
796
     * @param int $price
797
     * @param string|null $date
798
     * @param ChannelInterface|null $channel
799
     *
800
     * @return ProductInterface
801
     */
802
    private function createProduct($productName, $price = 100, $date = null, ChannelInterface $channel = null)
803
    {
804
        /** @var ProductInterface $product */
805
        $product = $this->productFactory->createWithVariant();
806
807
        $product->setName($productName);
808
        $product->setCode(StringInflector::nameToUppercaseCode($productName));
809
        $product->setSlug($this->slugGenerator->generate($productName));
810
        $product->setCreatedAt(new \DateTime($date));
811
812
        /** @var ProductVariantInterface $productVariant */
813
        $productVariant = $this->defaultVariantResolver->getVariant($product);
814
815
        if (null === $channel && $this->sharedStorage->has('channel')) {
816
            $channel = $this->sharedStorage->get('channel');
817
        }
818
819
        if (null !== $channel) {
820
            $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
821
        }
822
        $productVariant->setCode($product->getCode());
823
        $productVariant->setName($product->getName());
824
825
        return $product;
826
    }
827
828
    /**
829
     * @param ProductOptionInterface $option
830
     * @param string $value
831
     * @param string $code
832
     *
833
     * @return ProductOptionValueInterface
834
     */
835
    private function addProductOption(ProductOptionInterface $option, $value, $code)
836
    {
837
        /** @var ProductOptionValueInterface $optionValue */
838
        $optionValue = $this->productOptionValueFactory->createNew();
839
840
        $optionValue->setValue($value);
841
        $optionValue->setCode($code);
842
        $optionValue->setOption($option);
843
844
        $option->addValue($optionValue);
845
846
        return $optionValue;
847
    }
848
849
    /**
850
     * @param ProductInterface $product
851
     */
852
    private function saveProduct(ProductInterface $product)
853
    {
854
        $this->productRepository->add($product);
855
        $this->sharedStorage->set('product', $product);
856
    }
857
858
    /**
859
     * @param string $name
860
     *
861
     * @return NodeElement
862
     */
863
    private function getParameter($name)
864
    {
865
        return isset($this->minkParameters[$name]) ? $this->minkParameters[$name] : null;
866
    }
867
868
    /**
869
     * @param ProductInterface $product
870
     * @param $productVariantName
871
     * @param int $price
872
     * @param string $code
873
     * @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...
874
     *
875
     * @return ProductVariantInterface
876
     */
877
    private function createProductVariant(
878
        ProductInterface $product,
879
        $productVariantName,
880
        $price,
881
        $code,
882
        ChannelInterface $channel = null
883
    ) {
884
        $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);
885
886
        /** @var ProductVariantInterface $variant */
887
        $variant = $this->productVariantFactory->createNew();
888
889
        $variant->setName($productVariantName);
890
        $variant->setCode($code);
891
        $variant->setProduct($product);
892
        $variant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
893
894
        $product->addVariant($variant);
895
896
        $this->objectManager->flush();
897
        $this->sharedStorage->set('variant', $variant);
898
899
        return $variant;
900
    }
901
902
    /**
903
     * @param ProductInterface $product
904
     * @param string $name
905
     * @param string $locale
906
     */
907
    private function addProductTranslation(ProductInterface $product, $name, $locale)
908
    {
909
        /** @var ProductTranslationInterface|TranslationInterface $translation */
910
        $translation = $this->productTranslationFactory->createNew();
911
        $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\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...
912
        $translation->setName($name);
0 ignored issues
show
Bug introduced by
The method setName does only exist in Sylius\Component\Core\Mo...uctTranslationInterface, 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...
913
        $translation->setSlug($this->slugGenerator->generate($name));
0 ignored issues
show
Bug introduced by
The method setSlug does only exist in Sylius\Component\Core\Mo...uctTranslationInterface, 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...
914
915
        $product->addTranslation($translation);
0 ignored issues
show
Bug introduced by
It seems like $translation defined by $this->productTranslationFactory->createNew() on line 910 can also be of type object<Sylius\Component\...ctTranslationInterface>; 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...
916
    }
917
918
    /**
919
     * @param ProductVariantInterface $productVariant
920
     * @param string $name
921
     * @param string $locale
922
     */
923
    private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)
924
    {
925
        /** @var ProductVariantTranslationInterface|TranslationInterface $translation */
926
        $translation = $this->productVariantTranslationFactory->createNew();
927
        $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...
928
        $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...
929
930
        $productVariant->addTranslation($translation);
0 ignored issues
show
Bug introduced by
It seems like $translation defined by $this->productVariantTra...ionFactory->createNew() on line 926 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...
931
    }
932
933
    /**
934
     * @param int $price
935
     * @param ChannelInterface|null $channel
936
     *
937
     * @return ChannelPricingInterface
938
     */
939
    private function createChannelPricingForChannel($price, ChannelInterface $channel = null)
940
    {
941
        /** @var ChannelPricingInterface $channelPricing */
942
        $channelPricing = $this->channelPricingFactory->createNew();
943
        $channelPricing->setPrice($price);
944
        $channelPricing->setChannel($channel);
945
946
        return $channelPricing;
947
    }
948
}
949