Complex classes like ProductContext often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ProductContext, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 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(  | 
            ||
| 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 /^(this product) only variant was renamed to "([^"]+)"$/  | 
            ||
| 468 | */  | 
            ||
| 469 | public function productOnlyVariantWasRenamed(ProductInterface $product, $variantName)  | 
            ||
| 470 |     { | 
            ||
| 471 | Assert::true($product->isSimple());  | 
            ||
| 472 | |||
| 473 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 474 | $productVariant = $product->getVariants()->first();  | 
            ||
| 475 | $productVariant->setName($variantName);  | 
            ||
| 476 | |||
| 477 | $this->objectManager->flush();  | 
            ||
| 478 | }  | 
            ||
| 479 | |||
| 480 | /**  | 
            ||
| 481 | * @Given /^there is product "([^"]+)" available in ((?:this|that|"[^"]+") channel)$/  | 
            ||
| 482 |      * @Given /^the store has a product "([^"]+)" available in ("([^"]+)" channel)$/ | 
            ||
| 483 | */  | 
            ||
| 484 | public function thereIsProductAvailableInGivenChannel($productName, ChannelInterface $channel)  | 
            ||
| 485 |     { | 
            ||
| 486 | $product = $this->createProduct($productName, 0, $channel);  | 
            ||
| 487 | |||
| 488 | $this->saveProduct($product);  | 
            ||
| 489 | }  | 
            ||
| 490 | |||
| 491 | /**  | 
            ||
| 492 |      * @Given /^([^"]+) belongs to ("[^"]+" tax category)$/ | 
            ||
| 493 | */  | 
            ||
| 494 | public function productBelongsToTaxCategory(ProductInterface $product, TaxCategoryInterface $taxCategory)  | 
            ||
| 495 |     { | 
            ||
| 496 | /** @var ProductVariantInterface $variant */  | 
            ||
| 497 | $variant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 498 | $variant->setTaxCategory($taxCategory);  | 
            ||
| 499 | |||
| 500 | $this->objectManager->flush();  | 
            ||
| 501 | }  | 
            ||
| 502 | |||
| 503 | /**  | 
            ||
| 504 | * @Given /^(it) comes in the following variations:$/  | 
            ||
| 505 | */  | 
            ||
| 506 | public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)  | 
            ||
| 507 |     { | 
            ||
| 508 |         $channel = $this->sharedStorage->get('channel'); | 
            ||
| 509 | |||
| 510 |         foreach ($table->getHash() as $variantHash) { | 
            ||
| 511 | /** @var ProductVariantInterface $variant */  | 
            ||
| 512 | $variant = $this->productVariantFactory->createNew();  | 
            ||
| 513 | |||
| 514 | $variant->setName($variantHash['name']);  | 
            ||
| 515 | $variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name']));  | 
            ||
| 516 | $variant->addChannelPricing($this->createChannelPricingForChannel(  | 
            ||
| 517 | $this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])),  | 
            ||
| 518 | $channel  | 
            ||
| 519 | ));  | 
            ||
| 520 | |||
| 521 | $variant->setProduct($product);  | 
            ||
| 522 | $product->addVariant($variant);  | 
            ||
| 523 | }  | 
            ||
| 524 | |||
| 525 | $this->objectManager->flush();  | 
            ||
| 526 | }  | 
            ||
| 527 | |||
| 528 | /**  | 
            ||
| 529 |      * @Given /^("[^"]+" variant of product "[^"]+") belongs to ("[^"]+" tax category)$/ | 
            ||
| 530 | */  | 
            ||
| 531 | public function productVariantBelongsToTaxCategory(  | 
            ||
| 532 | ProductVariantInterface $productVariant,  | 
            ||
| 533 | TaxCategoryInterface $taxCategory  | 
            ||
| 534 |     ) { | 
            ||
| 535 | $productVariant->setTaxCategory($taxCategory);  | 
            ||
| 536 | $this->objectManager->flush($productVariant);  | 
            ||
| 537 | }  | 
            ||
| 538 | |||
| 539 | /**  | 
            ||
| 540 | * @Given /^(this product) has option "([^"]+)" with values "([^"]+)" and "([^"]+)"$/  | 
            ||
| 541 | * @Given /^(this product) has option "([^"]+)" with values "([^"]+)", "([^"]+)" and "([^"]+)"$/  | 
            ||
| 542 | */  | 
            ||
| 543 | public function thisProductHasOptionWithValues(ProductInterface $product, $optionName, ...$values)  | 
            ||
| 544 |     { | 
            ||
| 545 | /** @var ProductOptionInterface $option */  | 
            ||
| 546 | $option = $this->productOptionFactory->createNew();  | 
            ||
| 547 | |||
| 548 | $option->setName($optionName);  | 
            ||
| 549 | $option->setCode(StringInflector::nameToUppercaseCode($optionName));  | 
            ||
| 550 | |||
| 551 |         $this->sharedStorage->set(sprintf('%s_option', $optionName), $option); | 
            ||
| 552 | |||
| 553 |         foreach ($values as $key => $value) { | 
            ||
| 554 | $optionValue = $this->addProductOption($option, $value, StringInflector::nameToUppercaseCode($value));  | 
            ||
| 555 |             $this->sharedStorage->set(sprintf('%s_option_%s_value', $value, strtolower($optionName)), $optionValue); | 
            ||
| 556 | }  | 
            ||
| 557 | |||
| 558 | $product->addOption($option);  | 
            ||
| 559 | $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_MATCH);  | 
            ||
| 560 | |||
| 561 | $this->objectManager->persist($option);  | 
            ||
| 562 | $this->objectManager->flush();  | 
            ||
| 563 | }  | 
            ||
| 564 | |||
| 565 | /**  | 
            ||
| 566 | * @Given /^there (?:is|are) (\d+) unit(?:|s) of (product "([^"]+)") available in the inventory$/  | 
            ||
| 567 | */  | 
            ||
| 568 | public function thereIsQuantityOfProducts($quantity, ProductInterface $product)  | 
            ||
| 569 |     { | 
            ||
| 570 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 571 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 572 | $productVariant->setOnHand($quantity);  | 
            ||
| 573 | |||
| 574 | $this->objectManager->flush();  | 
            ||
| 575 | }  | 
            ||
| 576 | |||
| 577 | /**  | 
            ||
| 578 | * @Given /^the (product "([^"]+)") is out of stock$/  | 
            ||
| 579 | */  | 
            ||
| 580 | public function theProductIsOutOfStock(ProductInterface $product)  | 
            ||
| 581 |     { | 
            ||
| 582 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 583 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 584 | $productVariant->setTracked(true);  | 
            ||
| 585 | $productVariant->setOnHand(0);  | 
            ||
| 586 | |||
| 587 | $this->objectManager->flush();  | 
            ||
| 588 | }  | 
            ||
| 589 | |||
| 590 | /**  | 
            ||
| 591 | * @When other customer has bought :quantity :product products by this time  | 
            ||
| 592 | */  | 
            ||
| 593 | public function otherCustomerHasBoughtProductsByThisTime($quantity, ProductInterface $product)  | 
            ||
| 594 |     { | 
            ||
| 595 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 596 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 597 | $productVariant->setOnHand($productVariant->getOnHand() - $quantity);  | 
            ||
| 598 | |||
| 599 | $this->objectManager->flush();  | 
            ||
| 600 | }  | 
            ||
| 601 | |||
| 602 | /**  | 
            ||
| 603 | * @Given /^(this product) is tracked by the inventory$/  | 
            ||
| 604 |      * @Given /^(?:|the )("[^"]+" product) is(?:| also) tracked by the inventory$/ | 
            ||
| 605 | */  | 
            ||
| 606 | public function thisProductIsTrackedByTheInventory(ProductInterface $product)  | 
            ||
| 607 |     { | 
            ||
| 608 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 609 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 610 | $productVariant->setTracked(true);  | 
            ||
| 611 | |||
| 612 | $this->objectManager->flush();  | 
            ||
| 613 | }  | 
            ||
| 614 | |||
| 615 | /**  | 
            ||
| 616 |      * @Given /^(this product) is available in "([^"]+)" ([^"]+) priced at ("[^"]+")$/ | 
            ||
| 617 | */  | 
            ||
| 618 | public function thisProductIsAvailableInSize(ProductInterface $product, $optionValueName, $optionName, $price)  | 
            ||
| 619 |     { | 
            ||
| 620 | /** @var ProductVariantInterface $variant */  | 
            ||
| 621 | $variant = $this->productVariantFactory->createNew();  | 
            ||
| 622 | |||
| 623 |         $optionValue = $this->sharedStorage->get(sprintf('%s_option_%s_value', $optionValueName, $optionName)); | 
            ||
| 624 | |||
| 625 | $variant->addOptionValue($optionValue);  | 
            ||
| 626 |         $variant->addChannelPricing($this->createChannelPricingForChannel($price, $this->sharedStorage->get('channel'))); | 
            ||
| 627 |         $variant->setCode(sprintf('%s_%s', $product->getCode(), $optionValueName)); | 
            ||
| 628 | $variant->setName($product->getName());  | 
            ||
| 629 | |||
| 630 | $product->addVariant($variant);  | 
            ||
| 631 | $this->objectManager->flush();  | 
            ||
| 632 | }  | 
            ||
| 633 | |||
| 634 | /**  | 
            ||
| 635 | * @Given the :product product's :optionValueName size belongs to :shippingCategory shipping category  | 
            ||
| 636 | */  | 
            ||
| 637 | public function thisProductSizeBelongsToShippingCategory(ProductInterface $product, $optionValueName, ShippingCategoryInterface $shippingCategory)  | 
            ||
| 638 |     { | 
            ||
| 639 |         $code = sprintf('%s_%s', $product->getCode(), $optionValueName); | 
            ||
| 640 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 641 |         $productVariant = $product->getVariants()->filter(function ($variant) use ($code) { | 
            ||
| 642 | return $code === $variant->getCode();  | 
            ||
| 643 | })->first();  | 
            ||
| 644 | |||
| 645 |         Assert::notNull($productVariant, sprintf('Product variant with given code %s not exists!', $code)); | 
            ||
| 646 | |||
| 647 | $productVariant->setShippingCategory($shippingCategory);  | 
            ||
| 648 | $this->objectManager->flush();  | 
            ||
| 649 | }  | 
            ||
| 650 | |||
| 651 | /**  | 
            ||
| 652 | * @Given /^(this product) has (this product option)$/  | 
            ||
| 653 |      * @Given /^(this product) has (?:a|an) ("[^"]+" option)$/ | 
            ||
| 654 | */  | 
            ||
| 655 | public function thisProductHasThisProductOption(ProductInterface $product, ProductOptionInterface $option)  | 
            ||
| 656 |     { | 
            ||
| 657 | $product->addOption($option);  | 
            ||
| 658 | |||
| 659 | $this->objectManager->flush();  | 
            ||
| 660 | }  | 
            ||
| 661 | |||
| 662 | /**  | 
            ||
| 663 |      * @Given /^there are ([^"]+) units of ("[^"]+" variant of product "[^"]+") available in the inventory$/ | 
            ||
| 664 | */  | 
            ||
| 665 | public function thereAreItemsOfProductInVariantAvailableInTheInventory($quantity, ProductVariantInterface $productVariant)  | 
            ||
| 666 |     { | 
            ||
| 667 | $productVariant->setTracked(true);  | 
            ||
| 668 | $productVariant->setOnHand($quantity);  | 
            ||
| 669 | |||
| 670 | $this->objectManager->flush();  | 
            ||
| 671 | }  | 
            ||
| 672 | |||
| 673 | /**  | 
            ||
| 674 |      * @Given /^the ("[^"]+" product variant) is tracked by the inventory$/ | 
            ||
| 675 | */  | 
            ||
| 676 | public function theProductVariantIsTrackedByTheInventory(ProductVariantInterface $productVariant)  | 
            ||
| 677 |     { | 
            ||
| 678 | $productVariant->setTracked(true);  | 
            ||
| 679 | |||
| 680 | $this->objectManager->flush();  | 
            ||
| 681 | }  | 
            ||
| 682 | |||
| 683 | /**  | 
            ||
| 684 |      * @Given /^(this product)'s price is ("[^"]+")$/ | 
            ||
| 685 |      * @Given /^the (product "[^"]+") changed its price to ("[^"]+")$/ | 
            ||
| 686 |      * @Given /^(this product) price has been changed to ("[^"]+")$/ | 
            ||
| 687 | */  | 
            ||
| 688 | public function theProductChangedItsPriceTo(ProductInterface $product, $price)  | 
            ||
| 689 |     { | 
            ||
| 690 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 691 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 692 |         $channelPricing = $productVariant->getChannelPricingForChannel($this->sharedStorage->get('channel')); | 
            ||
| 693 | $channelPricing->setPrice($price);  | 
            ||
| 694 | |||
| 695 | $this->objectManager->flush();  | 
            ||
| 696 | }  | 
            ||
| 697 | |||
| 698 | /**  | 
            ||
| 699 | * @Given /^(this product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/  | 
            ||
| 700 |      * @Given /^the ("[^"]+" product)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/ | 
            ||
| 701 | * @Given /^(it)(?:| also) has an image "([^"]+)" with "([^"]+)" type$/  | 
            ||
| 702 | */  | 
            ||
| 703 | public function thisProductHasAnImageWithType(ProductInterface $product, $imagePath, $imageType)  | 
            ||
| 704 |     { | 
            ||
| 705 |         $filesPath = $this->getParameter('files_path'); | 
            ||
| 706 | |||
| 707 | /** @var ImageInterface $productImage */  | 
            ||
| 708 | $productImage = $this->productImageFactory->createNew();  | 
            ||
| 709 | $productImage->setFile(new UploadedFile($filesPath.$imagePath, basename($imagePath)));  | 
            ||
| 710 | $productImage->setType($imageType);  | 
            ||
| 711 | $this->imageUploader->upload($productImage);  | 
            ||
| 712 | |||
| 713 | $product->addImage($productImage);  | 
            ||
| 714 | |||
| 715 | $this->objectManager->flush($product);  | 
            ||
| 716 | }  | 
            ||
| 717 | |||
| 718 | /**  | 
            ||
| 719 |      * @Given /^(this product) belongs to ("([^"]+)" shipping category)$/ | 
            ||
| 720 | * @Given product :product shipping category has been changed to :shippingCategory  | 
            ||
| 721 | */  | 
            ||
| 722 | public function thisProductBelongsToShippingCategory(ProductInterface $product, ShippingCategoryInterface $shippingCategory)  | 
            ||
| 723 |     { | 
            ||
| 724 | $product->getVariants()->first()->setShippingCategory($shippingCategory);  | 
            ||
| 725 | $this->objectManager->flush();  | 
            ||
| 726 | }  | 
            ||
| 727 | |||
| 728 | /**  | 
            ||
| 729 | * @Given /^(this product) has been disabled$/  | 
            ||
| 730 | */  | 
            ||
| 731 | public function thisProductHasBeenDisabled(ProductInterface $product)  | 
            ||
| 732 |     { | 
            ||
| 733 | $product->disable();  | 
            ||
| 734 | $this->objectManager->flush();  | 
            ||
| 735 | }  | 
            ||
| 736 | |||
| 737 | /**  | 
            ||
| 738 | * @param string $price  | 
            ||
| 739 | *  | 
            ||
| 740 | * @return int  | 
            ||
| 741 | */  | 
            ||
| 742 | private function getPriceFromString($price)  | 
            ||
| 743 |     { | 
            ||
| 744 | return (int) round($price * 100, 2);  | 
            ||
| 745 | }  | 
            ||
| 746 | |||
| 747 | /**  | 
            ||
| 748 | * @param string $productName  | 
            ||
| 749 | * @param int $price  | 
            ||
| 750 | * @param ChannelInterface|null $channel  | 
            ||
| 751 | *  | 
            ||
| 752 | * @return ProductInterface  | 
            ||
| 753 | */  | 
            ||
| 754 | private function createProduct($productName, $price = 100, ChannelInterface $channel = null)  | 
            ||
| 755 |     { | 
            ||
| 756 |         if (null === $channel && $this->sharedStorage->has('channel')) { | 
            ||
| 757 |             $channel = $this->sharedStorage->get('channel'); | 
            ||
| 758 | }  | 
            ||
| 759 | |||
| 760 | /** @var ProductInterface $product */  | 
            ||
| 761 | $product = $this->productFactory->createWithVariant();  | 
            ||
| 762 | |||
| 763 | $product->setCode(StringInflector::nameToUppercaseCode($productName));  | 
            ||
| 764 | $product->setName($productName);  | 
            ||
| 765 | $product->setSlug($this->slugGenerator->generate($productName));  | 
            ||
| 766 | |||
| 767 |         if (null !== $channel) { | 
            ||
| 768 | $product->addChannel($channel);  | 
            ||
| 769 | |||
| 770 |             foreach ($channel->getLocales() as $locale) { | 
            ||
| 771 | $product->setFallbackLocale($locale->getCode());  | 
            ||
| 772 | $product->setCurrentLocale($locale->getCode());  | 
            ||
| 773 | |||
| 774 | $product->setName($productName);  | 
            ||
| 775 | $product->setSlug($this->slugGenerator->generate($productName));  | 
            ||
| 776 | }  | 
            ||
| 777 | }  | 
            ||
| 778 | |||
| 779 | /** @var ProductVariantInterface $productVariant */  | 
            ||
| 780 | $productVariant = $this->defaultVariantResolver->getVariant($product);  | 
            ||
| 781 | |||
| 782 |         if (null !== $channel) { | 
            ||
| 783 | $productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));  | 
            ||
| 784 | }  | 
            ||
| 785 | |||
| 786 | $productVariant->setCode($product->getCode());  | 
            ||
| 787 | $productVariant->setName($product->getName());  | 
            ||
| 788 | |||
| 789 | return $product;  | 
            ||
| 790 | }  | 
            ||
| 791 | |||
| 792 | /**  | 
            ||
| 793 | * @param ProductOptionInterface $option  | 
            ||
| 794 | * @param string $value  | 
            ||
| 795 | * @param string $code  | 
            ||
| 796 | *  | 
            ||
| 797 | * @return ProductOptionValueInterface  | 
            ||
| 798 | */  | 
            ||
| 799 | private function addProductOption(ProductOptionInterface $option, $value, $code)  | 
            ||
| 812 | |||
| 813 | /**  | 
            ||
| 814 | * @param ProductInterface $product  | 
            ||
| 815 | */  | 
            ||
| 816 | private function saveProduct(ProductInterface $product)  | 
            ||
| 821 | |||
| 822 | /**  | 
            ||
| 823 | * @param string $name  | 
            ||
| 824 | *  | 
            ||
| 825 | * @return NodeElement  | 
            ||
| 826 | */  | 
            ||
| 827 | private function getParameter($name)  | 
            ||
| 831 | |||
| 832 | /**  | 
            ||
| 833 | * @param ProductInterface $product  | 
            ||
| 834 | * @param $productVariantName  | 
            ||
| 835 | * @param int $price  | 
            ||
| 836 | * @param string $code  | 
            ||
| 837 | * @param ChannelInterface $channel  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 838 | * @param int $position  | 
            ||
| 839 | * @param bool $shippingRequired  | 
            ||
| 840 | *  | 
            ||
| 841 | * @return ProductVariantInterface  | 
            ||
| 842 | */  | 
            ||
| 843 | private function createProductVariant(  | 
            ||
| 844 | ProductInterface $product,  | 
            ||
| 845 | $productVariantName,  | 
            ||
| 846 | $price,  | 
            ||
| 847 | $code,  | 
            ||
| 848 | ChannelInterface $channel = null,  | 
            ||
| 849 | $position = null,  | 
            ||
| 850 | $shippingRequired = true  | 
            ||
| 851 |     ) { | 
            ||
| 852 | $product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);  | 
            ||
| 853 | |||
| 854 | /** @var ProductVariantInterface $variant */  | 
            ||
| 855 | $variant = $this->productVariantFactory->createNew();  | 
            ||
| 856 | |||
| 857 | $variant->setName($productVariantName);  | 
            ||
| 858 | $variant->setCode($code);  | 
            ||
| 859 | $variant->setProduct($product);  | 
            ||
| 860 | $variant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));  | 
            ||
| 861 | $variant->setPosition($position);  | 
            ||
| 862 | $variant->setShippingRequired($shippingRequired);  | 
            ||
| 863 | |||
| 864 | $product->addVariant($variant);  | 
            ||
| 865 | |||
| 866 | $this->objectManager->flush();  | 
            ||
| 867 |         $this->sharedStorage->set('variant', $variant); | 
            ||
| 868 | |||
| 869 | return $variant;  | 
            ||
| 870 | }  | 
            ||
| 871 | |||
| 872 | /**  | 
            ||
| 873 | * @param ProductInterface $product  | 
            ||
| 874 | * @param string $name  | 
            ||
| 875 | * @param string $locale  | 
            ||
| 876 | */  | 
            ||
| 877 | private function addProductTranslation(ProductInterface $product, $name, $locale)  | 
            ||
| 891 | |||
| 892 | /**  | 
            ||
| 893 | * @param ProductVariantInterface $productVariant  | 
            ||
| 894 | * @param string $name  | 
            ||
| 895 | * @param string $locale  | 
            ||
| 896 | */  | 
            ||
| 897 | private function addProductVariantTranslation(ProductVariantInterface $productVariant, $name, $locale)  | 
            ||
| 906 | |||
| 907 | /**  | 
            ||
| 908 | * @param int $price  | 
            ||
| 909 | * @param ChannelInterface|null $channel  | 
            ||
| 910 | *  | 
            ||
| 911 | * @return ChannelPricingInterface  | 
            ||
| 912 | */  | 
            ||
| 913 | private function createChannelPricingForChannel($price, ChannelInterface $channel = null)  | 
            ||
| 922 | }  | 
            ||
| 923 | 
This check looks for
@paramannotations 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.