diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index 6df07e7b3a9fa..bfbdd9e4688d9 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -2509,13 +2509,15 @@ public function setTypeId($typeId)
/**
* {@inheritdoc}
*
- * @return \Magento\Catalog\Api\Data\ProductExtensionInterface|null
+ * @return \Magento\Catalog\Api\Data\ProductExtensionInterface
*/
public function getExtensionAttributes()
{
$extensionAttributes = $this->_getExtensionAttributes();
- if (!$extensionAttributes) {
- return $this->extensionAttributesFactory->create(\Magento\Catalog\Api\Data\ProductInterface::class);
+ if (null === $extensionAttributes) {
+ /** @var \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes */
+ $extensionAttributes = $this->extensionAttributesFactory->create(ProductInterface::class);
+ $this->setExtensionAttributes($extensionAttributes);
}
return $extensionAttributes;
}
@@ -2639,4 +2641,68 @@ public function setAssociatedProductIds(array $productIds)
$this->getExtensionAttributes()->setConfigurableProductLinks($productIds);
return $this;
}
+
+ /**
+ * Get quantity and stock status data
+ *
+ * @return array|null
+ *
+ * @deprecated as Product model shouldn't be responsible for stock status
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
+ public function getQuantityAndStockStatus()
+ {
+ return $this->getData('quantity_and_stock_status');
+ }
+
+ /**
+ * Set quantity and stock status data
+ *
+ * @param array $quantityAndStockStatusData
+ * @return $this
+ *
+ * @deprecated as Product model shouldn't be responsible for stock status
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
+ public function setQuantityAndStockStatus($quantityAndStockStatusData)
+ {
+ $this->setData('quantity_and_stock_status', $quantityAndStockStatusData);
+ return $this;
+ }
+
+ /**
+ * Get stock data
+ *
+ * @return array|null
+ *
+ * @deprecated as Product model shouldn't be responsible for stock status
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
+ public function getStockData()
+ {
+ return $this->getData('stock_data');
+ }
+
+ /**
+ * Set stock data
+ *
+ * @param array $stockData
+ * @return $this
+ *
+ * @deprecated as Product model shouldn't be responsible for stock status
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
+ public function setStockData($stockData)
+ {
+ $this->setData('stock_data', $stockData);
+ return $this;
+ }
}
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 4df4691117201..eba000efa0969 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php
@@ -3,13 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Catalog\Model\Product\Attribute\Backend;
use Magento\Catalog\Model\Product;
/**
* Quantity and Stock Status attribute processing
+ *
+ * @deprecated as this attribute should be removed
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
*/
class Stock extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
{
@@ -47,25 +51,6 @@ public function afterLoad($object)
return parent::afterLoad($object);
}
- /**
- * Prepare inventory data from custom attribute
- *
- * @param Product $object
- * @return void
- */
- public function beforeSave($object)
- {
- $stockData = $object->getData($this->getAttribute()->getAttributeCode());
- if (isset($stockData['qty']) && $stockData['qty'] === '') {
- $stockData['qty'] = null;
- }
- if ($object->getStockData() !== null && $stockData !== null) {
- $object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData));
- }
- $object->unsetData($this->getAttribute()->getAttributeCode());
- parent::beforeSave($object);
- }
-
/**
* Validate
*
diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php
index 176a15c895261..efe20b4003d02 100644
--- a/app/code/Magento/Catalog/Model/Product/Copier.php
+++ b/app/code/Magento/Catalog/Model/Product/Copier.php
@@ -59,7 +59,9 @@ public function copy(\Magento\Catalog\Model\Product $product)
/** @var \Magento\Catalog\Model\Product $duplicate */
$duplicate = $this->productFactory->create();
- $duplicate->setData($product->getData());
+ $productData = $product->getData();
+ $productData = $this->removeStockItem($productData);
+ $duplicate->setData($productData);
$duplicate->setOptions([]);
$duplicate->setIsDuplicate(true);
$duplicate->setOriginalLinkId($product->getData($metadata->getLinkField()));
@@ -116,4 +118,21 @@ private function getMetadataPool()
}
return $this->metadataPool;
}
+
+ /**
+ * Remove stock item
+ *
+ * @param array $productData
+ * @return array
+ */
+ private function removeStockItem(array $productData)
+ {
+ if (isset($productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY])) {
+ $extensionAttributes = $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY];
+ if (null !== $extensionAttributes->getStockItem()) {
+ $extensionAttributes->setData('stock_item', null);
+ }
+ }
+ return $productData;
+ }
}
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 cc406d2def352..4f481caabf1df 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
@@ -74,68 +74,4 @@ public function testAfterLoad()
$this->assertEquals(1, $data[self::ATTRIBUTE_NAME]['is_in_stock']);
$this->assertEquals(5, $data[self::ATTRIBUTE_NAME]['qty']);
}
-
- public function testBeforeSave()
- {
- $object = new \Magento\Framework\DataObject(
- [
- self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 5],
- 'stock_data' => ['is_in_stock' => 2, 'qty' => 2],
- ]
- );
- $stockData = $object->getStockData();
- $this->assertEquals(2, $stockData['is_in_stock']);
- $this->assertEquals(2, $stockData['qty']);
- $this->assertNotEmpty($object->getData(self::ATTRIBUTE_NAME));
-
- $this->model->beforeSave($object);
-
- $stockData = $object->getStockData();
- $this->assertEquals(1, $stockData['is_in_stock']);
- $this->assertEquals(5, $stockData['qty']);
- $this->assertNull($object->getData(self::ATTRIBUTE_NAME));
- }
-
- public function testBeforeSaveQtyIsEmpty()
- {
- $object = new \Magento\Framework\DataObject(
- [
- self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => ''],
- 'stock_data' => ['is_in_stock' => 2, 'qty' => ''],
- ]
- );
-
- $this->model->beforeSave($object);
-
- $stockData = $object->getStockData();
- $this->assertNull($stockData['qty']);
- }
-
- public function testBeforeSaveQtyIsZero()
- {
- $object = new \Magento\Framework\DataObject(
- [
- self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0],
- 'stock_data' => ['is_in_stock' => 2, 'qty' => 0],
- ]
- );
-
- $this->model->beforeSave($object);
-
- $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/Product/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
index 8978af9edc409..bdedb6c72aadf 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
@@ -5,8 +5,12 @@
*/
namespace Magento\Catalog\Test\Unit\Model\Product;
+use Magento\Catalog\Api\Data\ProductInterface;
use \Magento\Catalog\Model\Product\Copier;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class CopierTest extends \PHPUnit_Framework_TestCase
{
/**
@@ -80,10 +84,28 @@ protected function setUp()
public function testCopy()
{
+ $stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
+ ->getMock();
+ $extensionAttributes = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class)
+ ->setMethods(['getStockItem', 'setData'])
+ ->getMock();
+ $extensionAttributes
+ ->expects($this->once())
+ ->method('getStockItem')
+ ->willReturn($stockItem);
+ $extensionAttributes
+ ->expects($this->once())
+ ->method('setData')
+ ->with('stock_item', null);
+
+ $productData = [
+ 'product data' => ['product data'],
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributes,
+ ];
$this->productMock->expects($this->atLeastOnce())->method('getWebsiteIds');
$this->productMock->expects($this->atLeastOnce())->method('getCategoryIds');
$this->productMock->expects($this->any())->method('getData')->willReturnMap([
- ['', null, 'product data'],
+ ['', null, $productData],
['linkField', null, '1'],
]);
@@ -135,7 +157,7 @@ public function testCopy()
)->with(
\Magento\Store\Model\Store::DEFAULT_STORE_ID
);
- $duplicateMock->expects($this->once())->method('setData')->with('product data');
+ $duplicateMock->expects($this->once())->method('setData')->with($productData);
$this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock);
$duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1');
$duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
index adb0d037067dc..2377b2313b9a7 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
@@ -195,6 +195,11 @@ class ProductTest extends \PHPUnit_Framework_TestCase
*/
private $collectionFactoryMock;
+ /**
+ * @var ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $extensionAttributes;
+
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
@@ -392,8 +397,16 @@ protected function setUp()
->setMethods(['create'])
->getMock();
$this->mediaConfig = $this->getMock(\Magento\Catalog\Model\Product\Media\Config::class, [], [], '', false);
- $this->objectManagerHelper = new ObjectManagerHelper($this);
+ $this->extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class)
+ ->setMethods(['getStockItem'])
+ ->getMock();
+ $this->extensionAttributesFactory
+ ->expects($this->any())
+ ->method('create')
+ ->willReturn($this->extensionAttributes);
+
+ $this->objectManagerHelper = new ObjectManagerHelper($this);
$this->model = $this->objectManagerHelper->getObject(
\Magento\Catalog\Model\Product::class,
[
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php
index b823ddb68e911..8967147968f15 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php
@@ -41,7 +41,6 @@ public function __construct(
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->request = $request;
$this->collection = $collectionFactory->create();
- $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0));
}
/**
@@ -49,6 +48,9 @@ public function __construct(
*/
public function getData()
{
+ $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0));
+ $this->collection->getSelect()->setPart('order', []);
+
$items = [];
foreach ($this->getCollection()->getItems() as $attribute) {
$items[] = $attribute->toArray();
diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php
index e22be4d004aad..64609da69d48b 100644
--- a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php
+++ b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php
@@ -4,31 +4,28 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\CatalogInventory\Model\Plugin;
+/**
+ * @deprecated Stock Item as a part of ExtensionAttributes is deprecated
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
class AfterProductLoad
{
/**
* @var \Magento\CatalogInventory\Api\StockRegistryInterface
*/
- protected $stockRegistry;
-
- /**
- * @var \Magento\Catalog\Api\Data\ProductExtensionFactory
- */
- protected $productExtensionFactory;
+ private $stockRegistry;
/**
* @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
- * @param \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory
*/
public function __construct(
- \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
- \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory
+ \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
) {
$this->stockRegistry = $stockRegistry;
- $this->productExtensionFactory = $productExtensionFactory;
}
/**
@@ -40,10 +37,6 @@ public function __construct(
public function afterLoad(\Magento\Catalog\Model\Product $product)
{
$productExtension = $product->getExtensionAttributes();
- if ($productExtension === null) {
- $productExtension = $this->productExtensionFactory->create();
- }
- // stockItem := \Magento\CatalogInventory\Api\Data\StockItemInterface
$productExtension->setStockItem($this->stockRegistry->getStockItem($product->getId()));
$product->setExtensionAttributes($productExtension);
return $product;
diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php
index 15ef1d8a85eb4..a48be126d8eee 100644
--- a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php
+++ b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php
@@ -4,61 +4,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\CatalogInventory\Model\Plugin;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\CatalogInventory\Api\Data\StockItemInterface;
-use Magento\CatalogInventory\Api\StockConfigurationInterface;
-use Magento\CatalogInventory\Api\StockRegistryInterface;
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Exception\CouldNotSaveException;
/**
- * Plugin for Magento\Catalog\Api\ProductRepositoryInterface
+ * Plugin is needed for backward compatibility
*
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @deprecated Stock data should be processed using the module API
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
*/
class AroundProductRepositorySave
{
/**
- * @var StockRegistryInterface
- */
- protected $stockRegistry;
-
- /**
- * @var StoreManagerInterface
- * @deprecated
- */
- protected $storeManager;
-
- /**
- * @var StockConfigurationInterface
- */
- private $stockConfiguration;
-
- /**
- * @param StockRegistryInterface $stockRegistry
- * @param StoreManagerInterface $storeManager
- * @param StockConfigurationInterface $stockConfiguration
- */
- public function __construct(
- StockRegistryInterface $stockRegistry,
- StoreManagerInterface $storeManager,
- StockConfigurationInterface $stockConfiguration
- ) {
- $this->stockRegistry = $stockRegistry;
- $this->storeManager = $storeManager;
- $this->stockConfiguration = $stockConfiguration;
- }
-
- /**
- * Save stock item information from received product
- *
- * Pay attention that in this code we mostly work with original product object to process stock item data,
- * not with received result (saved product) because it is already contains new empty stock item object.
+ * Plugin is needed for backward compatibility
*
* @param ProductRepositoryInterface $subject
* @param ProductInterface $result
@@ -75,93 +38,7 @@ public function afterSave(
ProductInterface $product,
$saveOptions = false
) {
- /* @var StockItemInterface $stockItem */
- $stockItem = $this->getStockItemToBeUpdated($product);
- if (null === $stockItem) {
- return $result;
- }
-
- // set fields that the customer should not care about
- $stockItem->setProductId($product->getId());
- $stockItem->setWebsiteId($this->stockConfiguration->getDefaultScopeId());
-
- $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem);
-
// since we just saved a portion of the product, force a reload of it before returning it
- return $subject->get($product->getSku(), false, $product->getStoreId(), true);
- }
-
- /**
- * Return the stock item that needs to be updated.
- * If the stock item does not need to be updated, return null.
- *
- * @param ProductInterface $product
- * @return StockItemInterface|null
- * @throws LocalizedException
- */
- protected function getStockItemToBeUpdated($product)
- {
- // from the API, all the data we care about will exist as extension attributes of the original product
- $stockItem = $this->getStockItemFromProduct($product);
-
- if ($stockItem === null) {
- $stockItem = $this->stockRegistry->getStockItem($product->getId());
- if ($stockItem->getItemId() !== null) {
- // we already have stock item info, so we return null since nothing more needs to be updated
- $stockItem = null;
- }
- }
-
- return $stockItem;
- }
-
- /**
- * @param ProductInterface $product
- * @return StockItemInterface
- * @throws LocalizedException
- */
- private function getStockItemFromProduct(ProductInterface $product)
- {
- $stockItem = null;
- $extendedAttributes = $product->getExtensionAttributes();
- if ($extendedAttributes !== null) {
- $stockItem = $extendedAttributes->getStockItem();
- if ($stockItem) {
- $this->validateStockItem($product, $stockItem);
- }
- }
-
- return $stockItem;
- }
-
- /**
- * @param ProductInterface $product
- * @param StockItemInterface $stockItem
- * @throws LocalizedException
- * @return void
- */
- private function validateStockItem(ProductInterface $product, StockItemInterface $stockItem)
- {
- $defaultScopeId = $this->stockConfiguration->getDefaultScopeId();
- $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId();
- $stockId = $stockItem->getStockId();
- if ($stockId !== null && $stockId != $defaultStockId) {
- throw new LocalizedException(
- __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId)
- );
- }
- $stockItemId = $stockItem->getItemId();
- if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) {
- throw new LocalizedException(
- __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId)
- );
- }
-
- $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId();
- if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) {
- throw new LocalizedException(
- __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId)
- );
- }
+ return $subject->get($product->getSku(), false, $product->getStoreId());
}
}
diff --git a/app/code/Magento/CatalogInventory/Model/StockItemValidator.php b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php
new file mode 100644
index 0000000000000..2cc4832159d55
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php
@@ -0,0 +1,74 @@
+stockConfiguration = $stockConfiguration;
+ $this->stockRegistry = $stockRegistry;
+ }
+
+ /**
+ * Validate Stock item
+ *
+ * @param ProductInterface $product
+ * @param StockItemInterface $stockItem
+ * @throws LocalizedException
+ * @return void
+ */
+ public function validate(ProductInterface $product, StockItemInterface $stockItem)
+ {
+ $defaultScopeId = $this->stockConfiguration->getDefaultScopeId();
+ $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId();
+ $stockId = $stockItem->getStockId();
+ if ($stockId !== null && $stockId != $defaultStockId) {
+ throw new LocalizedException(
+ __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId)
+ );
+ }
+
+ $stockItemId = $stockItem->getItemId();
+ if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) {
+ throw new LocalizedException(
+ __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId)
+ );
+ }
+
+ $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId();
+ if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) {
+ throw new LocalizedException(
+ __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId)
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php
new file mode 100644
index 0000000000000..4d4628e25efff
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php
@@ -0,0 +1,132 @@
+stockRegistry = $stockRegistry;
+ }
+
+ /**
+ * Process stock item data
+ *
+ * @param EventObserver $observer
+ * @return void
+ */
+ public function execute(EventObserver $observer)
+ {
+ $product = $observer->getEvent()->getProduct();
+ $this->processStockData($product);
+ }
+
+ /**
+ * Process stock item data
+ *
+ * Synchronize stock data from different sources (stock_data, quantity_and_stock_status, StockItem) and set it to
+ * stock_data key
+ *
+ * @param Product $product
+ * @return void
+ */
+ private function processStockData(Product $product)
+ {
+ /** @var Item $stockItem */
+ $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId());
+
+ $quantityAndStockStatus = $product->getData('quantity_and_stock_status');
+ if (is_array($quantityAndStockStatus)) {
+ $quantityAndStockStatus = $this->prepareQuantityAndStockStatus($stockItem, $quantityAndStockStatus);
+
+ if ($quantityAndStockStatus) {
+ $this->setStockDataToProduct($product, $stockItem, $quantityAndStockStatus);
+ }
+ }
+ $product->unsetData('quantity_and_stock_status');
+ }
+
+ /**
+ * Prepare quantity_and_stock_status data
+ *
+ * Remove not changed values from quantity_and_stock_status data
+ * Set null value for qty if passed empty
+ *
+ * @param StockItemInterface $stockItem
+ * @param array $quantityAndStockStatus
+ * @return array
+ */
+ private function prepareQuantityAndStockStatus(StockItemInterface $stockItem, array $quantityAndStockStatus)
+ {
+ $stockItemId = $stockItem->getItemId();
+
+ if (null !== $stockItemId) {
+ if (isset($quantityAndStockStatus['is_in_stock'])
+ && $stockItem->getIsInStock() == $quantityAndStockStatus['is_in_stock']
+ ) {
+ unset($quantityAndStockStatus['is_in_stock']);
+ }
+ if (isset($quantityAndStockStatus['qty'])
+ && $stockItem->getQty() == $quantityAndStockStatus['qty']
+ ) {
+ unset($quantityAndStockStatus['qty']);
+ }
+ }
+
+ if (array_key_exists('qty', $quantityAndStockStatus) && $quantityAndStockStatus['qty'] === '') {
+ $quantityAndStockStatus['qty'] = null;
+ }
+ return $quantityAndStockStatus;
+ }
+
+ /**
+ * Set stock data to product
+ *
+ * First of all we take stock_data data, replace it from quantity_and_stock_status data (if was changed) and finally
+ * replace it with data from Stock Item object (only if Stock Item was changed)
+ *
+ * @param Product $product
+ * @param Item $stockItem
+ * @param array $quantityAndStockStatus
+ * @return void
+ */
+ private function setStockDataToProduct(Product $product, Item $stockItem, array $quantityAndStockStatus)
+ {
+ $stockData = array_replace((array)$product->getData('stock_data'), $quantityAndStockStatus);
+ if ($stockItem->hasDataChanges()) {
+ $stockData = array_replace($stockData, $stockItem->getData());
+ }
+ $product->setData('stock_data', $stockData);
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php
index 57c3e7053cd12..5f1c4f4350b12 100644
--- a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php
+++ b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php
@@ -3,43 +3,46 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\CatalogInventory\Observer;
+use Magento\Catalog\Model\Product;
+use Magento\CatalogInventory\Model\Stock\Item;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Event\ObserverInterface;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
-use Magento\CatalogInventory\Api\StockIndexInterface;
-use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
use Magento\CatalogInventory\Api\StockRegistryInterface;
+use Magento\CatalogInventory\Model\StockItemValidator;
use Magento\Framework\Event\Observer as EventObserver;
+/**
+ * Saves stock data from a product to the Stock Item
+ *
+ * @deprecated Stock data should be processed using the module API
+ * @see StockItemInterface when you want to change the stock data
+ * @see StockStatusInterface when you want to read the stock data for representation layer (storefront)
+ * @see StockItemRepositoryInterface::save as extension point for customization of saving process
+ */
class SaveInventoryDataObserver implements ObserverInterface
{
- /**
- * @var StockIndexInterface
- * @deprecated
- */
- protected $stockIndex;
-
/**
* @var StockConfigurationInterface
*/
- protected $stockConfiguration;
+ private $stockConfiguration;
/**
* @var StockRegistryInterface
*/
- protected $stockRegistry;
+ private $stockRegistry;
/**
- * @var StockItemRepositoryInterface
+ * @var StockItemValidator
*/
- protected $stockItemRepository;
+ private $stockItemValidator;
/**
* @var array
*/
- protected $paramListToCheck = [
+ private $paramListToCheck = [
'use_config_min_qty' => [
'item' => 'stock_data/min_qty',
'config' => 'stock_data/use_config_min_qty',
@@ -71,73 +74,86 @@ class SaveInventoryDataObserver implements ObserverInterface
];
/**
- * @param StockIndexInterface $stockIndex
* @param StockConfigurationInterface $stockConfiguration
* @param StockRegistryInterface $stockRegistry
- * @param StockItemRepositoryInterface $stockItemRepository
+ * @param StockItemValidator $stockItemValidator
*/
public function __construct(
- StockIndexInterface $stockIndex,
StockConfigurationInterface $stockConfiguration,
StockRegistryInterface $stockRegistry,
- StockItemRepositoryInterface $stockItemRepository
+ StockItemValidator $stockItemValidator = null
) {
- $this->stockIndex = $stockIndex;
$this->stockConfiguration = $stockConfiguration;
$this->stockRegistry = $stockRegistry;
- $this->stockItemRepository = $stockItemRepository;
+ $this->stockItemValidator = $stockItemValidator ?: ObjectManager::getInstance()->get(StockItemValidator::class);
}
/**
- * Saving product inventory data. Product qty calculated dynamically.
+ * Saving product inventory data
+ *
+ * Takes data from the stock_data property of a product and sets it to Stock Item.
+ * Validates and saves Stock Item object.
*
* @param EventObserver $observer
- * @return $this
+ * @return void
*/
public function execute(EventObserver $observer)
{
$product = $observer->getEvent()->getProduct();
- if ($product->getStockData() === null) {
- return $this;
+ $stockItem = $this->getStockItemToBeUpdated($product);
+
+ if ($product->getStockData() !== null) {
+ $stockData = $this->getStockData($product);
+ $stockItem->addData($stockData);
}
+ $this->stockItemValidator->validate($product, $stockItem);
+ $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem);
+ }
- $this->saveStockItemData($product);
- return $this;
+ /**
+ * Return the stock item that needs to be updated
+ *
+ * @param Product $product
+ * @return Item
+ */
+ private function getStockItemToBeUpdated(Product $product)
+ {
+ $extendedAttributes = $product->getExtensionAttributes();
+ $stockItem = $extendedAttributes->getStockItem();
+
+ if ($stockItem === null) {
+ $stockItem = $this->stockRegistry->getStockItem($product->getId());
+ }
+ return $stockItem;
}
/**
- * Prepare stock item data for save
+ * Get stock data
*
- * @param \Magento\Catalog\Model\Product $product
- * @return $this
+ * @param Product $product
+ * @return array
*/
- protected function saveStockItemData($product)
+ private function getStockData(Product $product)
{
- $stockItemData = $product->getStockData();
- $stockItemData['product_id'] = $product->getId();
+ $stockData = $product->getStockData();
+ $stockData['product_id'] = $product->getId();
- if (!isset($stockItemData['website_id'])) {
- $stockItemData['website_id'] = $this->stockConfiguration->getDefaultScopeId();
+ if (!isset($stockData['website_id'])) {
+ $stockData['website_id'] = $this->stockConfiguration->getDefaultScopeId();
}
- $stockItemData['stock_id'] = $this->stockRegistry->getStock($stockItemData['website_id'])->getStockId();
+ $stockData['stock_id'] = $this->stockRegistry->getStock($stockData['website_id'])->getStockId();
foreach ($this->paramListToCheck as $dataKey => $configPath) {
if (null !== $product->getData($configPath['item']) && null === $product->getData($configPath['config'])) {
- $stockItemData[$dataKey] = false;
+ $stockData[$dataKey] = false;
}
}
$originalQty = $product->getData('stock_data/original_inventory_qty');
if (strlen($originalQty) > 0) {
- $stockItemData['qty_correction'] = (isset($stockItemData['qty']) ? $stockItemData['qty'] : 0)
+ $stockData['qty_correction'] = (isset($stockData['qty']) ? $stockData['qty'] : 0)
- $originalQty;
}
-
- // todo resolve issue with builder and identity field name
- $stockItem = $this->stockRegistry->getStockItem($stockItemData['product_id'], $stockItemData['website_id']);
-
- $stockItem->addData($stockItemData);
- $this->stockItemRepository->save($stockItem);
- return $this;
+ return $stockData;
}
}
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php
index 375ed5937c272..408802d80bd57 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php
@@ -27,7 +27,7 @@ class AfterProductLoadTest extends \PHPUnit_Framework_TestCase
protected $productExtensionFactoryMock;
/**
- * @var \Magento\Catalog\Api\Data\ProductExtension|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Catalog\Api\Data\ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $productExtensionMock;
@@ -53,7 +53,7 @@ protected function setUp()
->with($productId)
->willReturn($stockItemMock);
- $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class)
+ $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtensionInterface::class)
->setMethods(['setStockItem'])
->getMock();
$this->productExtensionMock->expects($this->once())
@@ -75,23 +75,6 @@ protected function setUp()
public function testAfterLoad()
{
- // test when extension attributes are not (yet) present in the product
- $this->productMock->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn(null);
- $this->productExtensionFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($this->productExtensionMock);
-
- $this->assertEquals(
- $this->productMock,
- $this->plugin->afterLoad($this->productMock)
- );
- }
-
- public function testAfterLoadWithExistingExtensionAttributes()
- {
- // test when extension attributes already exist
$this->productMock->expects($this->once())
->method('getExtensionAttributes')
->willReturn($this->productExtensionMock);
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php
deleted file mode 100644
index 86a4ba3e25fe0..0000000000000
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php
+++ /dev/null
@@ -1,350 +0,0 @@
-stockRegistry = $this->getMockBuilder(StockRegistryInterface::class)
- ->setMethods(['getStockItem', 'updateStockItemBySku'])
- ->getMockForAbstractClass();
- $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
- ->getMockForAbstractClass();
- $this->stockConfiguration = $this->getMockBuilder(StockConfigurationInterface::class)
- ->setMethods(['getDefaultScopeId'])
- ->getMockForAbstractClass();
-
- $this->plugin = new AroundProductRepositorySave(
- $this->stockRegistry,
- $this->storeManager,
- $this->stockConfiguration
- );
-
- $this->savedProduct = $this->getMockBuilder(ProductInterface::class)
- ->setMethods(['getExtensionAttributes', 'getStoreId'])
- ->getMockForAbstractClass();
-
- $this->productRepository = $this->getMockBuilder(ProductRepositoryInterface::class)
- ->setMethods(['get'])
- ->getMockForAbstractClass();
- $this->product = $this->getMockBuilder(ProductInterface::class)
- ->setMethods(['getExtensionAttributes', 'getStoreId'])
- ->getMockForAbstractClass();
- $this->productExtension = $this->getMockForAbstractClass(
- ProductExtensionInterface::class,
- [],
- '',
- false,
- false,
- true,
- ['getStockItem']
- );
- $this->stockItem = $this->getMockBuilder(StockItemInterface::class)
- ->setMethods(['setWebsiteId', 'getWebsiteId', 'getStockId'])
- ->getMockForAbstractClass();
- $this->defaultStock = $this->getMockBuilder(StockInterface::class)
- ->setMethods(['getStockId'])
- ->getMockForAbstractClass();
- }
-
- public function testAfterSaveWhenProductHasNoStockItemNeedingToBeUpdated()
- {
- // pretend we have no extension attributes at all
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn(null);
- $this->productExtension->expects($this->never())->method('getStockItem');
-
- // pretend that the product already has existing stock item information
- $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem);
- $this->stockItem->expects($this->once())->method('getItemId')->willReturn(1);
- $this->stockItem->expects($this->never())->method('setProductId');
- $this->stockItem->expects($this->never())->method('setWebsiteId');
-
- // expect that there are no changes to the existing stock item information
- $result = $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product);
- $this->assertEquals(
- $this->savedProduct,
- $result
- );
- }
-
- public function testAfterSaveWhenProductHasNoPersistentStockItemInfo()
- {
- // pretend we do have extension attributes, but none for 'stock_item'
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn($this->productExtension);
- $this->productExtension->expects($this->once())
- ->method('getStockItem')
- ->willReturn(null);
-
- $this->stockConfiguration->expects($this->once())->method('getDefaultScopeId')->willReturn(1);
- $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem);
- $this->stockRegistry->expects($this->once())->method('updateStockItemBySku');
-
- $this->stockItem->expects($this->once())->method('getItemId')->willReturn(null);
- $this->stockItem->expects($this->once())->method('setProductId');
- $this->stockItem->expects($this->once())->method('setWebsiteId');
- $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn(20);
-
- $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
- ->disableOriginalConstructor()->getMock();
- $this->productRepository->expects($this->once())->method('get')->willReturn($newProductMock);
-
- $this->assertEquals(
- $newProductMock,
- $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product)
- );
- }
-
- public function testAfterSave()
- {
- $productId = 5494;
- $storeId = 2;
- $sku = 'my product that needs saving';
- $defaultScopeId = 100;
- $this->stockConfiguration->expects($this->exactly(2))
- ->method('getDefaultScopeId')
- ->willReturn($defaultScopeId);
- $this->stockRegistry->expects($this->once())
- ->method('getStock')
- ->with($defaultScopeId)
- ->willReturn($this->defaultStock);
-
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn($this->productExtension);
- $this->productExtension->expects($this->once())
- ->method('getStockItem')
- ->willReturn($this->stockItem);
-
- $storedStockItem = $this->getMockBuilder(StockItemInterface::class)
- ->setMethods(['getItemId'])
- ->getMockForAbstractClass();
- $storedStockItem->expects($this->once())
- ->method('getItemId')
- ->willReturn(500);
- $this->stockRegistry->expects($this->once())
- ->method('getStockItem')
- ->willReturn($storedStockItem);
-
- $this->product->expects(($this->exactly(2)))->method('getId')->willReturn($productId);
- $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn($storeId);
- $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($sku);
-
- $this->stockItem->expects($this->once())->method('setProductId')->with($productId);
- $this->stockItem->expects($this->once())->method('setWebsiteId')->with($defaultScopeId);
-
- $this->stockRegistry->expects($this->once())
- ->method('updateStockItemBySku')
- ->with($sku, $this->stockItem);
-
- $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
- ->disableOriginalConstructor()->getMock();
- $this->productRepository->expects($this->once())
- ->method('get')
- ->with($sku, false, $storeId, true)
- ->willReturn($newProductMock);
-
- $this->assertEquals(
- $newProductMock,
- $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product)
- );
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\LocalizedException
- * @expectedExceptionMessage Invalid stock id: 100500. Only default stock with id 50 allowed
- */
- public function testAfterSaveWithInvalidStockId()
- {
- $stockId = 100500;
- $defaultScopeId = 100;
- $defaultStockId = 50;
-
- $this->stockItem->expects($this->once())
- ->method('getStockId')
- ->willReturn($stockId);
- $this->stockRegistry->expects($this->once())
- ->method('getStock')
- ->with($defaultScopeId)
- ->willReturn($this->defaultStock);
- $this->stockConfiguration->expects($this->once())
- ->method('getDefaultScopeId')
- ->willReturn($defaultScopeId);
- $this->defaultStock->expects($this->once())
- ->method('getStockId')
- ->willReturn($defaultStockId);
-
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn($this->productExtension);
- $this->productExtension->expects($this->once())
- ->method('getStockItem')
- ->willReturn($this->stockItem);
-
- $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product);
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\LocalizedException
- * @expectedExceptionMessage Invalid stock item id: 0. Should be null or numeric value greater than 0
- */
- public function testAfterSaveWithInvalidStockItemId()
- {
- $stockId = 80;
- $stockItemId = 0;
- $defaultScopeId = 100;
- $defaultStockId = 80;
-
- $this->stockItem->expects($this->once())
- ->method('getStockId')
- ->willReturn($stockId);
- $this->stockRegistry->expects($this->once())
- ->method('getStock')
- ->with($defaultScopeId)
- ->willReturn($this->defaultStock);
- $this->stockConfiguration->expects($this->once())
- ->method('getDefaultScopeId')
- ->willReturn($defaultScopeId);
- $this->defaultStock->expects($this->once())
- ->method('getStockId')
- ->willReturn($defaultStockId);
-
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn($this->productExtension);
- $this->productExtension->expects($this->once())
- ->method('getStockItem')
- ->willReturn($this->stockItem);
-
- $this->stockItem->expects($this->once())
- ->method('getItemId')
- ->willReturn($stockItemId);
-
- $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product);
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\LocalizedException
- * @expectedExceptionMessage Invalid stock item id: 35. Assigned stock item id is 40
- */
- public function testAfterSaveWithNotAssignedStockItemId()
- {
- $stockId = 80;
- $stockItemId = 35;
- $defaultScopeId = 100;
- $defaultStockId = 80;
- $storedStockitemId = 40;
-
- $this->stockItem->expects($this->once())
- ->method('getStockId')
- ->willReturn($stockId);
- $this->stockRegistry->expects($this->once())
- ->method('getStock')
- ->with($defaultScopeId)
- ->willReturn($this->defaultStock);
- $this->stockConfiguration->expects($this->once())
- ->method('getDefaultScopeId')
- ->willReturn($defaultScopeId);
- $this->defaultStock->expects($this->once())
- ->method('getStockId')
- ->willReturn($defaultStockId);
-
- $this->product->expects($this->once())
- ->method('getExtensionAttributes')
- ->willReturn($this->productExtension);
- $this->productExtension->expects($this->once())
- ->method('getStockItem')
- ->willReturn($this->stockItem);
-
- $this->stockItem->expects($this->once())
- ->method('getItemId')
- ->willReturn($stockItemId);
-
- $storedStockItem = $this->getMockBuilder(StockItemInterface::class)
- ->setMethods(['getItemId'])
- ->getMockForAbstractClass();
- $storedStockItem->expects($this->once())
- ->method('getItemId')
- ->willReturn($storedStockitemId);
- $this->stockRegistry->expects($this->once())
- ->method('getStockItem')
- ->willReturn($storedStockItem);
-
- $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product);
- }
-}
diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml
index 97af0f934ad9f..3b5f2483ec57e 100644
--- a/app/code/Magento/CatalogInventory/etc/events.xml
+++ b/app/code/Magento/CatalogInventory/etc/events.xml
@@ -33,6 +33,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml
index 2e30788604908..11bebb96ce313 100644
--- a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml
+++ b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml
@@ -7,6 +7,12 @@
-->
+
diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php
index b8770482f0324..0bd28f0f78d96 100644
--- a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php
+++ b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php
@@ -8,6 +8,7 @@
use Magento\Config\App\Config\Type\System;
use Magento\Config\Model\PreparedValueFactory;
use Magento\Framework\App\Config\ConfigPathResolver;
+use Magento\Framework\App\Config\Value;
use Magento\Framework\App\DeploymentConfig;
use Magento\Framework\Config\File\ConfigFilePool;
use Magento\Framework\Exception\CouldNotSaveException;
@@ -79,19 +80,27 @@ public function process($path, $value, $scope, $scopeCode)
$configPath = $this->configPathResolver->resolve($path, $scope, $scopeCode, System::CONFIG_TYPE);
$backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode);
- /**
- * Temporary solution until Magento introduce unified interface
- * for storing system configuration into database and configuration files.
- */
- $backendModel->validateBeforeSave();
- $backendModel->beforeSave();
+ if ($backendModel instanceof Value) {
+ /**
+ * Temporary solution until Magento introduce unified interface
+ * for storing system configuration into database and configuration files.
+ */
+ $backendModel->validateBeforeSave();
+ $backendModel->beforeSave();
- $this->deploymentConfigWriter->saveConfig(
- [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $backendModel->getValue())],
- false
- );
+ $value = $backendModel->getValue();
- $backendModel->afterSave();
+ $backendModel->afterSave();
+
+ /**
+ * Because FS does not support transactions,
+ * we'll write value just after all validations are triggered.
+ */
+ $this->deploymentConfigWriter->saveConfig(
+ [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $value)],
+ false
+ );
+ }
} catch (\Exception $exception) {
throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception);
}
diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php
index e37214411ff51..62028eb789230 100644
--- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php
+++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php
@@ -206,10 +206,10 @@ public function testCustomException()
->willReturn($this->valueMock);
$this->arrayManagerMock->expects($this->never())
->method('set');
- $this->valueMock->expects($this->never())
+ $this->valueMock->expects($this->once())
->method('getValue');
$this->valueMock->expects($this->once())
- ->method('validateBeforeSave')
+ ->method('afterSave')
->willThrowException(new \Exception('Invalid values'));
$this->deploymentConfigWriterMock->expects($this->never())
->method('saveConfig');
diff --git a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php
index ad7809ecee2ee..4c931a156a5f7 100644
--- a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php
+++ b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php
@@ -7,9 +7,12 @@
use Magento\Framework\App\Config\ConfigSourceInterface;
use Magento\Framework\App\DeploymentConfig;
+use Magento\Store\Model\Group;
use Magento\Store\Model\ResourceModel\Website\CollectionFactory as WebsiteCollectionFactory;
use Magento\Store\Model\ResourceModel\Group\CollectionFactory as GroupCollectionFactory;
use Magento\Store\Model\ResourceModel\Store\CollectionFactory as StoreCollectionFactory;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\Website;
use Magento\Store\Model\WebsiteFactory;
use Magento\Store\Model\GroupFactory;
use Magento\Store\Model\StoreFactory;
@@ -100,13 +103,13 @@ public function get($path = '')
if ($this->canUseDatabase()) {
switch ($scopePool) {
case 'websites':
- $data = $this->getWebsitesData($scopeCode);
+ $data['websites'] = $this->getWebsitesData($scopeCode);
break;
case 'groups':
- $data = $this->getGroupsData($scopeCode);
+ $data['groups'] = $this->getGroupsData($scopeCode);
break;
case 'stores':
- $data = $this->getStoresData($scopeCode);
+ $data['stores'] = $this->getStoresData($scopeCode);
break;
default:
$data = [
@@ -127,14 +130,15 @@ public function get($path = '')
*/
private function getWebsitesData($code = null)
{
- if ($code) {
+ if ($code !== null) {
$website = $this->websiteFactory->create();
$website->load($code);
- $data = $website->getData();
+ $data[$code] = $website->getData();
} else {
$collection = $this->websiteCollectionFactory->create();
$collection->setLoadDefault(true);
$data = [];
+ /** @var Website $website */
foreach ($collection as $website) {
$data[$website->getCode()] = $website->getData();
}
@@ -148,14 +152,15 @@ private function getWebsitesData($code = null)
*/
private function getGroupsData($id = null)
{
- if ($id) {
+ if ($id !== null) {
$group = $this->groupFactory->create();
$group->load($id);
- $data = $group->getData();
+ $data[$id] = $group->getData();
} else {
$collection = $this->groupCollectionFactory->create();
$collection->setLoadDefault(true);
$data = [];
+ /** @var Group $group */
foreach ($collection as $group) {
$data[$group->getId()] = $group->getData();
}
@@ -169,14 +174,21 @@ private function getGroupsData($id = null)
*/
private function getStoresData($code = null)
{
- if ($code) {
+ if ($code !== null) {
$store = $this->storeFactory->create();
- $store->load($code, 'code');
- $data = $store->getData();
+
+ if (is_numeric($code)) {
+ $store->load($code);
+ } else {
+ $store->load($code, 'code');
+ }
+
+ $data[$code] = $store->getData();
} else {
$collection = $this->storeCollectionFactory->create();
$collection->setLoadDefault(true);
$data = [];
+ /** @var Store $store */
foreach ($collection as $store) {
$data[$store->getCode()] = $store->getData();
}
diff --git a/app/code/Magento/Store/App/Config/Type/Scopes.php b/app/code/Magento/Store/App/Config/Type/Scopes.php
index d9dc0278e8627..b1f266c8b0cfe 100644
--- a/app/code/Magento/Store/App/Config/Type/Scopes.php
+++ b/app/code/Magento/Store/App/Config/Type/Scopes.php
@@ -34,6 +34,7 @@ public function __construct(
ConfigSourceInterface $source
) {
$this->source = $source;
+ $this->data = new DataObject();
}
/**
@@ -41,8 +42,10 @@ public function __construct(
*/
public function get($path = '')
{
- if (!$this->data) {
- $this->data = new DataObject($this->source->get());
+ $patchChunks = explode("/", $path);
+
+ if (!$this->data->getData($path) || count($patchChunks) == 1) {
+ $this->data->addData($this->source->get($path));
}
return $this->data->getData($path);
@@ -55,6 +58,6 @@ public function get($path = '')
*/
public function clean()
{
- $this->data = null;
+ $this->data = new DataObject();
}
}
diff --git a/app/code/Magento/Store/Model/Config/Processor/Fallback.php b/app/code/Magento/Store/Model/Config/Processor/Fallback.php
index 2578ea79a41db..91926cd23f9cf 100644
--- a/app/code/Magento/Store/Model/Config/Processor/Fallback.php
+++ b/app/code/Magento/Store/Model/Config/Processor/Fallback.php
@@ -6,7 +6,16 @@
namespace Magento\Store\Model\Config\Processor;
use Magento\Framework\App\Config\Spi\PostProcessorInterface;
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Store\Api\Data\StoreInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
use Magento\Store\App\Config\Type\Scopes;
+use Magento\Store\Model\ResourceModel\Store;
+use Magento\Store\Model\ResourceModel\Store\AllStoresCollectionFactory;
+use Magento\Store\Model\ResourceModel\Website;
+use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollection;
+use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollectionFactory;
/**
* Fallback through different scopes and merge them
@@ -18,13 +27,57 @@ class Fallback implements PostProcessorInterface
*/
private $scopes;
+ /**
+ * @var ResourceConnection
+ */
+ private $resourceConnection;
+
+ /**
+ * @var array
+ */
+ private $storeData = [];
+
+ /**
+ * @var array
+ */
+ private $websiteData = [];
+
+ /**
+ * @var Store
+ */
+ private $storeResource;
+
+ /**
+ * @var Website
+ */
+ private $websiteResource;
+
+ /**
+ * @var DeploymentConfig
+ */
+ private $deploymentConfig;
+
/**
* Fallback constructor.
+ *
* @param Scopes $scopes
+ * @param ResourceConnection $resourceConnection
+ * @param Store $storeResource
+ * @param Website $websiteResource
+ * @param DeploymentConfig $deploymentConfig
*/
- public function __construct(Scopes $scopes)
- {
+ public function __construct(
+ Scopes $scopes,
+ ResourceConnection $resourceConnection,
+ Store $storeResource,
+ Website $websiteResource,
+ DeploymentConfig $deploymentConfig
+ ) {
$this->scopes = $scopes;
+ $this->resourceConnection = $resourceConnection;
+ $this->storeResource = $storeResource;
+ $this->websiteResource = $websiteResource;
+ $this->deploymentConfig = $deploymentConfig;
}
/**
@@ -32,6 +85,14 @@ public function __construct(Scopes $scopes)
*/
public function process(array $data)
{
+ if ($this->deploymentConfig->isDbAvailable()) {//read only from db
+ $this->storeData = $this->storeResource->readAllStores();
+ $this->websiteData = $this->websiteResource->readAllWebsites();
+ } else {
+ $this->storeData = $this->scopes->get('stores');
+ $this->websiteData = $this->scopes->get('websites');
+ }
+
$defaultConfig = isset($data['default']) ? $data['default'] : [];
$result = [
'default' => $defaultConfig,
@@ -55,12 +116,14 @@ public function process(array $data)
* @param array $websitesConfig
* @return array
*/
- private function prepareWebsitesConfig(array $defaultConfig, array $websitesConfig)
- {
+ private function prepareWebsitesConfig(
+ array $defaultConfig,
+ array $websitesConfig
+ ) {
$result = [];
- foreach ((array)$this->scopes->get('websites') as $websiteData) {
- $code = $websiteData['code'];
- $id = $websiteData['website_id'];
+ foreach ((array)$this->websiteData as $website) {
+ $code = $website['code'];
+ $id = $website['website_id'];
$websiteConfig = isset($websitesConfig[$code]) ? $websitesConfig[$code] : [];
$result[$code] = array_replace_recursive($defaultConfig, $websiteConfig);
$result[$id] = $result[$code];
@@ -76,15 +139,19 @@ private function prepareWebsitesConfig(array $defaultConfig, array $websitesConf
* @param array $storesConfig
* @return array
*/
- private function prepareStoresConfig(array $defaultConfig, array $websitesConfig, array $storesConfig)
- {
+ private function prepareStoresConfig(
+ array $defaultConfig,
+ array $websitesConfig,
+ array $storesConfig
+ ) {
$result = [];
- foreach ((array)$this->scopes->get('stores') as $storeData) {
- $code = $storeData['code'];
- $id = $storeData['store_id'];
+
+ foreach ((array)$this->storeData as $store) {
+ $code = $store['code'];
+ $id = $store['store_id'];
$websiteConfig = [];
- if (isset($storeData['website_id'])) {
- $websiteConfig = $this->getWebsiteConfig($websitesConfig, $storeData['website_id']);
+ if (isset($store['website_id'])) {
+ $websiteConfig = $this->getWebsiteConfig($websitesConfig, $store['website_id']);
}
$storeConfig = isset($storesConfig[$code]) ? $storesConfig[$code] : [];
$result[$code] = array_replace_recursive($defaultConfig, $websiteConfig, $storeConfig);
@@ -94,17 +161,17 @@ private function prepareStoresConfig(array $defaultConfig, array $websitesConfig
}
/**
- * Retrieve Website Config
+ * Find information about website by its ID.
*
- * @param array $websites
+ * @param array $websites Has next format: (website_code => [website_data])
* @param int $id
* @return array
*/
private function getWebsiteConfig(array $websites, $id)
{
- foreach ($this->scopes->get('websites') as $websiteData) {
- if ($websiteData['website_id'] == $id) {
- $code = $websiteData['code'];
+ foreach ((array)$this->websiteData as $website) {
+ if ($website['website_id'] == $id) {
+ $code = $website['code'];
return isset($websites[$code]) ? $websites[$code] : [];
}
}
diff --git a/app/code/Magento/Store/Model/GroupRepository.php b/app/code/Magento/Store/Model/GroupRepository.php
index 80cc6f12c4de8..da1b82c3bf688 100644
--- a/app/code/Magento/Store/Model/GroupRepository.php
+++ b/app/code/Magento/Store/Model/GroupRepository.php
@@ -62,18 +62,8 @@ public function get($id)
return $this->entities[$id];
}
- $groupData = [];
- $groups = $this->getAppConfig()->get('scopes', 'groups', []);
- if ($groups) {
- foreach ($groups as $data) {
- if (isset($data['group_id']) && $data['group_id'] == $id) {
- $groupData = $data;
- break;
- }
- }
- }
$group = $this->groupFactory->create([
- 'data' => $groupData
+ 'data' => $this->getAppConfig()->get('scopes', "groups/$id", [])
]);
if (null === $group->getId()) {
diff --git a/app/code/Magento/Store/Model/ResourceModel/Store.php b/app/code/Magento/Store/Model/ResourceModel/Store.php
index 5e16fbd68cedc..4446472a71176 100644
--- a/app/code/Magento/Store/Model/ResourceModel/Store.php
+++ b/app/code/Magento/Store/Model/ResourceModel/Store.php
@@ -157,6 +157,20 @@ protected function _changeGroup(\Magento\Framework\Model\AbstractModel $model)
return $this;
}
+ /**
+ * Read information about all stores
+ *
+ * @return array
+ */
+ public function readAllStores()
+ {
+ $select = $this->getConnection()
+ ->select()
+ ->from($this->getTable('store'));
+
+ return $this->getConnection()->fetchAll($select);
+ }
+
/**
* Retrieve select object for load object data
*
diff --git a/app/code/Magento/Store/Model/ResourceModel/Website.php b/app/code/Magento/Store/Model/ResourceModel/Website.php
index e8aa73728dc27..9cd52ca2eb98b 100644
--- a/app/code/Magento/Store/Model/ResourceModel/Website.php
+++ b/app/code/Magento/Store/Model/ResourceModel/Website.php
@@ -36,6 +36,28 @@ protected function _initUniqueFields()
return $this;
}
+ /**
+ * Read all information about websites.
+ *
+ * Convert information to next format:
+ * [website_code => [website_data (website_id, code, name, etc...)]]
+ *
+ * @return array
+ */
+ public function readAllWebsites()
+ {
+ $websites = [];
+ $select = $this->getConnection()
+ ->select()
+ ->from($this->getTable('store_website'));
+
+ foreach($this->getConnection()->fetchAll($select) as $websiteData) {
+ $websites[$websiteData['code']] = $websiteData;
+ }
+
+ return $websites;
+ }
+
/**
* Validate website code before object save
*
diff --git a/app/code/Magento/Store/Model/StoreRepository.php b/app/code/Magento/Store/Model/StoreRepository.php
index 2ed8860615f8b..282dfb5b67081 100644
--- a/app/code/Magento/Store/Model/StoreRepository.php
+++ b/app/code/Magento/Store/Model/StoreRepository.php
@@ -100,14 +100,7 @@ public function getById($id)
return $this->entitiesById[$id];
}
- $storeData = [];
- $stores = $this->getAppConfig()->get('scopes', "stores", []);
- foreach ($stores as $data) {
- if (isset($data['store_id']) && $data['store_id'] == $id) {
- $storeData = $data;
- break;
- }
- }
+ $storeData = $this->getAppConfig()->get('scopes', "stores/$id", []);
$store = $this->storeFactory->create([
'data' => $storeData
]);
diff --git a/app/code/Magento/Store/Model/WebsiteRepository.php b/app/code/Magento/Store/Model/WebsiteRepository.php
index 14bbe20b30d6a..3b73ba4019a01 100644
--- a/app/code/Magento/Store/Model/WebsiteRepository.php
+++ b/app/code/Magento/Store/Model/WebsiteRepository.php
@@ -92,14 +92,8 @@ public function getById($id)
if (isset($this->entitiesById[$id])) {
return $this->entitiesById[$id];
}
- $websiteData = [];
- $websites = $this->getAppConfig()->get('scopes', 'websites', []);
- foreach ($websites as $data) {
- if (isset($data['website_id']) && $data['website_id'] == $id) {
- $websiteData = $data;
- break;
- }
- }
+
+ $websiteData = $this->getAppConfig()->get('scopes', "websites/$id", []);
$website = $this->factory->create([
'data' => $websiteData
]);
@@ -185,7 +179,7 @@ private function getAppConfig()
*/
private function initDefaultWebsite()
{
- $websites = (array)$this->getAppConfig()->get('scopes', 'websites', []);
+ $websites = (array) $this->getAppConfig()->get('scopes', 'websites', []);
foreach ($websites as $data) {
if (isset($data['is_default']) && $data['is_default'] == 1) {
if ($this->default) {
diff --git a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php
index 74d7a3e8b0eb5..1617161fff361 100644
--- a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php
+++ b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php
@@ -104,25 +104,22 @@ class RuntimeConfigSourceTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$this->data = [
- 'group' => [
- 'code' => 'myGroup',
- 'data' => [
+ 'groups' => [
+ '1' => [
'name' => 'My Group',
- 'group_id' => $this->data['group']['code']
- ]
+ 'group_id' => 1
+ ],
],
- 'website' => [
- 'code' => 'myWebsite',
- 'data' => [
- 'name' => 'My Website',
- 'website_code' => $this->data['website']['code']
+ 'stores' => [
+ 'myStore' => [
+ 'name' => 'My Store',
+ 'code' => 'myStore'
]
],
- 'store' => [
- 'code' => 'myStore',
- 'data' => [
- 'name' => 'My Store',
- 'store_code' => $this->data['store']['code']
+ 'websites' => [
+ 'myWebsite' => [
+ 'name' => 'My Website',
+ 'code' => 'myWebsite'
]
],
];
@@ -209,26 +206,16 @@ private function getExpectedResult($path)
{
switch ($this->getScope($path)) {
case 'websites':
- $result = $this->data['website']['data'];
+ $result = ['websites' => $this->data['websites']];
break;
case 'groups':
- $result = $this->data['group']['data'];
+ $result = ['groups' => $this->data['groups']];
break;
case 'stores':
- $result = $this->data['store']['data'];
+ $result = ['stores' => $this->data['stores']];
break;
default:
- $result = [
- 'websites' => [
- $this->data['website']['code'] => $this->data['website']['data']
- ],
- 'groups' => [
- $this->data['group']['code'] => $this->data['group']['data']
- ],
- 'stores' => [
- $this->data['store']['code'] => $this->data['store']['data']
- ],
- ];
+ $result = $this->data;
break;
}
return $result;
@@ -244,7 +231,7 @@ private function prepareStores($path)
->willReturn($this->store);
$this->store->expects($this->once())
->method('load')
- ->with($this->data['store']['code'], 'code')
+ ->with('myStore', 'code')
->willReturnSelf();
} else {
$this->storeCollectionFactory->expects($this->once())
@@ -259,11 +246,11 @@ private function prepareStores($path)
->willReturn(new \ArrayIterator([$this->store]));
$this->store->expects($this->once())
->method('getCode')
- ->willReturn($this->data['store']['code']);
+ ->willReturn('myStore');
}
$this->store->expects($this->once())
->method('getData')
- ->willReturn($this->data['store']['data']);
+ ->willReturn($this->data['stores']['myStore']);
}
}
@@ -277,7 +264,7 @@ private function prepareGroups($path)
->willReturn($this->group);
$this->group->expects($this->once())
->method('load')
- ->with($this->data['group']['code'])
+ ->with($this->data['groups']['1']['group_id'])
->willReturnSelf();
} else {
$this->groupCollectionFactory->expects($this->once())
@@ -292,11 +279,11 @@ private function prepareGroups($path)
->willReturn(new \ArrayIterator([$this->group]));
$this->group->expects($this->once())
->method('getId')
- ->willReturn($this->data['group']['code']);
+ ->willReturn($this->data['groups']['1']['group_id']);
}
$this->group->expects($this->once())
->method('getData')
- ->willReturn($this->data['group']['data']);
+ ->willReturn($this->data['groups']['1']);
}
}
@@ -310,7 +297,7 @@ private function prepareWebsites($path)
->willReturn($this->website);
$this->website->expects($this->once())
->method('load')
- ->with($this->data['website']['code'])
+ ->with($this->data['websites']['myWebsite']['code'])
->willReturnSelf();
} else {
$this->websiteCollectionFactory->expects($this->once())
@@ -325,11 +312,11 @@ private function prepareWebsites($path)
->willReturn(new \ArrayIterator([$this->website]));
$this->website->expects($this->once())
->method('getCode')
- ->willReturn($this->data['website']['code']);
+ ->willReturn('myWebsite');
}
$this->website->expects($this->once())
->method('getData')
- ->willReturn($this->data['website']['data']);
+ ->willReturn($this->data['websites']['myWebsite']);
}
}
@@ -350,7 +337,7 @@ public function getDataProvider()
{
return [
['websites/myWebsite'],
- ['groups/myGroup'],
+ ['groups/1'],
['stores/myStore'],
['default']
];
diff --git a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php
index a041fffbbb110..fc826daf19a61 100644
--- a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php
+++ b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php
@@ -22,6 +22,11 @@ class Save extends Action
*/
protected $fileProcessor;
+ /**
+ * Authorization level
+ */
+ const ADMIN_RESOURCE = 'Magento_Theme::theme';
+
/**
* @param Context $context
* @param FileProcessor $fileProcessor
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php
index d7f7fca3d674c..f13f88148e314 100644
--- a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php
@@ -19,6 +19,7 @@
use Magento\Catalog\Model\Product\Visibility;
use Magento\Framework\ObjectManagerInterface;
use Magento\Store\Api\StoreRepositoryInterface;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\TestFramework\Entity;
use Magento\TestFramework\Helper\Bootstrap;
@@ -112,6 +113,10 @@ public function testMultipleStores()
self::assertNotEquals($store->getId(), $bundle->getStoreId());
+ /** @var StoreManagerInterface $storeManager */
+ $storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $storeManager->setCurrentStore($store->getId());
+
$bundle->setStoreId($store->getId())
->setCopyFromView(true);
$updatedBundle = $productRepository->save($bundle);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php
new file mode 100644
index 0000000000000..e4821afcd24d2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php
@@ -0,0 +1,53 @@
+request = $objectManager->get(\Magento\Framework\App\RequestInterface::class);
+
+ /** Default Attribute Set Id is equal 4 */
+ $this->request->setParams(['template_id' => 4]);
+
+ $this->dataProvider = $objectManager->create(
+ \Magento\Catalog\Ui\DataProvider\Product\Attributes\Listing::class,
+ [
+ 'name' => 'product_attributes_grid_data_source',
+ 'primaryFieldName' => 'attribute_id',
+ 'requestFieldName' => 'id',
+ 'request' => $this->request
+ ]
+ );
+ }
+
+ public function testGetDataSortedAsc()
+ {
+ $this->dataProvider->addOrder('attribute_code', 'asc');
+ $data = $this->dataProvider->getData();
+ $this->assertEquals(2, $data['totalRecords']);
+ $this->assertEquals('color', $data['items'][0]['attribute_code']);
+ $this->assertEquals('manufacturer', $data['items'][1]['attribute_code']);
+ }
+
+ public function testGetDataSortedDesc()
+ {
+ $this->dataProvider->addOrder('attribute_code', 'desc');
+ $data = $this->dataProvider->getData();
+ $this->assertEquals(2, $data['totalRecords']);
+ $this->assertEquals('manufacturer', $data['items'][0]['attribute_code']);
+ $this->assertEquals('color', $data['items'][1]['attribute_code']);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php
new file mode 100644
index 0000000000000..521eff7c89ef8
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php
@@ -0,0 +1,74 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test stock item saving via stock item repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSave()
+ {
+ /** @var ProductInterface $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $this->dataObjectHelper->populateWithArray(
+ $stockItem,
+ $this->stockItemData,
+ StockItemInterface::class
+ );
+ $this->stockItemRepository->save($stockItem);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php
new file mode 100644
index 0000000000000..d11e2661c6088
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php
@@ -0,0 +1,101 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->get(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product
+ * model (deprecated)
+ */
+ public function testSaveBySetQuantityAndStockStatus()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setQuantityAndStockStatus($this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated)
+ * via product model (deprecated)
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setData('quantity_and_stock_status', $this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php
new file mode 100644
index 0000000000000..a840b48c18537
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php
@@ -0,0 +1,100 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->get(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setStockData' method (deprecated) via product model (deprecated)
+ */
+ public function testSaveBySetStockData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setStockData($this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated)
+ * via product model (deprecated)
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setData('stock_data', $this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php
new file mode 100644
index 0000000000000..412ddcfd17351
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php
@@ -0,0 +1,112 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->create(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item by product data via product model (deprecated)
+ */
+ public function testSave()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $productData = $this->productData;
+ $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData;
+ $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually created stock item (and set by extension attributes object) on product save via product
+ * model (deprecated)
+ */
+ public function testSaveManuallyCreatedStockItem()
+ {
+ /** @var StockItemInterface $stockItem */
+ $stockItem = $this->stockItemFactory->create();
+ $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class);
+
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->getExtensionAttributes()->setStockItem($stockItem);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php
new file mode 100644
index 0000000000000..d40a78d16d464
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php
@@ -0,0 +1,110 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ // prevent internal caching in property
+ $this->productRepository->cleanCache();
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->get(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product
+ * repository
+ */
+ public function testSaveBySetQuantityAndStockStatus()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setQuantityAndStockStatus($this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated)
+ * via product repository
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setData('quantity_and_stock_status', $this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php
new file mode 100644
index 0000000000000..271786918209d
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php
@@ -0,0 +1,109 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ // prevent internal caching in property
+ $this->productRepository->cleanCache();
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->get(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository
+ */
+ public function testSaveBySetStockData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setStockData($this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated)
+ * via product repository
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->setData('stock_data', $this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php
new file mode 100644
index 0000000000000..daf333a512f3f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php
@@ -0,0 +1,144 @@
+ Type::TYPE_SIMPLE,
+ 'website_ids' => [1],
+ ProductInterface::NAME => 'Simple',
+ ProductInterface::SKU => 'simple',
+ ProductInterface::PRICE => 100,
+ ProductInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var array
+ */
+ private $stockItemData = [
+ StockItemInterface::QTY => 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productFactory = $objectManager->get(ProductInterfaceFactory::class);
+ $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class);
+ $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ // prevent internal caching in property
+ $this->productRepository->cleanCache();
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+
+ /** @var CategorySetup $installer */
+ $installer = $objectManager->create(CategorySetup::class);
+ $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+ $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId;
+ }
+
+ /**
+ * Test saving of stock item by product data via product repository
+ */
+ public function testSave()
+ {
+ /** @var ProductInterface $product */
+ $product = $this->productFactory->create();
+ $productData = $this->productData;
+ $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData;
+ $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually created stock item (and set by extension attributes object) on product save via product
+ * repository
+ */
+ public function testSaveManuallyCreatedStockItem()
+ {
+ /** @var StockItemInterface $stockItem */
+ $stockItem = $this->stockItemFactory->create();
+ $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class);
+
+ /** @var ProductInterface $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product->getExtensionAttributes()->setStockItem($stockItem);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test automatically stock item creating on product save via product repository
+ */
+ public function testAutomaticallyStockItemCreating()
+ {
+ /** @var ProductInterface $product */
+ $product = $this->productFactory->create();
+ $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class);
+ $product = $this->productRepository->save($product);
+
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class);
+ $this->stockItemRepository->save($stockItem);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php
new file mode 100644
index 0000000000000..bc5c623e4999b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php
@@ -0,0 +1,73 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product
+ * model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetQuantityAndStockStatus()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setQuantityAndStockStatus($this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated)
+ * via product model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setData('quantity_and_stock_status', $this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php
new file mode 100644
index 0000000000000..c2468b690f95b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php
@@ -0,0 +1,73 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setStockData' method (deprecated) via product
+ * model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetStockData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setStockData($this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated)
+ * via product model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setData('stock_data', $this->stockItemData);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php
new file mode 100644
index 0000000000000..59c6d7a4554a7
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php
@@ -0,0 +1,113 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item by product data via product model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSave()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData;
+ $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually created stock item (and set by extension attributes object) on product save via
+ * product model (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveManuallyCreatedStockItem()
+ {
+ /** @var StockItemInterface $stockItem */
+ $stockItem = $this->stockItemFactory->create();
+ $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class);
+
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->getExtensionAttributes()->setStockItem($stockItem);
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually updated stock item (obtained from extension attributes object) on product save via
+ * product repository (deprecated)
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveManuallyUpdatedStockItem()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $this->dataObjectHelper->populateWithArray(
+ $stockItem,
+ $this->stockItemData,
+ StockItemInterface::class
+ );
+ $product->save();
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php
new file mode 100644
index 0000000000000..0bfe402505052
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php
@@ -0,0 +1,73 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product
+ * repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetQuantityAndStockStatus()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setQuantityAndStockStatus($this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated)
+ * via product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setData('quantity_and_stock_status', $this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php
new file mode 100644
index 0000000000000..8ff4b89d2480c
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php
@@ -0,0 +1,72 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetStockData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setStockData($this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated)
+ * via product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveBySetData()
+ {
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->setData('stock_data', $this->stockItemData);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php
new file mode 100644
index 0000000000000..8a3786f839902
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php
@@ -0,0 +1,120 @@
+ 555,
+ StockItemInterface::MANAGE_STOCK => true,
+ StockItemInterface::IS_IN_STOCK => false,
+ ];
+
+ public function setUp()
+ {
+ $objectManager = Bootstrap::getObjectManager();
+ $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class);
+ $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class);
+ $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class);
+ }
+
+ /**
+ * Test saving of stock item by product data via product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSave()
+ {
+ /** @var ProductInterface $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData;
+ $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually created stock item (and set by extension attributes object) on product save via
+ * product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveManuallyCreatedStockItem()
+ {
+ /** @var StockItemInterface $stockItem */
+ $stockItem = $this->stockItemFactory->create();
+ $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class);
+
+ /** @var Product $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $product->getExtensionAttributes()->setStockItem($stockItem);
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+
+ /**
+ * Test saving of manually updated stock item (obtained from extension attributes object) on product save via
+ * product repository
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSaveManuallyUpdatedStockItem()
+ {
+ /** @var ProductInterface $product */
+ $product = $this->productRepository->get('simple', false, null, true);
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $this->dataObjectHelper->populateWithArray(
+ $stockItem,
+ $this->stockItemData,
+ StockItemInterface::class
+ );
+ $this->productRepository->save($product);
+
+ $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php
new file mode 100644
index 0000000000000..189f8767bac60
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php
@@ -0,0 +1,131 @@
+hydrator = $hydrator;
+ $this->stockItemRepository = $stockItemRepository;
+ $this->stockItemCriteriaFactory = $stockItemCriteriaFactory;
+ $this->productRepository = $productRepository;
+ $this->productFactory = $productFactory;
+ }
+
+ /**
+ * @param string $sku
+ * @param array $expectedData
+ */
+ public function checkStockItemData($sku, array $expectedData)
+ {
+ $product = $this->productRepository->get($sku, false, null, true);
+ $this->doCheckStockItemData($product, $expectedData);
+
+ /** @var Product $product */
+ $productLoadedByModel = $this->productFactory->create();
+ $productLoadedByModel->load($product->getId());
+ $this->doCheckStockItemData($product, $expectedData);
+ }
+
+ /**
+ * @param Product $product
+ * @param array $expectedData
+ */
+ private function doCheckStockItemData(Product $product, array $expectedData)
+ {
+ $stockItem = $product->getExtensionAttributes()->getStockItem();
+ $stockItem = $this->stockItemRepository->get($stockItem->getItemId());
+
+ $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem));
+
+ $criteria = $this->stockItemCriteriaFactory->create();
+ $result = $this->stockItemRepository->getList($criteria);
+ $items = $result->getItems();
+ $stockItem = current($items);
+ $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem));
+
+ $expectedQuantityAndStockStatusData = array_intersect_key($expectedData, [
+ StockItemInterface::IS_IN_STOCK => 0,
+ StockItemInterface::QTY => 0,
+ ]);
+ \PHPUnit_Framework_Assert::assertNotNull($product->getQuantityAndStockStatus());
+ $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getQuantityAndStockStatus());
+
+ \PHPUnit_Framework_Assert::assertNotNull($product->getData('quantity_and_stock_status'));
+ $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getData('quantity_and_stock_status'));
+ }
+
+ /**
+ * @param array $expected
+ * @param array $actual
+ * @return void
+ */
+ private function assertArrayContains(array $expected, array $actual)
+ {
+ foreach ($expected as $key => $value) {
+ \PHPUnit_Framework_Assert::assertArrayHasKey(
+ $key,
+ $actual,
+ "Expected value for key '{$key}' is missed"
+ );
+ if (is_array($value)) {
+ $this->assertArrayContains($value, $actual[$key]);
+ } else {
+ \PHPUnit_Framework_Assert::assertEquals(
+ $value,
+ $actual[$key],
+ "Expected value for key '{$key}' doesn't match"
+ );
+ }
+ }
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php
new file mode 100644
index 0000000000000..5132a2f9456de
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php
@@ -0,0 +1,86 @@
+objectManager = $this->getObjectManager();
+
+ parent::setUp();
+ }
+
+ /**
+ * @magentoDbIsolation enabled
+ * @magentoDataFixture Magento/Review/_files/config.php
+ * @dataProvider getCorrectFlagDataProvider
+ */
+ public function testGetCorrectFlag(
+ $path,
+ $scope,
+ $scopeId,
+ $value,
+ $expectedResult
+ ) {
+ /** @var State $appState */
+ $appState = $this->objectManager->get(State::class);
+ $appState->setAreaCode(Area::AREA_FRONTEND);
+
+ /** @var Value $config */
+ $config = $this->objectManager->create(Value::class);
+ $config->setPath($path);
+ $config->setScope($scope);
+ $config->setScopeId($scopeId);
+ $config->setValue($value);
+ $config->save();
+ /** @var ReinitableConfig $reinitableConfig */
+ $reinitableConfig = $this->objectManager->create(ReinitableConfig::class);
+ $reinitableConfig->reinit();
+
+ /** @var \Magento\Review\Block\Form $form */
+ $form = $this->objectManager->create(\Magento\Review\Block\Form::class);
+ $result = $form->getAllowWriteReviewFlag();
+ $this->assertEquals($result, $expectedResult);
+ }
+
+ public function getCorrectFlagDataProvider()
+ {
+ return [
+ [
+ 'path' => 'catalog/review/allow_guest',
+ 'scope' => 'websites',
+ 'scopeId' => '1',
+ 'value' => 0,
+ 'expectedResult' => false,
+ ],
+ [
+ 'path' => 'catalog/review/allow_guest',
+ 'scope' => 'websites',
+ 'scopeId' => '1',
+ 'value' => 1,
+ 'expectedResult' => true
+ ]
+ ];
+ }
+
+ private function getObjectManager()
+ {
+ return \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/config.php b/dev/tests/integration/testsuite/Magento/Review/_files/config.php
new file mode 100644
index 0000000000000..13436974d6305
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Review/_files/config.php
@@ -0,0 +1,15 @@
+create(Value::class);
+$config->setPath('catalog/review/allow_guest');
+$config->setScope('default');
+$config->setScopeId(0);
+$config->setValue(1);
+$config->save();
diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php
index d295bb89579ec..4ff3dfde24177 100644
--- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php
+++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php
@@ -35,7 +35,6 @@ public function tearDown()
public function testExecuteConsoleOutput()
{
- $this->markTestSkipped('MAGETWO-64249: Unexpected test exit on Travis CI');
$this->tester->execute(
[
'directory' => BP . '/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/',
diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
index 9d1ad1095b11a..62b97e54bf0f0 100644
--- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
@@ -9,7 +9,7 @@
* Test class for \Magento\Sitemap\Model\ResourceModel\Catalog\Product.
* - test products collection generation for sitemap
*
- * @magentoDataFixtureBeforeTransaction Magento/CatalogSearch/_files/full_reindex.php
+ * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php
* @magentoDataFixture Magento/Sitemap/_files/sitemap_products.php
*/
class ProductTest extends \PHPUnit_Framework_TestCase
diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php
index 27a247107fc33..fb8d3e3f28c3c 100644
--- a/lib/internal/Magento/Framework/Console/Cli.php
+++ b/lib/internal/Magento/Framework/Console/Cli.php
@@ -11,7 +11,7 @@
use Magento\Framework\App\ProductMetadata;
use Magento\Framework\App\State;
use Magento\Framework\Composer\ComposerJsonFinder;
-use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Console\Exception\GenerationDirectoryAccessException;
use Magento\Framework\Filesystem\Driver\File;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Shell\ComplexParameter;
@@ -66,17 +66,27 @@ class Cli extends Console\Application
/**
* @param string $name the application name
* @param string $version the application version
+ * @SuppressWarnings(PHPMD.ExitExpression)
*/
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
- $configuration = require BP . '/setup/config/application.config.php';
- $bootstrapApplication = new Application();
- $application = $bootstrapApplication->bootstrap($configuration);
- $this->serviceManager = $application->getServiceManager();
+ try {
+ $configuration = require BP . '/setup/config/application.config.php';
+ $bootstrapApplication = new Application();
+ $application = $bootstrapApplication->bootstrap($configuration);
+ $this->serviceManager = $application->getServiceManager();
+
+ $this->assertCompilerPreparation();
+ $this->initObjectManager();
+ $this->assertGenerationPermissions();
+ } catch (\Exception $exception) {
+ $output = new \Symfony\Component\Console\Output\ConsoleOutput();
+ $output->writeln(
+ '' . $exception->getMessage() . ''
+ );
- $this->assertCompilerPreparation();
- $this->initObjectManager();
- $this->assertGenerationPermissions();
+ exit(static::RETURN_FAILURE);
+ }
if ($version == 'UNKNOWN') {
$directoryList = new DirectoryList(BP);
@@ -91,20 +101,13 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
/**
* {@inheritdoc}
*
- * @throws \Exception the exception in case of unexpected error
+ * @throws \Exception The exception in case of unexpected error
*/
public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
{
$exitCode = parent::doRun($input, $output);
if ($this->initException) {
- $output->writeln(
- "We're sorry, an error occurred. Try clearing the cache and code generation directories. "
- . "By default, they are: " . $this->getDefaultDirectoryPath(DirectoryList::CACHE) . ", "
- . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_METADATA) . ", "
- . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ", and var/page_cache."
- );
-
throw $this->initException;
}
@@ -154,24 +157,17 @@ protected function getApplicationCommands()
* Object Manager initialization.
*
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
*/
private function initObjectManager()
{
- try {
- $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER);
- $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null;
-
- $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager();
+ $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER);
+ $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null;
- /** @var ObjectManagerProvider $omProvider */
- $omProvider = $this->serviceManager->get(ObjectManagerProvider::class);
- $omProvider->setObjectManager($this->objectManager);
- } catch (FileSystemException $exception) {
- $this->writeGenerationDirectoryReadError();
+ $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager();
- exit(static::RETURN_FAILURE);
- }
+ /** @var ObjectManagerProvider $omProvider */
+ $omProvider = $this->serviceManager->get(ObjectManagerProvider::class);
+ $omProvider->setObjectManager($this->objectManager);
}
/**
@@ -182,7 +178,7 @@ private function initObjectManager()
* developer - application will be terminated
*
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
+ * @throws GenerationDirectoryAccessException If generation directory is read-only in developer mode
*/
private function assertGenerationPermissions()
{
@@ -197,9 +193,7 @@ private function assertGenerationPermissions()
if ($state->getMode() !== State::MODE_PRODUCTION
&& !$generationDirectoryAccess->check()
) {
- $this->writeGenerationDirectoryReadError();
-
- exit(static::RETURN_FAILURE);
+ throw new GenerationDirectoryAccessException();
}
}
@@ -207,7 +201,7 @@ private function assertGenerationPermissions()
* Checks whether compiler is being prepared.
*
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
+ * @throws GenerationDirectoryAccessException If generation directory is read-only
*/
private function assertCompilerPreparation()
{
@@ -222,33 +216,10 @@ private function assertCompilerPreparation()
new File()
);
- try {
- $compilerPreparation->handleCompilerEnvironment();
- } catch (FileSystemException $e) {
- $this->writeGenerationDirectoryReadError();
-
- exit(static::RETURN_FAILURE);
- }
+ $compilerPreparation->handleCompilerEnvironment();
}
}
- /**
- * Writes read error to console.
- *
- * @return void
- */
- private function writeGenerationDirectoryReadError()
- {
- $output = new \Symfony\Component\Console\Output\ConsoleOutput();
- $output->writeln(
- ''
- . 'Command line user does not have read and write permissions on '
- . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ' directory. '
- . 'Please address this issue before using Magento command line.'
- . ''
- );
- }
-
/**
* Retrieves vendor commands.
*
@@ -270,22 +241,4 @@ protected function getVendorCommands($objectManager)
return $commands;
}
-
- /**
- * Get default directory path by code
- *
- * @param string $code
- * @return string
- */
- private function getDefaultDirectoryPath($code)
- {
- $config = DirectoryList::getDefaultConfig();
- $result = '';
-
- if (isset($config[$code][DirectoryList::PATH])) {
- $result = $config[$code][DirectoryList::PATH];
- }
-
- return $result;
- }
}
diff --git a/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php
new file mode 100644
index 0000000000000..fc65cc0362e24
--- /dev/null
+++ b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php
@@ -0,0 +1,48 @@
+getDefaultDirectoryPath(DirectoryList::GENERATED) . ' directory. '
+ . 'Please address this issue before using Magento command line.'
+ );
+
+ parent::__construct($phrase, $cause, $code);
+ }
+
+ /**
+ * Get default directory path by code
+ *
+ * @param string $code
+ * @return string
+ */
+ private function getDefaultDirectoryPath($code)
+ {
+ $config = DirectoryList::getDefaultConfig();
+ $result = '';
+
+ if (isset($config[$code][DirectoryList::PATH])) {
+ $result = $config[$code][DirectoryList::PATH];
+ }
+
+ return $result;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
index 7349872ff4ac1..b8978c9ef7181 100644
--- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
+++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
@@ -7,9 +7,8 @@
use Magento\Framework\App\Bootstrap;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem\Directory\WriteFactory;
use Magento\Framework\Filesystem\DriverPool;
-use Magento\Framework\Filesystem\File\WriteFactory;
-use Magento\Framework\Filesystem\Directory\Write;
use Zend\ServiceManager\ServiceManager;
use Magento\Setup\Mvc\Bootstrap\InitParamListener;
@@ -33,7 +32,7 @@ public function __construct(
}
/**
- * Check generated/code read and write access
+ * Check write permissions to generation folders
*
* @return bool
*/
@@ -44,33 +43,32 @@ public function check()
? $initParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]
: [];
$directoryList = new DirectoryList(BP, $filesystemDirPaths);
- $generationDirectoryPath = $directoryList->getPath(DirectoryList::GENERATED_CODE);
$driverPool = new DriverPool();
$fileWriteFactory = new WriteFactory($driverPool);
- /** @var \Magento\Framework\Filesystem\DriverInterface $driver */
- $driver = $driverPool->getDriver(DriverPool::FILE);
- $directoryWrite = new Write($fileWriteFactory, $driver, $generationDirectoryPath);
- if ($directoryWrite->isExist()) {
- if ($directoryWrite->isDirectory()
- || $directoryWrite->isReadable()
- ) {
+
+ $generationDirs = [
+ DirectoryList::GENERATED,
+ DirectoryList::GENERATED_CODE,
+ DirectoryList::GENERATED_METADATA
+ ];
+
+ foreach ($generationDirs as $generationDirectory) {
+ $directoryPath = $directoryList->getPath($generationDirectory);
+ $directoryWrite = $fileWriteFactory->create($directoryPath);
+
+ if (!$directoryWrite->isExist()) {
try {
- $probeFilePath = $generationDirectoryPath . DIRECTORY_SEPARATOR . uniqid(mt_rand()).'tmp';
- $fileWriteFactory->create($probeFilePath, DriverPool::FILE, 'w');
- $driver->deleteFile($probeFilePath);
+ $directoryWrite->create();
} catch (\Exception $e) {
return false;
}
- } else {
- return false;
}
- } else {
- try {
- $directoryWrite->create();
- } catch (\Exception $e) {
+
+ if (!$directoryWrite->isWritable()) {
return false;
}
}
+
return true;
}
}
diff --git a/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php
new file mode 100644
index 0000000000000..fcc12c8a76bd3
--- /dev/null
+++ b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php
@@ -0,0 +1,21 @@
+assertContains(
+ 'Command line user does not have read and write permissions on generated directory.',
+ $exception->getMessage()
+ );
+ }
+}
diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php
index 6bdddfe0053a8..9ea938d51fb37 100644
--- a/setup/src/Magento/Setup/Console/CompilerPreparation.php
+++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php
@@ -7,6 +7,7 @@
use Magento\Framework\App\Bootstrap;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Console\Exception\GenerationDirectoryAccessException;
use Magento\Framework\Console\GenerationDirectoryAccess;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Filesystem\Driver\File;
@@ -59,31 +60,31 @@ public function __construct(
/**
* Determine whether a CLI command is for compilation, and if so, clear the directory.
*
- * @throws FileSystemException if generation directory is read-only
+ * @throws GenerationDirectoryAccessException If generation directory is read-only
* @return void
*/
public function handleCompilerEnvironment()
{
- $compilationCommands = [DiCompileCommand::NAME];
+ $compilationCommands = $this->getCompilerInvalidationCommands();
$cmdName = $this->input->getFirstArgument();
$isHelpOption = $this->input->hasParameterOption('--help') || $this->input->hasParameterOption('-h');
if (!in_array($cmdName, $compilationCommands) || $isHelpOption) {
return;
}
- $compileDirList = [];
+
+ if (!$this->getGenerationDirectoryAccess()->check()) {
+ throw new GenerationDirectoryAccessException();
+ }
+
$mageInitParams = $this->serviceManager->get(InitParamListener::BOOTSTRAP_PARAM);
$mageDirs = isset($mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS])
? $mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]
: [];
$directoryList = new DirectoryList(BP, $mageDirs);
- $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_CODE);
- $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_METADATA);
-
- if (!$this->getGenerationDirectoryAccess()->check()) {
- throw new FileSystemException(
- new Phrase('Generation directory can not be written.')
- );
- }
+ $compileDirList = [
+ $directoryList->getPath(DirectoryList::GENERATED_CODE),
+ $directoryList->getPath(DirectoryList::GENERATED_METADATA),
+ ];
foreach ($compileDirList as $compileDir) {
if ($this->filesystemDriver->isExists($compileDir)) {
@@ -92,6 +93,22 @@ public function handleCompilerEnvironment()
}
}
+ /**
+ * Retrieves command list with commands which invalidates compiler
+ *
+ * @return array
+ */
+ private function getCompilerInvalidationCommands()
+ {
+ return [
+ DiCompileCommand::NAME,
+ 'module:disable',
+ 'module:enable',
+ 'module:uninstall',
+ 'deploy:mode:set'
+ ];
+ }
+
/**
* Retrieves generation directory access checker.
*