diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php
index 5c3fd4730aaed..0bb468b77ee6e 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php
@@ -59,7 +59,7 @@ public function beforeSave($object)
if (isset($stockData['qty']) && $stockData['qty'] === '') {
$stockData['qty'] = null;
}
- if ($object->getStockData() !== null || $stockData !== null) {
+ if ($object->getStockData() !== null && $stockData !== null) {
$object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData));
}
$object->unsetData($this->getAttribute()->getAttributeCode());
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php
new file mode 100644
index 0000000000000..e6a995b654703
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php
@@ -0,0 +1,26 @@
+baseSelectProcessors = $baseSelectProcessors;
+ }
+
+ /**
+ * @param Select $select
+ * @return Select
+ */
+ public function process(Select $select)
+ {
+ foreach ($this->baseSelectProcessors as $baseSelectProcessor) {
+ $select = $baseSelectProcessor->process($select);
+ }
+ return $select;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
index ea72691ea0039..3aa6642c82d84 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
@@ -6,7 +6,8 @@
namespace Magento\Catalog\Model\ResourceModel\Product\Indexer;
use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface;
@@ -32,22 +33,31 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild
*/
private $metadataPool;
+ /**
+ * @var BaseSelectProcessorInterface
+ */
+ private $baseSelectProcessor;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
* @param \Magento\Customer\Model\Session $customerSession
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param BaseSelectProcessorInterface $baseSelectProcessor
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\ResourceConnection $resourceConnection,
\Magento\Customer\Model\Session $customerSession,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ BaseSelectProcessorInterface $baseSelectProcessor = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
$this->customerSession = $customerSession;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -58,24 +68,27 @@ public function build($productId)
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
$productTable = $this->resource->getTableName('catalog_product_entity');
- return [$this->resource->getConnection()->select()
+ $priceSelect = $this->resource->getConnection()->select()
->from(['parent' => $productTable], '')
->joinInner(
['link' => $this->resource->getTableName('catalog_product_relation')],
"link.parent_id = parent.$linkField",
[]
)->joinInner(
- ['child' => $productTable],
- "child.entity_id = link.child_id",
+ [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
['t' => $this->resource->getTableName('catalog_product_index_price')],
- 't.entity_id = child.entity_id',
+ sprintf('t.entity_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
[]
- )->where('parent.entity_id = ? ', $productId)
+ )->where('parent.entity_id = ?', $productId)
->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId())
->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId())
->order('t.min_price ' . Select::SQL_ASC)
- ->limit(1)];
+ ->limit(1);
+ $priceSelect = $this->baseSelectProcessor->process($priceSelect);
+
+ return [$priceSelect];
}
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
index d325ab1a9a08d..7caa72b367979 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
@@ -37,25 +38,34 @@ class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilde
*/
private $metadataPool;
+ /**
+ * @var BaseSelectProcessorInterface
+ */
+ private $baseSelectProcessor;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
* @param \Magento\Eav\Model\Config $eavConfig
* @param \Magento\Catalog\Helper\Data $catalogHelper
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param BaseSelectProcessorInterface $baseSelectProcessor
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\ResourceConnection $resourceConnection,
\Magento\Eav\Model\Config $eavConfig,
\Magento\Catalog\Helper\Data $catalogHelper,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ BaseSelectProcessorInterface $baseSelectProcessor = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
$this->eavConfig = $eavConfig;
$this->catalogHelper = $catalogHelper;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -74,18 +84,19 @@ public function build($productId)
"link.parent_id = parent.$linkField",
[]
)->joinInner(
- ['child' => $productTable],
- "child.entity_id = link.child_id",
+ [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField),
['entity_id']
)->joinInner(
['t' => $priceAttribute->getBackendTable()],
- "t.$linkField = child.$linkField",
+ sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
[]
- )->where('parent.entity_id = ? ', $productId)
+ )->where('parent.entity_id = ?', $productId)
->where('t.attribute_id = ?', $priceAttribute->getAttributeId())
->where('t.value IS NOT NULL')
->order('t.value ' . Select::SQL_ASC)
->limit(1);
+ $priceSelect = $this->baseSelectProcessor->process($priceSelect);
$priceSelectDefault = clone $priceSelect;
$priceSelectDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php
index 792a8f5b86d10..68eaf206e293f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php
@@ -7,9 +7,13 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBuilderInterface
{
/**
@@ -47,6 +51,11 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui
*/
private $metadataPool;
+ /**
+ * @var BaseSelectProcessorInterface
+ */
+ private $baseSelectProcessor;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
@@ -55,6 +64,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui
* @param \Magento\Framework\Stdlib\DateTime $dateTime
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param BaseSelectProcessorInterface $baseSelectProcessor
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
@@ -63,7 +73,8 @@ public function __construct(
\Magento\Catalog\Helper\Data $catalogHelper,
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ BaseSelectProcessorInterface $baseSelectProcessor = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
@@ -72,6 +83,8 @@ public function __construct(
$this->dateTime = $dateTime;
$this->localeDate = $localeDate;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -95,12 +108,12 @@ public function build($productId)
"link.parent_id = parent.$linkField",
[]
)->joinInner(
- ['child' => $productTable],
- "child.entity_id = link.child_id",
+ [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
['t' => $specialPriceAttribute->getBackendTable()],
- "t.$linkField = child.$linkField",
+ sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
[]
)->joinLeft(
['special_from' => $specialPriceFromDate->getBackendTable()],
@@ -116,7 +129,7 @@ public function build($productId)
$specialPriceToDate->getAttributeId()
),
''
- )->where('parent.entity_id = ? ', $productId)
+ )->where('parent.entity_id = ?', $productId)
->where('t.attribute_id = ?', $specialPriceAttribute->getAttributeId())
->where('t.value IS NOT NULL')
->where(
@@ -127,6 +140,7 @@ public function build($productId)
$currentDate
)->order('t.value ' . Select::SQL_ASC)
->limit(1);
+ $specialPrice = $this->baseSelectProcessor->process($specialPrice);
$specialPriceDefault = clone $specialPrice;
$specialPriceDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php
index d2d6d89c0a2a5..25bf83f837de7 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php
@@ -6,7 +6,7 @@
namespace Magento\Catalog\Model\ResourceModel\Product;
use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Catalog\Model\Product;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilderInterface
@@ -41,25 +41,34 @@ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilde
*/
private $metadataPool;
+ /**
+ * @var BaseSelectProcessorInterface
+ */
+ private $baseSelectProcessor;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
* @param \Magento\Customer\Model\Session $customerSession
* @param \Magento\Catalog\Helper\Data $catalogHelper
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param BaseSelectProcessorInterface $baseSelectProcessor
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\ResourceConnection $resourceConnection,
\Magento\Customer\Model\Session $customerSession,
\Magento\Catalog\Helper\Data $catalogHelper,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ BaseSelectProcessorInterface $baseSelectProcessor = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
$this->customerSession = $customerSession;
$this->catalogHelper = $catalogHelper;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -77,18 +86,19 @@ public function build($productId)
"link.parent_id = parent.$linkField",
[]
)->joinInner(
- ['child' => $productTable],
- "child.entity_id = link.child_id",
+ [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
['t' => $this->resource->getTableName('catalog_product_entity_tier_price')],
- "t.$linkField = child.$linkField",
+ sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
[]
- )->where('parent.entity_id = ? ', $productId)
+ )->where('parent.entity_id = ?', $productId)
->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId())
->where('t.qty = ?', 1)
->order('t.value ' . Select::SQL_ASC)
->limit(1);
+ $priceSelect = $this->baseSelectProcessor->process($priceSelect);
$priceSelectDefault = clone $priceSelect;
$priceSelectDefault->where('t.website_id = ?', self::DEFAULT_WEBSITE_ID);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php
new file mode 100644
index 0000000000000..656998113fdb9
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php
@@ -0,0 +1,61 @@
+eavConfig = $eavConfig;
+ $this->metadataPool = $metadataPool;
+ }
+
+ /**
+ * @param Select $select
+ * @return Select
+ */
+ public function process(Select $select)
+ {
+ $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+ $statusAttribute = $this->eavConfig->getAttribute(Product::ENTITY, ProductInterface::STATUS);
+
+ $select->join(
+ ['status_attr' => $statusAttribute->getBackendTable()],
+ sprintf('status_attr.%s = %s.%1$s', $linkField, self::PRODUCT_TABLE_ALIAS),
+ []
+ )
+ ->where('status_attr.attribute_id = ?', $statusAttribute->getAttributeId())
+ ->where('status_attr.value = ?', Status::STATUS_ENABLED);
+
+ return $select;
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php
index ec9cb624db79a..32d62fd7a998e 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php
@@ -125,4 +125,17 @@ public function testBeforeSaveQtyIsZero()
$stockData = $object->getStockData();
$this->assertEquals(0, $stockData['qty']);
}
+
+ public function testBeforeSaveNoStockData()
+ {
+ $object = new \Magento\Framework\DataObject(
+ [
+ self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0]
+ ]
+ );
+
+ $this->model->beforeSave($object);
+ $this->assertNull($object->getStockData());
+ $this->assertNull($object->getData(self::ATTRIBUTE_NAME));
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php
new file mode 100644
index 0000000000000..d296474c202f0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php
@@ -0,0 +1,54 @@
+objectManager = new ObjectManager($this);
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\InputException
+ */
+ public function testInitializeWithWrongProcessorInstance()
+ {
+ $processorValid = $this->getMock(BaseSelectProcessorInterface::class);
+ $processorInvalid = $this->getMock(\stdClass::class);
+
+ $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [
+ 'baseSelectProcessors' => [$processorValid, $processorInvalid],
+ ]);
+ }
+
+ public function testProcess()
+ {
+ $select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();
+
+ $processorFirst = $this->getMock(BaseSelectProcessorInterface::class);
+ $processorFirst->expects($this->once())->method('process')->with($select)->willReturn($select);
+
+ $processorSecond = $this->getMock(BaseSelectProcessorInterface::class);
+ $processorSecond->expects($this->once())->method('process')->with($select)->willReturn($select);
+
+ /** @var CompositeBaseSelectProcessor $baseSelectProcessors */
+ $baseSelectProcessors = $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [
+ 'baseSelectProcessors' => [$processorFirst, $processorSecond],
+ ]);
+ $this->assertEquals($select, $baseSelectProcessors->process($select));
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php
new file mode 100644
index 0000000000000..0909f754a01c2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php
@@ -0,0 +1,102 @@
+eavConfig = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock();
+ $this->metadataPool = $this->getMockBuilder(MetadataPool::class)->disableOriginalConstructor()->getMock();
+ $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();
+
+ $this->statusBaseSelectProcessor = (new ObjectManager($this))->getObject(StatusBaseSelectProcessor::class, [
+ 'eavConfig' => $this->eavConfig,
+ 'metadataPool' => $this->metadataPool,
+ ]);
+ }
+
+ public function testProcess()
+ {
+ $linkField = 'link_field';
+ $backendTable = 'backend_table';
+ $attributeId = 'attribute_id';
+
+ $metadata = $this->getMock(EntityMetadataInterface::class);
+ $metadata->expects($this->once())
+ ->method('getLinkField')
+ ->willReturn($linkField);
+ $this->metadataPool->expects($this->once())
+ ->method('getMetadata')
+ ->with(ProductInterface::class)
+ ->willReturn($metadata);
+
+ $statusAttribute = $this->getMockBuilder(AttributeInterface::class)
+ ->setMethods(['getBackendTable', 'getAttributeId'])
+ ->getMock();
+ $statusAttribute->expects($this->once())
+ ->method('getBackendTable')
+ ->willReturn($backendTable);
+ $statusAttribute->expects($this->once())
+ ->method('getAttributeId')
+ ->willReturn($attributeId);
+ $this->eavConfig->expects($this->once())
+ ->method('getAttribute')
+ ->with(Product::ENTITY, ProductInterface::STATUS)
+ ->willReturn($statusAttribute);
+
+ $this->select->expects($this->once())
+ ->method('join')
+ ->with(
+ ['status_attr' => $backendTable],
+ sprintf('status_attr.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
+ []
+ )
+ ->willReturnSelf();
+ $this->select->expects($this->at(1))
+ ->method('where')
+ ->with('status_attr.attribute_id = ?', $attributeId)
+ ->willReturnSelf();
+ $this->select->expects($this->at(2))
+ ->method('where')
+ ->with('status_attr.value = ?', Status::STATUS_ENABLED)
+ ->willReturnSelf();
+
+ $this->assertEquals($this->select, $this->statusBaseSelectProcessor->process($this->select));
+ }
+}
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index d191f0332f5f2..6b39520ae021e 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -837,4 +837,12 @@
+
+
+
+
+ - Magento\Catalog\Model\ResourceModel\Product\StatusBaseSelectProcessor
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php
new file mode 100644
index 0000000000000..829fa8decda7d
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php
@@ -0,0 +1,51 @@
+resource = $resource;
+ }
+
+ /**
+ * Add stock item filter to selects
+ *
+ * @param Select $select
+ * @return Select
+ */
+ public function process(Select $select)
+ {
+ $stockStatusTable = $this->resource->getTableName('cataloginventory_stock_status');
+
+ /** @var Select $select */
+ $select->join(
+ ['stock' => $stockStatusTable],
+ sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
+ []
+ )
+ ->where('stock.stock_status = ?', Stock::STOCK_IN_STOCK);
+ return $select;
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php
new file mode 100644
index 0000000000000..4756e42ffe602
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php
@@ -0,0 +1,69 @@
+resource = $this->getMockBuilder(ResourceConnection::class)->disableOriginalConstructor()->getMock();
+ $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();
+
+ $this->stockStatusBaseSelectProcessor = (new ObjectManager($this))->getObject(
+ StockStatusBaseSelectProcessor::class,
+ [
+ 'resource' => $this->resource,
+ ]
+ );
+ }
+
+ public function testProcess()
+ {
+ $tableName = 'table_name';
+
+ $this->resource->expects($this->once())
+ ->method('getTableName')
+ ->with('cataloginventory_stock_status')
+ ->willReturn($tableName);
+
+ $this->select->expects($this->once())
+ ->method('join')
+ ->with(
+ ['stock' => $tableName],
+ sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
+ []
+ )
+ ->willReturnSelf();
+ $this->select->expects($this->once())
+ ->method('where')
+ ->with('stock.stock_status = ?', Stock::STOCK_IN_STOCK)
+ ->willReturnSelf();
+
+ $this->stockStatusBaseSelectProcessor->process($this->select);
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml
index 71b42ef89f73c..e3ca5c01cedab 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -79,4 +79,11 @@
Magento\CatalogInventory\Model\Indexer\Stock\Processor
+
+
+
+ - Magento\CatalogInventory\Model\ResourceModel\Product\StockStatusBaseSelectProcessor
+
+
+
diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
index 7d8d44dcbb68a..55b76eb225028 100644
--- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
+++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
@@ -6,7 +6,8 @@
namespace Magento\CatalogRule\Model\ResourceModel\Product;
use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface;
@@ -42,6 +43,11 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec
*/
private $metadataPool;
+ /**
+ * @var BaseSelectProcessorInterface
+ */
+ private $baseSelectProcessor;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
@@ -49,6 +55,7 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec
* @param \Magento\Framework\Stdlib\DateTime $dateTime
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param BaseSelectProcessorInterface $baseSelectProcessor
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
@@ -56,7 +63,8 @@ public function __construct(
\Magento\Customer\Model\Session $customerSession,
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ BaseSelectProcessorInterface $baseSelectProcessor = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
@@ -64,6 +72,8 @@ public function __construct(
$this->dateTime = $dateTime;
$this->localeDate = $localeDate;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -76,25 +86,28 @@ public function build($productId)
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
$productTable = $this->resource->getTableName('catalog_product_entity');
- return [$this->resource->getConnection()->select()
- ->from(['parent' => $productTable], '')
- ->joinInner(
- ['link' => $this->resource->getTableName('catalog_product_relation')],
- "link.parent_id = parent.$linkField",
- []
- )->joinInner(
- ['child' => $productTable],
- "child.entity_id = link.child_id",
- ['entity_id']
- )->joinInner(
- ['t' => $this->resource->getTableName('catalogrule_product_price')],
- 't.product_id = child.entity_id',
- []
- )->where('parent.entity_id = ? ', $productId)
+ $priceSelect = $this->resource->getConnection()->select()
+ ->from(['parent' => $productTable], '')
+ ->joinInner(
+ ['link' => $this->resource->getTableName('catalog_product_relation')],
+ "link.parent_id = parent.$linkField",
+ []
+ )->joinInner(
+ [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
+ ['entity_id']
+ )->joinInner(
+ ['t' => $this->resource->getTableName('catalogrule_product_price')],
+ sprintf('t.product_id = %s.%s', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField),
+ []
+ )->where('parent.entity_id = ?', $productId)
->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId())
->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId())
->where('t.rule_date = ?', $currentDate)
->order('t.rule_price ' . Select::SQL_ASC)
- ->limit(1)];
+ ->limit(1);
+ $priceSelect = $this->baseSelectProcessor->process($priceSelect);
+
+ return [$priceSelect];
}
}
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php
index dcb75fe725dc2..872538d9babab 100644
--- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php
@@ -18,26 +18,6 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac
/** @var Configurable */
private $configurable;
- /**
- * @var RequestSafetyInterface
- */
- private $requestSafety;
-
- /**
- * @var ResourceConnection
- */
- private $resource;
-
- /**
- * @var LinkedProductSelectBuilderInterface
- */
- private $linkedProductSelectBuilder;
-
- /**
- * @var CollectionFactory
- */
- private $collectionFactory;
-
/**
* @var ProductInterface[]
*/
@@ -49,6 +29,7 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac
* @param LinkedProductSelectBuilderInterface $linkedProductSelectBuilder
* @param CollectionFactory $collectionFactory
* @param RequestSafetyInterface $requestSafety
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
Configurable $configurable,
@@ -58,10 +39,6 @@ public function __construct(
RequestSafetyInterface $requestSafety
) {
$this->configurable = $configurable;
- $this->resource = $resourceConnection;
- $this->linkedProductSelectBuilder = $linkedProductSelectBuilder;
- $this->collectionFactory = $collectionFactory;
- $this->requestSafety = $requestSafety;
}
/**
@@ -70,17 +47,7 @@ public function __construct(
public function getProducts(ProductInterface $product)
{
if (!isset($this->products[$product->getId()])) {
- if ($this->requestSafety->isSafeMethod()) {
- $productIds = $this->resource->getConnection()->fetchCol(
- '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($product->getId())) . ')'
- );
-
- $this->products[$product->getId()] = $this->collectionFactory->create()
- ->addAttributeToSelect(['price', 'special_price'])
- ->addIdFilter($productIds);
- } else {
- $this->products[$product->getId()] = $this->configurable->getUsedProducts($product);
- }
+ $this->products[$product->getId()] = $this->configurable->getUsedProducts($product);
}
return $this->products[$product->getId()];
}
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php
index c7ac9446f42e3..c0f2c218cc77c 100644
--- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php
@@ -10,6 +10,7 @@
/**
* Provide configurable sub-products for price calculation
+ * @api
*/
interface ConfigurableOptionsProviderInterface
{
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php
index 75d08b5aa419b..68e82ed76a23f 100644
--- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php
@@ -6,7 +6,6 @@
namespace Magento\ConfigurableProduct\Pricing\Price;
-use Magento\Catalog\Model\Product;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Pricing\PriceCurrencyInterface;
@@ -29,23 +28,27 @@ class ConfigurablePriceResolver implements PriceResolverInterface
protected $configurable;
/**
- * @var ConfigurableOptionsProviderInterface
+ * @var LowestPriceOptionsProviderInterface
*/
- private $configurableOptionsProvider;
+ private $lowestPriceOptionsProvider;
/**
* @param PriceResolverInterface $priceResolver
* @param Configurable $configurable
* @param PriceCurrencyInterface $priceCurrency
+ * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider
*/
public function __construct(
PriceResolverInterface $priceResolver,
Configurable $configurable,
- PriceCurrencyInterface $priceCurrency
+ PriceCurrencyInterface $priceCurrency,
+ LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null
) {
$this->priceResolver = $priceResolver;
$this->configurable = $configurable;
$this->priceCurrency = $priceCurrency;
+ $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?:
+ ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class);
}
/**
@@ -57,7 +60,7 @@ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $produ
{
$price = null;
- foreach ($this->getConfigurableOptionsProvider()->getProducts($product) as $subProduct) {
+ foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) {
$productPrice = $this->priceResolver->resolvePrice($subProduct);
$price = $price ? min($price, $productPrice) : $productPrice;
}
@@ -69,17 +72,4 @@ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $produ
return (float)$price;
}
-
- /**
- * @return \Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface
- * @deprecated
- */
- private function getConfigurableOptionsProvider()
- {
- if (null === $this->configurableOptionsProvider) {
- $this->configurableOptionsProvider = ObjectManager::getInstance()
- ->get(ConfigurableOptionsProviderInterface::class);
- }
- return $this->configurableOptionsProvider;
- }
}
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php
index 14b788258ab75..7a710e2caacc9 100644
--- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php
@@ -44,22 +44,31 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu
*/
private $configurableOptionsProvider;
+ /**
+ * @var LowestPriceOptionsProviderInterface
+ */
+ private $lowestPriceOptionsProvider;
+
/**
* @param \Magento\Framework\Pricing\SaleableInterface $saleableItem
* @param float $quantity
* @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
* @param PriceResolverInterface $priceResolver
+ * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider
*/
public function __construct(
\Magento\Framework\Pricing\SaleableInterface $saleableItem,
$quantity,
\Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator,
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
- PriceResolverInterface $priceResolver
+ PriceResolverInterface $priceResolver,
+ LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null
) {
parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency);
$this->priceResolver = $priceResolver;
+ $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?:
+ ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class);
}
/**
@@ -88,7 +97,6 @@ public function getAmount()
public function getMaxRegularAmount()
{
if (null === $this->maxRegularAmount) {
- $this->maxRegularAmount = $this->doGetMaxRegularAmount();
$this->maxRegularAmount = $this->doGetMaxRegularAmount() ?: false;
}
return $this->maxRegularAmount;
@@ -96,7 +104,7 @@ public function getMaxRegularAmount()
}
/**
- * Get max regular amount. Template method
+ * Get max regular amount
*
* @return \Magento\Framework\Pricing\Amount\AmountInterface
*/
@@ -124,14 +132,14 @@ public function getMinRegularAmount()
}
/**
- * Get min regular amount. Template method
+ * Get min regular amount
*
* @return \Magento\Framework\Pricing\Amount\AmountInterface
*/
protected function doGetMinRegularAmount()
{
$minAmount = null;
- foreach ($this->getUsedProducts() as $product) {
+ foreach ($this->lowestPriceOptionsProvider->getProducts($this->product) as $product) {
$childPriceAmount = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getAmount();
if (!$minAmount || ($childPriceAmount->getValue() < $minAmount->getValue())) {
$minAmount = $childPriceAmount;
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php
new file mode 100644
index 0000000000000..1b758866100eb
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php
@@ -0,0 +1,63 @@
+resource = $resourceConnection;
+ $this->linkedProductSelectBuilder = $linkedProductSelectBuilder;
+ $this->collectionFactory = $collectionFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProducts(ProductInterface $product)
+ {
+ $productIds = $this->resource->getConnection()->fetchCol(
+ '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($product->getId())) . ')'
+ );
+
+ $lowestPriceChildProducts = $this->collectionFactory->create()
+ ->addAttributeToSelect(['price', 'special_price'])
+ ->addIdFilter($productIds)
+ ->getItems();
+ return $lowestPriceChildProducts;
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php
new file mode 100644
index 0000000000000..c0989a929be55
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php
@@ -0,0 +1,21 @@
+configurableOptionsProvider = $configurableOptionsProvider;
parent::__construct($context, $saleableItem, $price, $rendererPool, $data);
+ $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?:
+ ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class);
}
/**
@@ -48,7 +54,7 @@ public function __construct(
public function hasSpecialPrice()
{
$product = $this->getSaleableItem();
- foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) {
+ foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) {
$regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue();
$finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue();
if ($finalPrice < $regularPrice) {
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php
index 0e2cfc0630226..8db61bb5e0a43 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php
@@ -5,13 +5,15 @@
*/
namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Price;
-use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface;
+use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase
{
- /** @var ConfigurableOptionsProviderInterface */
- private $cofigurableOptionProvider;
+ /**
+ * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $lowestPriceOptionsProvider;
/**
* @var \Magento\ConfigurableProduct\Pricing\Price\ConfigurablePriceResolver
@@ -19,12 +21,12 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase
protected $resolver;
/**
- * @var PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Model\Product\Type\Configurable
+ * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Model\Product\Type\Configurable
*/
protected $configurable;
/**
- * @var PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface
+ * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface
*/
protected $priceResolver;
@@ -36,8 +38,7 @@ protected function setUp()
$className = \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface::class;
$this->priceResolver = $this->getMockForAbstractClass($className, [], '', false, true, true, ['resolvePrice']);
- $this->cofigurableOptionProvider = $this->getMockBuilder(ConfigurableOptionsProviderInterface::class)
- ->disableOriginalConstructor()->getMock();
+ $this->lowestPriceOptionsProvider = $this->getMock(LowestPriceOptionsProviderInterface::class);
$objectManager = new ObjectManager($this);
$this->resolver = $objectManager->getObject(
@@ -45,7 +46,7 @@ protected function setUp()
[
'priceResolver' => $this->priceResolver,
'configurable' => $this->configurable,
- 'configurableOptionsProvider' => $this->cofigurableOptionProvider,
+ 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider,
]
);
}
@@ -63,7 +64,7 @@ public function testResolvePriceWithNoPrices()
$product->expects($this->once())->method('getSku')->willReturn('Kiwi');
- $this->cofigurableOptionProvider->expects($this->once())->method('getProducts')->willReturn([]);
+ $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([]);
$this->resolver->resolvePrice($product);
}
@@ -83,8 +84,11 @@ public function testResolvePrice($expectedValue)
$product->expects($this->never())->method('getSku');
- $this->cofigurableOptionProvider->expects($this->once())->method('getProducts')->willReturn([$product]);
- $this->priceResolver->expects($this->atLeastOnce())->method('resolvePrice')->willReturn($price);
+ $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([$product]);
+ $this->priceResolver->expects($this->once())
+ ->method('resolvePrice')
+ ->with($product)
+ ->willReturn($price);
$this->assertEquals($expectedValue, $this->resolver->resolvePrice($product));
}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php
index 4dbcfed531525..b102e1d81f48e 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php
@@ -7,8 +7,9 @@
use Magento\Catalog\Pricing\Price\FinalPrice;
use Magento\Catalog\Pricing\Price\RegularPrice;
-use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface;
+use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface;
use Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase
{
@@ -33,9 +34,9 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase
private $rendererPool;
/**
- * @var ConfigurableOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- private $configurableOptionsProvider;
+ private $lowestPriceOptionsProvider;
/**
* @var FinalPriceBox
@@ -59,15 +60,18 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $this->configurableOptionsProvider = $this->getMockBuilder(ConfigurableOptionsProviderInterface::class)
+ $this->lowestPriceOptionsProvider = $this->getMockBuilder(LowestPriceOptionsProviderInterface::class)
->getMockForAbstractClass();
- $this->model = new FinalPriceBox(
- $this->context,
- $this->saleableItem,
- $this->price,
- $this->rendererPool,
- $this->configurableOptionsProvider
+ $this->model = (new ObjectManager($this))->getObject(
+ FinalPriceBox::class,
+ [
+ 'context' => $this->context,
+ 'saleableItem' => $this->saleableItem,
+ 'price' => $this->price,
+ 'rendererPool' => $this->rendererPool,
+ 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider,
+ ]
);
}
@@ -115,7 +119,7 @@ public function testHasSpecialPrice(
->method('getPriceInfo')
->willReturn($priceInfoMock);
- $this->configurableOptionsProvider->expects($this->once())
+ $this->lowestPriceOptionsProvider->expects($this->once())
->method('getProducts')
->with($this->saleableItem)
->willReturn([$productMock]);
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index 89b271de838f9..b4d63d9d0245f 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -13,6 +13,7 @@
+
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php
new file mode 100644
index 0000000000000..0dc5c3ef620c3
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php
@@ -0,0 +1,43 @@
+get(ProductRepositoryInterface::class);
+ /** @var ProductInterface $product */
+ $product = $productRepository->get('simple', false, null, true);
+
+ /** @var ProductExtensionInterface $ea */
+ $ea = $product->getExtensionAttributes();
+ $ea->getStockItem()->setQty(555);
+ $productRepository->save($product);
+
+ $product = $productRepository->get('simple', false, null, true);
+ $this->assertEquals(555, $product->getExtensionAttributes()->getStockItem()->getQty());
+
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $stockItem->setQty(200);
+ /** @var StockItemRepositoryInterface $stockItemRepository */
+ $stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class);
+ $stockItemRepository->save($stockItem);
+ $this->assertEquals(200, $product->getExtensionAttributes()->getStockItem()->getQty());
+
+ $product = $productRepository->get('simple', false, null, true);
+ $this->assertEquals(200, $product->getExtensionAttributes()->getStockItem()->getQty());
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php
new file mode 100644
index 0000000000000..50787c7962412
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php
@@ -0,0 +1,98 @@
+storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class);
+ $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
+ $this->lowestPriceOptionsProvider = Bootstrap::getObjectManager()->get(
+ LowestPriceOptionsProviderInterface::class
+ );
+ }
+
+ /**
+ * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+ */
+ public function testGetProductsIfOneOfChildIsDisabled()
+ {
+ $configurableProduct = $this->productRepository->getById(1, false, null, true);
+ $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct);
+ $this->assertCount(1, $lowestPriceChildrenProducts);
+ $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts);
+ $this->assertEquals(10, $lowestPriceChildrenProduct->getPrice());
+
+ // load full aggregation root
+ $lowestPriceChildProduct = $this->productRepository->get(
+ $lowestPriceChildrenProduct->getSku(),
+ false,
+ null,
+ true
+ );
+ $lowestPriceChildProduct->setStatus(Status::STATUS_DISABLED);
+ // update in global scope
+ $currentStoreId = $this->storeManager->getStore()->getId();
+ $this->storeManager->setCurrentStore(Store::ADMIN_CODE);
+ $this->productRepository->save($lowestPriceChildProduct);
+ $this->storeManager->setCurrentStore($currentStoreId);
+
+ $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct);
+ $this->assertCount(1, $lowestPriceChildrenProducts);
+ $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts);
+ $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice());
+ }
+
+ /**
+ * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+ */
+ public function testGetProductsIfOneOfChildIsOutOfStock()
+ {
+ $configurableProduct = $this->productRepository->getById(1, false, null, true);
+ $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct);
+ $this->assertCount(1, $lowestPriceChildrenProducts);
+ $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts);
+ $this->assertEquals(10, $lowestPriceChildrenProduct->getPrice());
+
+ // load full aggregation root
+ $lowestPriceChildProduct = $this->productRepository->get(
+ $lowestPriceChildrenProduct->getSku(),
+ false,
+ null,
+ true
+ );
+ $stockItem = $lowestPriceChildProduct->getExtensionAttributes()->getStockItem();
+ $stockItem->setIsInStock(0);
+ $this->productRepository->save($lowestPriceChildProduct);
+
+ $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct);
+ $this->assertCount(1, $lowestPriceChildrenProducts);
+ $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts);
+ $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice());
+ }
+}