diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 946f530655daa..6cfa90a7c4c0c 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -1613,6 +1613,17 @@ public function setIsDuplicable($value) */ public function isSalable() { + if ($this->_catalogProduct->getSkipSaleableCheck()) { + return true; + } + if (($this->getOrigData('status') != $this->getData('status')) + || $this->isStockStatusChanged()) { + $this->unsetData('salable'); + } + + if ($this->hasData('salable')) { + return $this->getData('salable'); + } $this->_eventManager->dispatch('catalog_product_is_salable_before', ['product' => $this]); $salable = $this->isAvailable(); @@ -1622,7 +1633,8 @@ public function isSalable() 'catalog_product_is_salable_after', ['product' => $this, 'salable' => $object] ); - return $object->getIsSalable(); + $this->setData('salable', $object->getIsSalable()); + return $this->getData('salable'); } /** @@ -1632,7 +1644,7 @@ public function isSalable() */ public function isAvailable() { - return $this->getTypeInstance()->isSalable($this) || $this->_catalogProduct->getSkipSaleableCheck(); + return $this->_catalogProduct->getSkipSaleableCheck() || $this->getTypeInstance()->isSalable($this); } /** diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php index 783507a0994f7..43a9549c46293 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php @@ -156,6 +156,11 @@ class Full */ private $iteratorFactory; + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + /** * @param ResourceConnection $resource * @param \Magento\Catalog\Model\Product\Type $catalogProductType @@ -175,6 +180,7 @@ class Full * @param \Magento\Framework\Search\Request\DimensionFactory $dimensionFactory * @param \Magento\Framework\Indexer\ConfigInterface $indexerConfig * @param \Magento\CatalogSearch\Model\Indexer\Fulltext\Action\IndexIteratorFactory $indexIteratorFactory + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -195,7 +201,8 @@ public function __construct( \Magento\CatalogSearch\Model\ResourceModel\Fulltext $fulltextResource, \Magento\Framework\Search\Request\DimensionFactory $dimensionFactory, \Magento\Framework\Indexer\ConfigInterface $indexerConfig, - \Magento\CatalogSearch\Model\Indexer\Fulltext\Action\IndexIteratorFactory $indexIteratorFactory + \Magento\CatalogSearch\Model\Indexer\Fulltext\Action\IndexIteratorFactory $indexIteratorFactory, + \Magento\Framework\EntityManager\MetadataPool $metadataPool = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -216,6 +223,8 @@ public function __construct( $this->fulltextResource = $fulltextResource; $this->dimensionFactory = $dimensionFactory; $this->iteratorFactory = $indexIteratorFactory; + $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\MetadataPool::class); } /** @@ -252,14 +261,22 @@ protected function getTable($table) */ protected function getProductIdsFromParents(array $entityIds) { - return $this->connection + /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */ + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $fieldForParent = $metadata->getLinkField(); + + $select = $this->connection ->select() - ->from($this->getTable('catalog_product_relation'), 'parent_id') + ->from(['relation' => $this->getTable('catalog_product_relation')], []) ->distinct(true) ->where('child_id IN (?)', $entityIds) ->where('parent_id NOT IN (?)', $entityIds) - ->query() - ->fetchAll(\Zend_Db::FETCH_COLUMN); + ->join( + ['cpe' => $this->getTable('catalog_product_entity')], + 'relation.parent_id = cpe.' . $fieldForParent, + ['cpe.entity_id'] + ); + return $this->connection->fetchCol($select); } /** diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Collection/SalableProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Collection/SalableProcessor.php new file mode 100644 index 0000000000000..1fc4f6d4337bb --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Collection/SalableProcessor.php @@ -0,0 +1,59 @@ +stockStatusFactory = $stockStatusFactory; + } + + /** + * Adds filters to the collection to help determine if product is available for sale. + * + * This method adds several additional checks for a children products availability. + * Children products should be enabled and available in stock to be sold. + * It also adds the specific flag to the collection to prevent the case + * when filter already added and therefore may break the collection. + * + * @param Collection $collection + * @return Collection + */ + public function process(Collection $collection) + { + $collection->addAttributeToFilter( + ProductInterface::STATUS, + Status::STATUS_ENABLED + ); + + $stockFlag = 'has_stock_status_filter'; + if (!$collection->hasFlag($stockFlag)) { + $stockStatusResource = $this->stockStatusFactory->create(); + $stockStatusResource->addStockDataToCollection($collection, true); + $collection->setFlag($stockFlag, true); + } + + return $collection; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 93438b0fde3c7..e98f546943ff8 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -12,6 +12,10 @@ use Magento\Catalog\Model\Product; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\CatalogInventory\Model\Stock\Status; +use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable as ProductTypeConfigurable; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; @@ -96,29 +100,28 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType /** * Catalog product type configurable * - * @var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable + * @var ProductTypeConfigurable */ protected $_catalogProductTypeConfigurable; /** * Attribute collection factory * - * @var - * \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory + * @var ProductTypeConfigurable\Attribute\CollectionFactory */ protected $_attributeCollectionFactory; /** * Product collection factory * - * @var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory + * @var ProductTypeConfigurable\Product\CollectionFactory */ protected $_productCollectionFactory; /** * Configurable attribute factory * - * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory + * @var AttributeFactory */ protected $configurableAttributeFactory; @@ -177,7 +180,11 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType private $productFactory; /** - * @codingStandardsIgnoreStart/End + * @var SalableProcessor + */ + private $salableProcessor; + + /** * * @param \Magento\Catalog\Model\Product\Option $catalogProductOption * @param \Magento\Eav\Model\Config $eavConfig @@ -190,17 +197,21 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType * @param ProductRepositoryInterface $productRepository * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory $typeConfigurableFactory * @param \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $eavAttributeFactory - * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory $configurableAttributeFactory - * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory $productCollectionFactory - * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory $attributeCollectionFactory - * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $catalogProductTypeConfigurable + * @param AttributeFactory $configurableAttributeFactory + * @param ProductTypeConfigurable\Product\CollectionFactory $productCollectionFactory + * @param ProductTypeConfigurable\Attribute\CollectionFactory $attributeCollectionFactory + * @param ProductTypeConfigurable $catalogProductTypeConfigurable * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor * @param \Magento\Framework\Cache\FrontendInterface $cache, * @param \Magento\Customer\Model\Session $customerSession, * @param StockRegistryInterface $stockRegistry, * @param ProductInterfaceFactory $productFactory - * + * @param \Magento\Framework\Cache\FrontendInterface $cache + * @param \Magento\Customer\Model\Session $customerSession + * @param StockRegistryInterface $stockRegistry + * @param ProductInterfaceFactory $productFactory + * @param SalableProcessor $salableProcessor * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -215,16 +226,17 @@ public function __construct( ProductRepositoryInterface $productRepository, \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory $typeConfigurableFactory, \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $eavAttributeFactory, - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory $configurableAttributeFactory, - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory $productCollectionFactory, - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory $attributeCollectionFactory, - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $catalogProductTypeConfigurable, + AttributeFactory $configurableAttributeFactory, + ProductTypeConfigurable\Product\CollectionFactory $productCollectionFactory, + ProductTypeConfigurable\Attribute\CollectionFactory $attributeCollectionFactory, + ProductTypeConfigurable $catalogProductTypeConfigurable, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor, \Magento\Framework\Cache\FrontendInterface $cache = null, \Magento\Customer\Model\Session $customerSession = null, StockRegistryInterface $stockRegistry = null, - ProductInterfaceFactory $productFactory = null + ProductInterfaceFactory $productFactory = null, + SalableProcessor $salableProcessor = null ) { $this->typeConfigurableFactory = $typeConfigurableFactory; $this->_eavAttributeFactory = $eavAttributeFactory; @@ -251,6 +263,7 @@ public function __construct( ); $this->stockRegistry = $stockRegistry ?: ObjectManager::getInstance() ->get(StockRegistryInterface::class); + $this->salableProcessor = $salableProcessor ?: ObjectManager::getInstance()->get(SalableProcessor::class); } /** @@ -262,6 +275,7 @@ private function getCache() if (null === $this->cache) { $this->cache = ObjectManager::getInstance()->get(\Magento\Framework\Cache\FrontendInterface::class); } + return $this->cache; } @@ -274,6 +288,7 @@ private function getCustomerSession() if (null === $this->customerSession) { $this->customerSession = ObjectManager::getInstance()->get(\Magento\Customer\Model\Session::class); } + return $this->customerSession; } @@ -292,6 +307,7 @@ public function getRelationInfo() )->setChildFieldName( 'product_id' ); + return $info; } @@ -329,10 +345,10 @@ public function getParentIdsByChild($childId) */ public function canUseAttribute(\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute) { - return $attribute->getIsGlobal() == \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL && - $attribute->getIsVisible() && - $attribute->usesSource() && - $attribute->getIsUserDefined(); + return $attribute->getIsGlobal() == ScopedAttributeInterface::SCOPE_GLOBAL && + $attribute->getIsVisible() && + $attribute->usesSource() && + $attribute->getIsUserDefined(); } /** @@ -396,6 +412,7 @@ public function getUsedProductAttributeIds($product) } $product->setData($this->usedProductAttributeIds, $usedProductAttributeIds); } + return $product->getData($this->usedProductAttributeIds); } @@ -411,7 +428,7 @@ public function getUsedProductAttributes($product) $usedProductAttributes = []; $usedAttributes = []; foreach ($this->getConfigurableAttributes($product) as $attribute) { - if (!is_null($attribute->getProductAttribute())) { + if ($attribute->getProductAttribute() !== null) { $id = $attribute->getProductAttribute()->getId(); $usedProductAttributes[$id] = $attribute->getProductAttribute(); $usedAttributes[$id] = $attribute; @@ -420,6 +437,7 @@ public function getUsedProductAttributes($product) $product->setData($this->_usedAttributes, $usedAttributes); $product->setData($this->usedProductAttributes, $usedProductAttributes); } + return $product->getData($this->usedProductAttributes); } @@ -438,7 +456,7 @@ public function getConfigurableAttributes($product) if (!$product->hasData($this->_configurableAttributes)) { $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $productId = $product->getData($metadata->getLinkField()); - $cacheId = __CLASS__ . $productId . '_' . $product->getStoreId(); + $cacheId = __CLASS__ . $productId . '_' . $product->getStoreId(); $configurableAttributes = $this->getCache()->load($cacheId); $configurableAttributes = $this->hasCacheData($configurableAttributes); if ($configurableAttributes) { @@ -456,6 +474,7 @@ public function getConfigurableAttributes($product) $product->setData($this->_configurableAttributes, $configurableAttributes); } \Magento\Framework\Profiler::stop('CONFIGURABLE:' . __METHOD__); + return $product->getData($this->_configurableAttributes); } @@ -474,6 +493,7 @@ protected function hasCacheData($configurableAttributes) } } } + return false; } @@ -488,7 +508,7 @@ public function resetConfigurableAttributes($product) $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $productId = $product->getData($metadata->getLinkField()); $product->unsetData($this->_configurableAttributes); - $cacheId = __CLASS__ . $productId . '_' . $product->getStoreId(); + $cacheId = __CLASS__ . $productId . '_' . $product->getStoreId(); $this->getCache()->remove($cacheId); $this->getCache()->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::TYPE_CODE . '_' . $productId]); @@ -525,6 +545,7 @@ public function getConfigurableAttributesAsArray($product) 'options' => $eavAttribute->getSource()->getAllOptions(false), ]; } + return $res; } @@ -532,7 +553,7 @@ public function getConfigurableAttributesAsArray($product) * Retrieve configurable attribute collection * * @param \Magento\Catalog\Model\Product $product - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection + * @return ProductTypeConfigurable\Attribute\Collection */ public function getConfigurableAttributeCollection(\Magento\Catalog\Model\Product $product) { @@ -555,6 +576,7 @@ public function getUsedProductIds($product) } $product->setData($this->_usedProductIds, $usedProductIds); } + return $product->getData($this->_usedProductIds); } @@ -637,7 +659,8 @@ function ($item) { $product->setData($this->_usedProducts, $usedProducts); } \Magento\Framework\Profiler::stop('CONFIGURABLE:' . __METHOD__); - $usedProducts = $product->getData($this->_usedProducts); + $usedProducts = $product->getData($this->_usedProducts); + return $usedProducts; } @@ -660,7 +683,7 @@ protected function getGalleryReadHandler() * Retrieve related products collection * * @param \Magento\Catalog\Model\Product $product - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection + * @return ProductTypeConfigurable\Product\Collection */ public function getUsedProductCollection($product) { @@ -670,7 +693,7 @@ public function getUsedProductCollection($product) )->setProductFilter( $product ); - if (!is_null($this->getStoreFilter($product))) { + if ($this->getStoreFilter($product) !== null) { $collection->addStoreFilter($this->getStoreFilter($product)); } @@ -725,7 +748,7 @@ public function save($product) { parent::save($product); $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); - $cacheId = __CLASS__ . $product->getData($metadata->getLinkField()) . '_' . $product->getStoreId(); + $cacheId = __CLASS__ . $product->getData($metadata->getLinkField()) . '_' . $product->getStoreId(); $this->cache->remove($cacheId); $extensionAttributes = $product->getExtensionAttributes(); @@ -738,6 +761,7 @@ public function save($product) if (empty($extensionAttributes->getConfigurableProductLinks())) { $this->saveRelatedProducts($product); } + return $this; } @@ -767,7 +791,8 @@ private function saveConfigurableOptions(ProductInterface $product) $attributeData['attribute_id'] = $configurableAttribute->getAttributeId(); } elseif (!empty($attributeData['attribute_id'])) { $attribute = $this->_eavConfig->getAttribute( - \Magento\Catalog\Model\Product::ENTITY, $attributeData['attribute_id'] + \Magento\Catalog\Model\Product::ENTITY, + $attributeData['attribute_id'] ); $attributeData['attribute_id'] = $attribute->getId(); if (!$this->canUseAttribute($attribute)) { @@ -785,7 +810,7 @@ private function saveConfigurableOptions(ProductInterface $product) ->setProductId($product->getData($metadata->getLinkField())) ->save(); } - /** @var $configurableAttributesCollection \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection */ + /** @var $configurableAttributesCollection ProductTypeConfigurable\Attribute\Collection */ $configurableAttributesCollection = $this->_attributeCollectionFactory->create(); $configurableAttributesCollection->setProductFilter($product); $configurableAttributesCollection->addFieldToFilter( @@ -824,10 +849,10 @@ public function isSalable($product) $salable = parent::isSalable($product); if ($salable !== false) { - if (!is_null($product)) { - $this->setStoreFilter($product->getStoreId(), $product); - } - $salable = count($this->getSalableUsedProducts($product)) > 0; + $collection = $this->getUsedProductCollection($product); + $collection->addStoreFilter($this->getStoreFilter($product)); + $collection = $this->salableProcessor->process($collection); + $salable = 0 !== $collection->getSize(); } return $salable; @@ -883,6 +908,7 @@ public function getProductByAttributes($attributesInfo, $product) } } } + return null; } @@ -922,6 +948,7 @@ public function getSelectedAttributesInfo($product) } } \Magento\Framework\Profiler::stop('CONFIGURABLE:' . __METHOD__); + return $attributes; } @@ -1013,6 +1040,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p $_result[0]->setCartQty(1); } $result[] = $_result[0]; + return $result; } else { if (!$this->_isStrictProcessMode($processMode)) { @@ -1050,6 +1078,7 @@ public function checkProductBuyState($product) throw new \Magento\Framework\Exception\LocalizedException($this->getSpecifyOptionMessage()); } } + return $this; } @@ -1099,6 +1128,7 @@ public function isVirtual($product) return $optionProduct->isVirtual(); } } + return parent::isVirtual($product); } @@ -1158,6 +1188,7 @@ public function assignProductToOption($optionProduct, $option, $product) } else { $option->getItem()->setHasConfigurationUnavailableError(true); } + return $this; } @@ -1232,6 +1263,7 @@ public function getConfigurableOptions($product) * Delete data specific for Configurable product type * * @param \Magento\Catalog\Model\Product $product + * @return void */ public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product) { @@ -1252,6 +1284,7 @@ public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product) public function getAttributeById($attributeId, $product) { $attribute = parent::getAttributeById($attributeId, $product); + return $attribute ?: $this->_eavAttributeFactory->create()->load($attributeId); } @@ -1271,6 +1304,7 @@ public function setImageFromChildProduct(\Magento\Catalog\Model\Product $product } } } + return parent::setImageFromChildProduct($product); } @@ -1283,6 +1317,7 @@ private function getMetadataPool() if (!$this->metadataPool) { $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); } + return $this->metadataPool; } @@ -1296,6 +1331,7 @@ private function getCatalogConfig() if (!$this->catalogConfig) { $this->catalogConfig = ObjectManager::getInstance()->get(Config::class); } + return $this->catalogConfig; } @@ -1314,8 +1350,10 @@ public function getSalableUsedProducts(Product $product, $requiredAttributeIds = $product->getId(), $product->getStore()->getWebsiteId() ); + return (int)$stockStatus->getStockStatus() === Status::STATUS_IN_STOCK && $product->isSalable(); }); + return $usedSalableProducts; } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Collection/SalableProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Collection/SalableProcessorTest.php new file mode 100644 index 0000000000000..b6f9066b60e18 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Collection/SalableProcessorTest.php @@ -0,0 +1,75 @@ +objectManager = new ObjectManager($this); + + $this->stockStatusFactory = $this->getMockBuilder( + \Magento\CatalogInventory\Model\ResourceModel\Stock\StatusFactory::class + ) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = $this->objectManager->getObject( + \Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor::class, + [ + 'stockStatusFactory' => $this->stockStatusFactory, + ] + ); + } + + public function testProcess() + { + $productCollection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class) + ->setMethods(['addAttributeToFilter']) + ->disableOriginalConstructor() + ->getMock(); + + $productCollection->expects($this->once()) + ->method('addAttributeToFilter') + ->with(ProductInterface::STATUS, Status::STATUS_ENABLED) + ->will($this->returnSelf()); + + $stockStatusResource = $this->getMockBuilder(\Magento\CatalogInventory\Model\ResourceModel\Stock\Status::class) + ->setMethods(['addStockDataToCollection']) + ->disableOriginalConstructor() + ->getMock(); + $stockStatusResource->expects($this->once()) + ->method('addStockDataToCollection') + ->with($productCollection, true) + ->will($this->returnSelf()); + + $this->stockStatusFactory + ->expects($this->once()) + ->method('create') + ->will($this->returnValue($stockStatusResource)); + + $this->model->process($productCollection); + + $this->assertTrue($productCollection->hasFlag(self::STOCK_FLAG)); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index 638080f225920..68339bdde3acc 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -13,15 +13,20 @@ use Magento\Catalog\Model\Config; use Magento\CatalogInventory\Model\Stock\Status; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; use Magento\Framework\EntityManager\EntityMetadata; use Magento\Framework\EntityManager\MetadataPool; use Magento\Customer\Model\Session; +use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; /** * Class \Magento\ConfigurableProduct\Test\Unit\Model\Product\Type\ConfigurableTest * * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class ConfigurableTest extends \PHPUnit_Framework_TestCase { @@ -104,6 +109,11 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase */ private $productFactory; + /** + * @var SalableProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + private $salableProcessor; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -111,7 +121,13 @@ protected function setUp() { $this->_objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $eventManager = $this->getMock(\Magento\Framework\Event\ManagerInterface::class, [], [], '', false); - $fileStorageDbMock = $this->getMock(\Magento\MediaStorage\Helper\File\Storage\Database::class, [], [], '', false); + $fileStorageDbMock = $this->getMock( + \Magento\MediaStorage\Helper\File\Storage\Database::class, + [], + [], + '', + false + ); $filesystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) ->disableOriginalConstructor() ->getMock(); @@ -142,7 +158,7 @@ protected function setUp() false ); $this->_attributeCollectionFactory = $this->getMock( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory::class, + CollectionFactory::class, ['create'], [], '', @@ -181,6 +197,14 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->salableProcessor = $this->getMock( + SalableProcessor::class, + [], + [], + '', + false + ); + $this->_model = $this->_objectHelper->getObject( Configurable::class, [ @@ -199,13 +223,11 @@ protected function setUp() 'cache' => $this->cache, 'catalogConfig' => $this->catalogConfig, 'stockRegistry' => $this->stockRegistry, - 'productFactory' => $this->productFactory + 'productFactory' => $this->productFactory, + 'salableProcessor' => $this->salableProcessor, + 'metadataPool' => $this->metadataPool, ] ); - $refClass = new \ReflectionClass(Configurable::class); - $refProperty = $refClass->getProperty('metadataPool'); - $refProperty->setAccessible(true); - $refProperty->setValue($this->_model, $this->metadataPool); } public function testHasWeightTrue() @@ -259,7 +281,7 @@ public function testSave() $product->expects($this->any()) ->method('getData') ->willReturnMap($dataMap); - $attribute = $this->getMockBuilder(\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['addData', 'setStoreId', 'setProductId', 'save', '__wakeup', '__sleep']) ->getMock(); @@ -273,9 +295,9 @@ public function testSave() $this->_configurableAttributeFactoryMock->expects($this->any())->method('create') ->will($this->returnValue($attribute)); - $attributeCollection = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class - )->setMethods(['setProductFilter', 'addFieldToFilter', 'walk'])->disableOriginalConstructor() + $attributeCollection = $this->getMockBuilder(Collection::class) + ->setMethods(['setProductFilter', 'addFieldToFilter', 'walk']) + ->disableOriginalConstructor() ->getMock(); $this->_attributeCollectionFactory->expects($this->any())->method('create') ->will($this->returnValue($attributeCollection)); @@ -312,27 +334,19 @@ public function testCanUseAttribute() '', false ); - $attribute->expects($this->once()) - ->method('getIsGlobal') - ->will($this->returnValue(1)); - $attribute->expects($this->once()) - ->method('getIsVisible') - ->will($this->returnValue(1)); - $attribute->expects($this->once()) - ->method('usesSource') - ->will($this->returnValue(1)); - $attribute->expects($this->once()) - ->method('getIsUserDefined') - ->will($this->returnValue(1)); + $attribute->expects($this->once())->method('getIsGlobal')->will($this->returnValue(1)); + $attribute->expects($this->once())->method('getIsVisible')->will($this->returnValue(1)); + $attribute->expects($this->once())->method('usesSource')->will($this->returnValue(1)); + $attribute->expects($this->once())->method('getIsUserDefined')->will($this->returnValue(1)); $this->assertTrue($this->_model->canUseAttribute($attribute)); } public function testGetUsedProducts() { - $attributeCollection = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class - )->setMethods(['setProductFilter', 'addFieldToFilter', 'walk'])->disableOriginalConstructor() + $attributeCollection = $this->getMockBuilder(Collection::class) + ->setMethods(['setProductFilter', 'addFieldToFilter', 'walk']) + ->disableOriginalConstructor() ->getMock(); $attributeCollection->expects($this->any())->method('setProductFilter')->will($this->returnSelf()); $this->_attributeCollectionFactory->expects($this->any())->method('create') @@ -407,7 +421,6 @@ public function testGetUsedProducts() new \ArrayIterator([]) ); - $this->_productCollectionFactory->expects($this->any())->method('create') ->will($this->returnValue($productCollection)); $this->_model->getUsedProducts($product); @@ -490,7 +503,7 @@ public function testGetConfigurableAttributesAsArray($productStore, $attributeSt $eavAttribute->expects($this->any())->method('getStoreLabel')->will($this->returnValue('Store Label')); $eavAttribute->expects($this->any())->method('setStoreId')->with($attributeStore); - $attribute = $this->getMockBuilder(\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['getProductAttribute', '__wakeup', '__sleep']) ->getMock(); @@ -539,7 +552,7 @@ public function testGetConfigurableAttributes() $configurableAttributes = '_cache_instance_configurable_attributes'; /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */ - $product = $this->getMockBuilder('Magento\Catalog\Model\Product') + $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->setMethods(['getData', 'hasData', 'setData', 'getIdentities', 'getId', 'getStoreId']) ->disableOriginalConstructor() ->getMock(); @@ -563,7 +576,7 @@ public function testGetConfigurableAttributes() ->willReturn('link'); $attributeCollection = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class + Collection::class ) ->setMethods(['setProductFilter', 'orderByPosition', 'load']) ->disableOriginalConstructor() @@ -576,7 +589,7 @@ public function testGetConfigurableAttributes() ->method('process') ->with( $this->isInstanceOf( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class + Collection::class ) ); @@ -613,7 +626,7 @@ public function testHasOptionsConfigurableAttribute() ->setMethods(['__wakeup', 'getAttributeCode', 'getOptions', 'hasData', 'getData']) ->disableOriginalConstructor() ->getMock(); - $attributeMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attributeMock = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMock(); @@ -652,29 +665,49 @@ public function testIsSalable() ->setMethods(['__wakeup', 'getStatus', 'hasData', 'getData', 'getStoreId', 'setData']) ->disableOriginalConstructor() ->getMock(); - $childProductMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMethods(['__wakeup', 'isSalable', 'getStore']) - ->disableOriginalConstructor() - ->getMock(); - - $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->setMethods(['getWebsiteId']) - ->getMockForAbstractClass(); - - $stockStatus = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockStatusInterface::class) - ->setMethods(['getStockStatus']) - ->getMockForAbstractClass(); $productMock->expects($this->once())->method('getStatus')->willReturn(1); $productMock->expects($this->any())->method('hasData')->willReturn(true); $productMock->expects($this->at(2))->method('getData')->with('is_salable')->willReturn(true); - $productMock->expects($this->once())->method('getStoreId')->willReturn(1); - $productMock->expects($this->once())->method('setData')->willReturnSelf(); - $productMock->expects($this->at(6))->method('getData')->willReturn([$childProductMock]); - $childProductMock->expects($this->once())->method('getStore')->willReturn($storeMock); - $this->stockRegistry->expects($this->once())->method('getStockStatus')->willReturn($stockStatus); - $stockStatus->expects($this->once())->method('getStockStatus')->willReturn(1); - $childProductMock->expects($this->once())->method('isSalable')->willReturn(true); + + $productCollection = $this->getMockBuilder( + \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection::class + ) + ->setMethods( + [ + 'setFlag', + 'setProductFilter', + 'addStoreFilter', + 'getSize' + ] + ) + ->disableOriginalConstructor() + ->getMock(); + $productCollection->expects($this->any())->method('setFlag')->will($this->returnSelf()); + $productCollection + ->expects($this->once()) + ->method('setProductFilter') + ->with($productMock) + ->will($this->returnSelf()); + $productCollection + ->expects($this->once()) + ->method('addStoreFilter') + ->will($this->returnSelf()); + $productCollection + ->expects($this->once()) + ->method('getSize') + ->willReturn(1); + + $this->salableProcessor + ->expects($this->once()) + ->method('process') + ->with($productCollection) + ->will($this->returnValue($productCollection)); + + $this->_productCollectionFactory + ->expects($this->once()) + ->method('create') + ->will($this->returnValue($productCollection)); $this->assertTrue($this->_model->isSalable($productMock)); } @@ -748,12 +781,14 @@ public function testGetSelectedAttributesInfo() ->setMethods(['__wakeup', 'getCustomOption', 'hasData', 'getData']) ->disableOriginalConstructor() ->getMock(); - $optionMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class) + $optionMock = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class + ) ->setMethods(['getValue']) ->disableOriginalConstructor() ->getMock(); $usedAttributeMock = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class + Attribute::class ) ->setMethods(['getProductAttribute']) ->disableOriginalConstructor() @@ -965,4 +1000,3 @@ public function testSetImageFromChildProduct() $this->_model->setImageFromChildProduct($productMock); } } - diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php index 350c9585d15fd..54398f8bf9dc7 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php @@ -41,7 +41,7 @@ public function __construct(CurlTransport $transport) * @param array $options [optional] * @return void */ - protected function execute($command, $options = []) + public function execute($command, $options = []) { $curl = $this->transport; $curl->write($this->prepareUrl($command, $options), [], CurlInterface::GET); @@ -53,10 +53,10 @@ protected function execute($command, $options = []) * Prepare url. * * @param string $command - * @param array $options + * @param array $options [optional] * @return string */ - private function prepareUrl($command, array $options) + private function prepareUrl($command, $options = []) { $command .= ' ' . implode(' ', $options); return $_ENV['app_frontend_url'] . self::URL . '?command=' . urlencode($command); diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Cache.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Cache.php index 272e3158d3c27..4b0fc013d04ec 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Cache.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Cache.php @@ -11,8 +11,24 @@ /** * Handle cache for tests executions. */ -class Cache extends Cli +class Cache { + /** + * Perform bin/magento commands from command line for functional tests executions. + * + * @var Cli + */ + private $cli; + + /** + * Cache constructor. + * @param Cli $cli + */ + public function __construct(Cli $cli) + { + $this->cli = $cli; + } + /** * Parameter for flush cache command. */ @@ -38,7 +54,7 @@ class Cache extends Cli public function flush(array $cacheTypes = []) { $options = empty($cacheTypes) ? '' : ' ' . implode(' ', $cacheTypes); - parent::execute(Cache::PARAM_CACHE_FLUSH . $options); + $this->cli->execute(Cache::PARAM_CACHE_FLUSH . $options); } /** @@ -49,7 +65,7 @@ public function flush(array $cacheTypes = []) */ public function disableCache($cacheType = null) { - parent::execute(Cache::PARAM_CACHE_DISABLE . ($cacheType ? " $cacheType" : '')); + $this->cli->execute(Cache::PARAM_CACHE_DISABLE . ($cacheType ? " $cacheType" : '')); } /** @@ -60,6 +76,6 @@ public function disableCache($cacheType = null) */ public function enableCache($cacheType = null) { - parent::execute(Cache::PARAM_CACHE_ENABLE . ($cacheType ? " $cacheType" : '')); + $this->cli->execute(Cache::PARAM_CACHE_ENABLE . ($cacheType ? " $cacheType" : '')); } } diff --git a/dev/tests/functional/tests/app/Magento/Config/Test/TestStep/SetupConfigurationStep.php b/dev/tests/functional/tests/app/Magento/Config/Test/TestStep/SetupConfigurationStep.php index a1f3996197517..b5df8040b575d 100644 --- a/dev/tests/functional/tests/app/Magento/Config/Test/TestStep/SetupConfigurationStep.php +++ b/dev/tests/functional/tests/app/Magento/Config/Test/TestStep/SetupConfigurationStep.php @@ -38,11 +38,11 @@ class SetupConfigurationStep implements TestStepInterface protected $rollback; /** - * Flush cache. + * Flush cache after change config flag. * * @var bool */ - protected $flushCache; + private $flushCache; /** * Cli command to do operations with cache. @@ -94,7 +94,7 @@ public function run() $config = $this->fixtureFactory->createByCode('configData', ['dataset' => $configDataSet . $prefix]); if ($config->hasData('section')) { $config->persist(); - $result = array_merge($result, $config->getSection()); + $result = array_replace_recursive($result, $config->getSection()); } if ($this->flushCache) { $this->cache->flush(); diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigData.xml new file mode 100644 index 0000000000000..c04ba239a9ca5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigData.xml @@ -0,0 +1,26 @@ + + + + + + + default + 0 + 1 + + + + + + default + 0 + 0 + + + + diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml index 5839ec2562bc7..66a4748e71288 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml @@ -508,6 +508,61 @@ + + + + + + 12.00 + Yes + + + + + + catalogProductSimple::offline + + + catalogProductAttribute::attribute_type_dropdown_one_option + + + + + + + + + option_key_1_%isolation% + 560 + Yes + + + option_key_2_%isolation% + 560 + Yes + + + + + + catalogProductAttribute::attribute_type_dropdown_two_options + + + catalogProductSimple::out_of_stock + catalogProductSimple::offline + + + + 10 + 1 + + + 20 + 2 + + + + diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.php index 2344e3eb80e75..f220109f86ca6 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.php @@ -8,8 +8,10 @@ use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; use Magento\Catalog\Test\Page\Adminhtml\CatalogProductNew; +use Magento\Config\Test\TestStep\SetupConfigurationStep; use Magento\ConfigurableProduct\Test\Fixture\ConfigurableProduct; use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\TestStep\TestStepFactory; /** * Test Coverage for CreateConfigurableProductEntity @@ -58,31 +60,69 @@ class CreateConfigurableProductEntityTest extends Injectable */ protected $productNew; + /** + * Factory for creation SetupConfigurationStep. + * + * @var TestStepFactory + */ + protected $testStepFactory; + + /** + * Configuration data holder. + * + * @var string + */ + protected $configData = null; + /** * Injection data. * * @param CatalogProductIndex $productIndex * @param CatalogProductNew $productNew + * @param TestStepFactory $testStepFactory * @return void */ - public function __inject(CatalogProductIndex $productIndex, CatalogProductNew $productNew) - { + public function __inject( + CatalogProductIndex $productIndex, + CatalogProductNew $productNew, + TestStepFactory $testStepFactory + ) { $this->productIndex = $productIndex; $this->productNew = $productNew; + $this->testStepFactory = $testStepFactory; } /** * Test create catalog Configurable product run. * * @param ConfigurableProduct $product + * @param string|null $configData * @return void */ - public function test(ConfigurableProduct $product) + public function test(ConfigurableProduct $product, $configData = null) { + //Preconditions + $this->configData = $configData; + $this->testStepFactory->create( + SetupConfigurationStep::class, + ['configData' => $this->configData, 'flushCache' => true] + )->run(); + // Steps $this->productIndex->open(); $this->productIndex->getGridPageActionBlock()->addProduct('configurable'); $this->productNew->getProductForm()->fill($product); $this->productNew->getFormPageActions()->save($product); } + + /** + * Revert Display Out Of Stock Products configuration. + */ + public function teatDown() + { + $this->testStepFactory->create( + SetupConfigurationStep::class, + ['configData' => $this->configData, 'flushCache' => true] + )->cleanUp(); + } } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml index 2f46e7dd3adc3..2e86dd00971a1 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml @@ -129,5 +129,59 @@ + + test_type:acceptance_test, test_type:extended_acceptance_test + with_out_of_stock_item + Configurable Product %isolation% + configurable_sku_%isolation% + 100 + default + no + default_subcategory + configurable-product-%isolation% + custom_attribute_set_%isolation% + display_out_of_stock_products + + + + + + + + test_type:acceptance_test, test_type:extended_acceptance_test + with_disabled_item + Configurable Product %isolation% + configurable_sku_%isolation% + 100 + default + no + default_subcategory + configurable-product-%isolation% + custom_attribute_set_%isolation% + display_out_of_stock_products + + + + + + + + test_type:acceptance_test, test_type:extended_acceptance_test + with_one_disabled_item_and_one_out_of_stock_item + Configurable Product %isolation% + configurable_sku_%isolation% + 100 + default + no + default_subcategory + configurable-product-%isolation% + custom_attribute_set_%isolation% + display_out_of_stock_products + + + + + + diff --git a/dev/tests/functional/utils/command.php b/dev/tests/functional/utils/command.php index 397f60cd07ea1..fd99536659423 100644 --- a/dev/tests/functional/utils/command.php +++ b/dev/tests/functional/utils/command.php @@ -4,9 +4,17 @@ * See COPYING.txt for license details. */ +$commandList = [ + 'cache:flush', + 'cache:disable', + 'cache:enable', +]; + if (isset($_GET['command'])) { $command = urldecode($_GET['command']); - exec('php -f ../../../../bin/magento ' . $command); + if (in_array($command, $commandList)) { + exec('php -f ../../../../bin/magento ' . $command); + } } else { throw new \InvalidArgumentException("Command GET parameter is not set."); } diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php new file mode 100644 index 0000000000000..8ee4f8dd7d61b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php @@ -0,0 +1,56 @@ +fullAction = Bootstrap::getObjectManager()->create( + \Magento\CatalogSearch\Model\Indexer\Fulltext\Action\Full::class + ); + } + + public function testRebuildStoreIndexConfigurable() + { + $storeId = 1; + + $simpleProductId = $this->getIdBySku('simple_10'); + $configProductId = $this->getIdBySku('configurable'); + + $expected = [ + $simpleProductId, + $configProductId + ]; + $storeIndexDataSimple = $this->fullAction->rebuildStoreIndex($storeId, [$simpleProductId]); + $storeIndexDataExpected = $this->fullAction->rebuildStoreIndex($storeId, $expected); + + $this->assertEquals($storeIndexDataSimple, $storeIndexDataExpected); + } + + /** + * @param string $sku + * @return int + */ + private function getIdBySku($sku) + { + /** @var Product $product */ + $product = Bootstrap::getObjectManager()->get(Product::class); + + return $product->getIdBySku($sku); + } +}