diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php index 327729cf83695931a8c378dc91f41ab66a9e7d69..290770bd296c3bf9ef8854de86008674776ae2b7 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php @@ -73,9 +73,12 @@ class Sku extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { $attribute = $this->getAttribute(); $entity = $attribute->getEntity(); - $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); $attributeValue = $object->getData($attribute->getAttributeCode()); + $increment = null; while (!$entity->checkAttributeUniqueValue($attribute, $object)) { + if ($increment === null) { + $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); + } $sku = trim($attributeValue); if (strlen($sku . '-' . ++$increment) > self::SKU_MAX_LENGTH) { $sku = substr($sku, 0, -strlen($increment) - 1); diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php index f17760237034041764539e533d5f1440b8dad3e4..cbf0464ca366158d4d3e4d75f65e4435f0834502 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php @@ -58,6 +58,11 @@ class CreateHandler implements ExtensionInterface */ protected $fileStorageDb; + /** + * @var array + */ + private $mediaAttributeCodes; + /** * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository @@ -145,9 +150,11 @@ class CreateHandler implements ExtensionInterface } /* @var $mediaAttribute \Magento\Catalog\Api\Data\ProductAttributeInterface */ - foreach ($this->mediaConfig->getMediaAttributeCodes() as $mediaAttrCode) { + foreach ($this->getMediaAttributeCodes() as $mediaAttrCode) { $attrData = $product->getData($mediaAttrCode); - + if (empty($attrData) && empty($clearImages) && empty($newImages) && empty($existImages)) { + continue; + } if (in_array($attrData, $clearImages)) { $product->setData($mediaAttrCode, 'no_selection'); } @@ -394,4 +401,17 @@ class CreateHandler implements ExtensionInterface ); } } + + /** + * Get Media Attribute Codes cached value + * + * @return array + */ + private function getMediaAttributeCodes() + { + if ($this->mediaAttributeCodes === null) { + $this->mediaAttributeCodes = $this->mediaConfig->getMediaAttributeCodes(); + } + return $this->mediaAttributeCodes; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php index 3df4d9813a668b6fc81d7706412e1cf4e671c5e5..167dc2be15b292c4fabd9983911cd54cba89e62c 100644 --- a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php @@ -40,7 +40,6 @@ class SaveHandler Link $linkResource, ProductLinkRepositoryInterface $productLinkRepository ) { - $this->metadataPool = $metadataPool; $this->linkResource = $linkResource; $this->productLinkRepository = $productLinkRepository; @@ -54,12 +53,18 @@ class SaveHandler */ public function execute($entityType, $entity) { - /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ - foreach ($this->productLinkRepository->getList($entity) as $link) { - $this->productLinkRepository->delete($link); + $link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField()); + if ($this->linkResource->hasProductLinks($link)) { + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ + foreach ($this->productLinkRepository->getList($entity) as $link) { + $this->productLinkRepository->delete($link); + } } - foreach ($entity->getProductLinks() as $link) { - $this->productLinkRepository->save($link); + $productLinks = $entity->getProductLinks(); + if (count($productLinks) > 0) { + foreach ($entity->getProductLinks() as $link) { + $this->productLinkRepository->save($link); + } } return $entity; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 407c2027923de4c683272773d519cf1adc7b1917..e743c5d384bdd539a15c4f666b36ce0f43f7f4d0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -238,7 +238,9 @@ class Category extends AbstractResource if (!$object->getChildrenCount()) { $object->setChildrenCount(0); } - + $object->setAttributeSetId( + $object->getAttributeSetId() ?: $this->getEntityType()->getDefaultAttributeSetId() + ); if ($object->isObjectNew()) { if ($object->getPosition() === null) { $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index 99c4316d20740edc4d3d6415bbfb06a344e7e5c7..8e93868dc7e51916a1c44997ad98045edbe1bd4f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -351,14 +351,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute implements */ public function getApplyTo() { - if ($this->getData(self::APPLY_TO)) { - if (is_array($this->getData(self::APPLY_TO))) { - return $this->getData(self::APPLY_TO); - } - return explode(',', $this->getData(self::APPLY_TO)); - } else { - return []; + $applyTo = $this->_getData(self::APPLY_TO) ?: []; + if (!is_array($applyTo)) { + $applyTo = explode(',', $applyTo); } + return $applyTo; } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php index bbccabe5a61c969f74294398ad484e1b0d3c64f9..17bfa90e8842d4dc511d8d78f2dc23151a3934b3 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php @@ -96,6 +96,30 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $connection->fetchOne($select, $bind); } + /** + * Check if product has links. + * + * @param int $parentId ID of product + * @return bool + */ + public function hasProductLinks($parentId) + { + $connection = $this->getConnection(); + $select = $connection->select()->from( + $this->getMainTable(), + ['count' => new \Zend_Db_Expr('COUNT(*)')] + )->where( + 'product_id = :product_id' + ) ; + + return $connection->fetchOne( + $select, + [ + 'product_id' => $parentId + ] + ) > 0; + } + /** * Save Product Links process * diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index c5cdd1950cc3803a238bdd77caa1ee5fa2f8b88e..58e364920bb6ad4919212ba329e2d8294cd36027 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -15,13 +15,14 @@ use Magento\CatalogInventory\Model\Indexer\Stock\Processor; use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResource; use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; use Magento\CatalogInventory\Model\StockRegistryStorage; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\MapperFactory; use Magento\Framework\DB\QueryBuilderFactory; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** * Class StockItemRepository @@ -89,6 +90,9 @@ class StockItemRepository implements StockItemRepositoryInterface */ protected $stockRegistryStorage; + /** @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */ + protected $productCollectionFactory; + /** * @param StockConfigurationInterface $stockConfiguration * @param StockStateProviderInterface $stockStateProvider @@ -129,6 +133,21 @@ class StockItemRepository implements StockItemRepositoryInterface $this->dateTime = $dateTime; } + /** + * @deprecated + * @return \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + */ + private function getProductCollectionFactory() + { + if ($this->productCollectionFactory === null) { + $this->productCollectionFactory = ObjectManager::getInstance()->get( + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class + ); + } + + return $this->productCollectionFactory; + } + /** * @inheritdoc */ @@ -136,8 +155,12 @@ class StockItemRepository implements StockItemRepositoryInterface { try { /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productFactory->create(); - $product->load($stockItem->getProductId()); + $product = $this->getProductCollectionFactory()->create() + ->setFlag('has_stock_status_filter') + ->addIdFilter($stockItem->getProductId()) + ->addFieldToSelect('type_id') + ->getFirstItem(); + if (!$product->getId()) { return $stockItem; } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php index 769e2db4cc84553a3a10e081e6785a02a72b5d15..23b7805e622e32938a3403e58af7f30d8a8d80e8 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php @@ -5,6 +5,8 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Stock; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogInventory\Model\Stock\StockItemRepository; use Magento\CatalogInventory\Api\Data as InventoryApiData; use Magento\CatalogInventory\Model\StockRegistryStorage; @@ -153,9 +155,8 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['load', 'getId', 'getTypeId', '__wakeup']) ->getMock(); - $this->productFactoryMock->expects($this->any()) - ->method('create') - ->willReturn($this->productMock); + + $this->productFactoryMock->expects($this->any())->method('create')->willReturn($this->productMock); $this->queryBuilderFactoryMock = $this->getMockBuilder(\Magento\Framework\DB\QueryBuilderFactory::class) ->setMethods(['create']) @@ -185,6 +186,22 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); + $productCollection = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Collection::class + )->disableOriginalConstructor()->getMock(); + + $productCollection->expects($this->any())->method('setFlag')->willReturnSelf(); + $productCollection->expects($this->any())->method('addIdFilter')->willReturnSelf(); + $productCollection->expects($this->any())->method('addFieldToSelect')->willReturnSelf(); + $productCollection->expects($this->any())->method('getFirstItem')->willReturn($this->productMock); + + $productCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $productCollectionFactory->expects($this->any())->method('create')->willReturn($productCollection); + $this->model = (new ObjectManager($this))->getObject( StockItemRepository::class, [ @@ -200,6 +217,7 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase 'indexProcessor' => $this->indexProcessorMock, 'dateTime' => $this->dateTime, 'stockRegistryStorage' => $this->stockRegistryStorage, + 'productCollectionFactory' => $productCollectionFactory, ] ); } @@ -263,7 +281,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn($productId); $this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId'); $this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(true); @@ -309,7 +326,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn(null); $this->stockRegistryStorage->expects($this->never())->method('removeStockItem'); $this->stockRegistryStorage->expects($this->never())->method('removeStockStatus'); @@ -325,7 +341,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn($productId); $this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId'); $this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(false); diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php index ea24235b0fe2ef694e2087f7a8bdbf4c8ece3399..cb9a6b534609f7205f2c8a569b022389e13436b7 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php @@ -32,7 +32,7 @@ class UpdateConfigurations 'swatch_image', 'small_image', 'thumbnail', - 'image' + 'image', ]; /** @@ -65,13 +65,15 @@ class UpdateConfigurations ) { $configurations = $this->getConfigurations(); $configurations = $this->variationHandler->duplicateImagesForVariations($configurations); - foreach ($configurations as $productId => $productData) { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0)); - $productData = $this->variationHandler->processMediaGallery($product, $productData); - $product->addData($productData); - if ($product->hasDataChanges()) { - $product->save(); + if (count($configurations)) { + foreach ($configurations as $productId => $productData) { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0)); + $productData = $this->variationHandler->processMediaGallery($product, $productData); + $product->addData($productData); + if ($product->hasDataChanges()) { + $product->save(); + } } } return $configurableProduct; @@ -91,6 +93,12 @@ class UpdateConfigurations } foreach ($configurableMatrix as $item) { + if (empty($item['was_changed'])) { + continue; + } else { + unset($item['was_changed']); + } + if (!$item['newProduct']) { $result[$item['id']] = $this->mapData($item); diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 321870b96d32abf13c48fdc0975efc9ba5248f17..0d0bba60a7777c75deef4bc9e0a41720b9597163 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -29,6 +29,9 @@ class VariationHandler /** @var \Magento\Catalog\Model\ProductFactory */ protected $productFactory; + /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[] */ + private $attributes; + /** * @var \Magento\CatalogInventory\Api\StockConfigurationInterface * @deprecated @@ -70,6 +73,7 @@ class VariationHandler public function generateSimpleProducts($parentProduct, $productsData) { $generatedProductIds = []; + $this->attributes = null; $productsData = $this->duplicateImagesForVariations($productsData); foreach ($productsData as $simpleProductData) { $newSimpleProduct = $this->productFactory->create(); @@ -160,7 +164,10 @@ class VariationHandler $parentProduct->getNewVariationsAttributeSetId() ); - foreach ($product->getTypeInstance()->getSetAttributes($product) as $attribute) { + if ($this->attributes === null) { + $this->attributes = $product->getTypeInstance()->getSetAttributes($product); + } + foreach ($this->attributes as $attribute) { if ($attribute->getIsUnique() || $attribute->getAttributeCode() == 'url_key' || $attribute->getFrontend()->getInputType() == 'gallery' || diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php index bed5c96b5216eb76344e9ce593ea56056be9a35b..def49f42fa960b738fe952be4a2d18bada61ffd2 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php @@ -90,13 +90,24 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase 'swatch_image' => 'simple2_swatch_image', 'small_image' => 'simple2_small_image', 'thumbnail' => 'simple2_thumbnail', - 'image' => 'simple2_image' + 'image' => 'simple2_image', + 'was_changed' => true, ], [ 'newProduct' => false, 'id' => 'product3', - 'qty' => '3' - ] + 'qty' => '3', + 'was_changed' => true, + ], + [ + 'newProduct' => false, + 'id' => 'product4', + 'status' => 'simple4_status', + 'sku' => 'simple2_sku', + 'name' => 'simple2_name', + 'price' => '3.33', + 'weight' => '5.55', + ], ]; $configurations = [ 'product2' => [ @@ -118,8 +129,8 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase ]; /** @var Product[]|\PHPUnit_Framework_MockObject_MockObject[] $productMocks */ $productMocks = [ - 'product2' => $this->getProductMock($configurations['product2'], true), - 'product3' => $this->getProductMock($configurations['product3']) + 'product2' => $this->getProductMock($configurations['product2'], true, true), + 'product3' => $this->getProductMock($configurations['product3'], false, true), ]; $this->requestMock->expects(static::any()) @@ -161,26 +172,27 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase * @param bool $hasDataChanges * @return Product|\PHPUnit_Framework_MockObject_MockObject */ - protected function getProductMock(array $expectedData = null, $hasDataChanges = false) + protected function getProductMock(array $expectedData = null, $hasDataChanges = false, $wasChanged = false) { $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); - if ($expectedData !== null) { - $productMock->expects(static::once()) - ->method('addData') - ->with($expectedData) + if ($wasChanged !== false) { + if ($expectedData !== null) { + $productMock->expects(static::once()) + ->method('addData') + ->with($expectedData) + ->willReturnSelf(); + } + + $productMock->expects(static::any()) + ->method('hasDataChanges') + ->willReturn($hasDataChanges); + $productMock->expects($hasDataChanges ? static::once() : static::never()) + ->method('save') ->willReturnSelf(); } - - $productMock->expects(static::any()) - ->method('hasDataChanges') - ->willReturn($hasDataChanges); - $productMock->expects($hasDataChanges ? static::once() : static::never()) - ->method('save') - ->willReturnSelf(); - return $productMock; } } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js index ffabd9a8627dfca82da9b87ebd84c799086d9edf..c182d9f8216c09a42868eb487dfa06b0e718b5c7 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js @@ -391,11 +391,11 @@ define([ 'small_image': row['small_image'], image: row.image, 'thumbnail': row.thumbnail, - 'attributes': attributesText + 'attributes': attributesText, + 'was_changed': true }; product[this.canEditField] = row.editable; product[this.newProductField] = row.newProduct; - tmpArray.push(product); }, this); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 4f13ee75bfe32c29ae544c19e950691ead763323..0d8d18df223767b2812f58356de3b7904090d8f7 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -6,8 +6,8 @@ namespace Magento\Eav\Model\Entity\Attribute; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\Exception\LocalizedException; /** * Entity/Attribute/Model - attribute abstract @@ -595,11 +595,10 @@ abstract class AbstractAttribute extends \Magento\Framework\Model\AbstractExtens { /** @var array $emptyStringTypes list of attribute types that treat empty string as a possible value */ $emptyStringTypes = ['int', 'decimal', 'datetime', 'varchar', 'text', 'static']; - $attributeType = $this->getBackend()->getType(); return (is_array($value) && count($value) == 0) || $value === null - || ($value === false && $attributeType != 'int') - || ($value === '' && in_array($attributeType, $emptyStringTypes)); + || ($value === false && $this->getBackend()->getType() != 'int') + || ($value === '' && in_array($this->getBackend()->getType(), $emptyStringTypes)); } /** diff --git a/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php b/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..439f550a2bf020fadd8ae18abfb2348f4b18cf6b --- /dev/null +++ b/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Eav\Model\ResourceModel; + +use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; +use Magento\Eav\Model\Entity\AttributeCache; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Сlass responsible for loading and caching of attributes related to the given attribute set. + * + * Can be used to improve performance of services that mostly read attribute data. + */ +class AttributeLoader +{ + /** Name of ATTRIBUTE_SET_ID field */ + const ATTRIBUTE_SET_ID = 'attribute_set_id'; + + /** + * @var AttributeRepository + */ + private $attributeRepository; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var AttributeCache + */ + private $attributeCache; + + /** + * AttributeLoader constructor. + * @param AttributeRepository $attributeRepository + * @param MetadataPool $metadataPool + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param AttributeCache $attributeCache + */ + public function __construct( + AttributeRepository $attributeRepository, + MetadataPool $metadataPool, + SearchCriteriaBuilder $searchCriteriaBuilder, + AttributeCache $attributeCache + ) { + $this->attributeRepository = $attributeRepository; + $this->metadataPool = $metadataPool; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->attributeCache = $attributeCache; + } + + /** + * Get attributes list from attribute set + * + * @param string $entityType + * @param int $attributeSetId + * @return \Magento\Eav\Api\Data\AttributeInterface[]|\object[] + */ + public function getAttributes($entityType, $attributeSetId = null) + { + $suffix = self::ATTRIBUTE_SET_ID . '-' . ($attributeSetId ?: 'all'); + if ($attributes = $this->attributeCache->getAttributes($entityType, $suffix)) { + return $attributes; + } + + $metadata = $this->metadataPool->getMetadata($entityType); + + if ($attributeSetId === null) { + $criteria = $this->searchCriteriaBuilder->addFilter(self::ATTRIBUTE_SET_ID, null, 'neq')->create(); + } else { + $criteria = $this->searchCriteriaBuilder->addFilter(self::ATTRIBUTE_SET_ID, $attributeSetId)->create(); + } + + $searchResult = $this->attributeRepository->getList( + $metadata->getEavEntityType(), + $criteria + ); + $attributes = $searchResult->getItems(); + + $this->attributeCache->saveAttributes( + $entityType, + $attributes, + $suffix + ); + return $attributes; + } +} diff --git a/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php b/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php index 74373cb593c4086c06378050e1fa89b944d78be6..5e340595df9867b1cb02fd795c6fc9be7e34fc02 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php +++ b/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php @@ -113,13 +113,14 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->delete[$entityType] as $link => $data) { $attributeCodes = array_keys($data); foreach ($attributeCodes as $attributeCode) { /** @var AbstractAttribute $attribute */ $attribute = $this->attributeRepository->get($metadata->getEavEntityType(), $attributeCode); $conditions = [ - $metadata->getLinkField() . ' = ?' => $link, + $linkField . ' = ?' => $link, 'attribute_id = ?' => $attribute->getAttributeId() ]; foreach ($context as $scope) { @@ -147,6 +148,7 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->insert[$entityType] as $link => $data) { foreach ($data as $attributeCode => $attributeValue) { /** @var AbstractAttribute $attribute */ @@ -155,7 +157,7 @@ class AttributePersistor $attributeCode ); $data = [ - $metadata->getLinkField() => $link, + $linkField => $link, 'attribute_id' => $attribute->getAttributeId(), 'value' => $this->prepareValue($entityType, $attributeValue, $attribute) ]; @@ -180,6 +182,7 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->update[$entityType] as $link => $data) { foreach ($data as $attributeCode => $attributeValue) { /** @var AbstractAttribute $attribute */ @@ -188,7 +191,7 @@ class AttributePersistor $attributeCode ); $conditions = [ - $metadata->getLinkField() . ' = ?' => $link, + $linkField . ' = ?' => $link, 'attribute_id = ?' => $attribute->getAttributeId(), ]; foreach ($context as $scope) { diff --git a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php index 1a8aaaf027f561966101e360020fb3a2441e27d6..df411b6a21698033c60839c0d855c3b0ed4e96e5 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php @@ -6,9 +6,10 @@ namespace Magento\Eav\Model\ResourceModel; use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; -use Magento\Framework\EntityManager\Operation\AttributeInterface; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\EntityManager\Operation\AttributeInterface; use Magento\Framework\Model\Entity\ScopeResolver; /** @@ -42,40 +43,43 @@ class CreateHandler implements AttributeInterface */ private $scopeResolver; + /** + * @var AttributeLoader + */ + private $attributeLoader; + /** * @param AttributeRepository $attributeRepository * @param MetadataPool $metadataPool * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param AttributePersistor $attributePersistor * @param ScopeResolver $scopeResolver + * @param AttributeLoader $attributeLoader */ public function __construct( AttributeRepository $attributeRepository, MetadataPool $metadataPool, SearchCriteriaBuilder $searchCriteriaBuilder, AttributePersistor $attributePersistor, - ScopeResolver $scopeResolver + ScopeResolver $scopeResolver, + AttributeLoader $attributeLoader = null ) { $this->attributeRepository = $attributeRepository; $this->metadataPool = $metadataPool; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->attributePersistor = $attributePersistor; $this->scopeResolver = $scopeResolver; + $this->attributeLoader = $attributeLoader ?: ObjectManager::getInstance()->get(AttributeLoader::class); } /** * @param string $entityType + * @param int $attributeSetId * @return \Magento\Eav\Api\Data\AttributeInterface[] - * @throws \Exception */ - protected function getAttributes($entityType) + protected function getAttributes($entityType, $attributeSetId = null) { - $metadata = $this->metadataPool->getMetadata($entityType); - $searchResult = $this->attributeRepository->getList( - $metadata->getEavEntityType(), - $this->searchCriteriaBuilder->addFilter('attribute_set_id', null, 'neq')->create() - ); - return $searchResult->getItems(); + return $this->attributeLoader->getAttributes($entityType, $attributeSetId); } /** @@ -92,23 +96,28 @@ class CreateHandler implements AttributeInterface $metadata = $this->metadataPool->getMetadata($entityType); if ($metadata->getEavEntityType()) { $processed = []; + $entityLinkField = $metadata->getLinkField(); + $attributeSetId = isset($entityData[AttributeLoader::ATTRIBUTE_SET_ID]) + ? $entityData[AttributeLoader::ATTRIBUTE_SET_ID] + : null; // @todo verify is it normal to not have attributer_set_id /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute */ - foreach ($this->getAttributes($entityType) as $attribute) { + foreach ($this->getAttributes($entityType, $attributeSetId) as $attribute) { if ($attribute->isStatic()) { continue; } - if (isset($entityData[$attribute->getAttributeCode()]) - && !is_array($entityData[$attribute->getAttributeCode()]) - && !$attribute->isValueEmpty($entityData[$attribute->getAttributeCode()]) + + $attributeCode = $attribute->getAttributeCode(); + if (isset($entityData[$attributeCode]) + && !is_array($entityData[$attributeCode]) + && !$attribute->isValueEmpty($entityData[$attributeCode]) ) { - $entityLinkField = $metadata->getLinkField(); $this->attributePersistor->registerInsert( $entityType, $entityData[$entityLinkField], - $attribute->getAttributeCode(), - $entityData[$attribute->getAttributeCode()] + $attributeCode, + $entityData[$attributeCode] ); - $processed[$attribute->getAttributeCode()] = $entityData[$attribute->getAttributeCode()]; + $processed[$attributeCode] = $entityData[$attributeCode]; } } $context = $this->scopeResolver->getEntityContext($entityType, $entityData); diff --git a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php index 0f892a272fa1a209ca7b2e632abc98db5afb5d3d..c775a24a03c465dad45640a7f634823cb8b43ef8 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php @@ -54,6 +54,11 @@ class UpdateHandler implements AttributeInterface */ private $readHandler; + /** + * @var AttributeLoader + */ + private $attributeLoader; + /** * UpdateHandler constructor. * @param AttributeRepository $attributeRepository @@ -62,6 +67,7 @@ class UpdateHandler implements AttributeInterface * @param AttributePersistor $attributePersistor * @param ReadSnapshot $readSnapshot * @param ScopeResolver $scopeResolver + * @param AttributeLoader $attributeLoader */ public function __construct( AttributeRepository $attributeRepository, @@ -69,7 +75,8 @@ class UpdateHandler implements AttributeInterface SearchCriteriaBuilder $searchCriteriaBuilder, AttributePersistor $attributePersistor, ReadSnapshot $readSnapshot, - ScopeResolver $scopeResolver + ScopeResolver $scopeResolver, + AttributeLoader $attributeLoader = null ) { $this->attributeRepository = $attributeRepository; $this->metadataPool = $metadataPool; @@ -77,22 +84,17 @@ class UpdateHandler implements AttributeInterface $this->attributePersistor = $attributePersistor; $this->readSnapshot = $readSnapshot; $this->scopeResolver = $scopeResolver; + $this->attributeLoader = $attributeLoader ?: ObjectManager::getInstance()->get(AttributeLoader::class); } /** * @param string $entityType + * @param int $attributeSetId * @return \Magento\Eav\Api\Data\AttributeInterface[] - * @throws \Exception */ - protected function getAttributes($entityType) + protected function getAttributes($entityType, $attributeSetId = null) { - $metadata = $this->metadataPool->getMetadata($entityType); - - $searchResult = $this->attributeRepository->getList( - $metadata->getEavEntityType(), - $this->searchCriteriaBuilder->addFilter('attribute_set_id', null, 'neq')->create() - ); - return $searchResult->getItems(); + return $this->attributeLoader->getAttributes($entityType, $attributeSetId); } /** @@ -118,8 +120,11 @@ class UpdateHandler implements AttributeInterface $entityDataForSnapshot[$scope->getIdentifier()] = $entityData[$scope->getIdentifier()]; } } + $attributeSetId = isset($entityData[AttributeLoader::ATTRIBUTE_SET_ID]) + ? $entityData[AttributeLoader::ATTRIBUTE_SET_ID] + : null; // @todo verify is it normal to not have attributer_set_id $snapshot = $this->readSnapshot->execute($entityType, $entityDataForSnapshot); - foreach ($this->getAttributes($entityType) as $attribute) { + foreach ($this->getAttributes($entityType, $attributeSetId) as $attribute) { if ($attribute->isStatic()) { continue; } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php index d6b88e0ac56915c3b116ac740b18cfd31ae9f876..a10bafacda42dc2b72fcc285307a0250cae3074b 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php @@ -207,7 +207,7 @@ class AbstractAttributeTest extends \PHPUnit_Framework_TestCase ] ); $backendModelMock->expects($this->any())->method('getType')->willReturn($attributeType); - $model->expects($this->once())->method('getBackend')->willReturn($backendModelMock); + $model->expects($this->any())->method('getBackend')->willReturn($backendModelMock); $this->assertEquals($isEmpty, $model->isValueEmpty($value)); } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php index 5eeb7ddb61fa05657b08dda4711e82938e8778ce..0ad53eeeb90d9dab3a8877624429efefe314aefd 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php @@ -51,6 +51,7 @@ class ApiDataFixture */ public function startTest(\PHPUnit_Framework_TestCase $test) { + \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); /** Apply method level fixtures if thy are available, apply class level fixtures otherwise */ $this->_applyFixtures($this->_getFixtures('method', $test) ?: $this->_getFixtures('class', $test)); } @@ -61,6 +62,9 @@ class ApiDataFixture public function endTest() { $this->_revertFixtures(); + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager->get(\Magento\Eav\Model\Entity\AttributeCache::class)->clear(); } /** diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php index 7fb35a4412bf8bbbda969b646a23eee84aac61ac..ec230f3c5126185e21d27226c65f6e09e7f125e5 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php @@ -56,7 +56,7 @@ $product->setTypeId( )->setId( 444 )->setAttributeSetId( - 5 + 4 )->setStoreId( 1 )->setWebsiteIds(