Skip to content

Commit ba4b975

Browse files
Merge pull request #1581 from magento-tango/MAGETWO-71554
Bug - MAGETWO-71554 Visual Merchandiser category edit performance issue - for 2.2
2 parents fb05546 + 833cfa3 commit ba4b975

File tree

12 files changed

+441
-61
lines changed

12 files changed

+441
-61
lines changed

app/code/Magento/Catalog/Model/Category.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
* @method Category setUrlPath(string $urlPath)
2929
* @method Category getSkipDeleteChildren()
3030
* @method Category setSkipDeleteChildren(boolean $value)
31+
* @method Category setChangedProductIds(array $categoryIds) Set products ids that inserted or deleted for category
32+
* @method array getChangedProductIds() Get products ids that inserted or deleted for category
3133
*
3234
* @SuppressWarnings(PHPMD.LongVariable)
3335
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Catalog\Model\Category\Product;
7+
8+
/**
9+
* Resolver to get product positions by ids assigned to specific category
10+
*/
11+
class PositionResolver extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
12+
{
13+
/**
14+
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
15+
* @param string $connectionName
16+
*/
17+
public function __construct(
18+
\Magento\Framework\Model\ResourceModel\Db\Context $context,
19+
$connectionName = null
20+
) {
21+
parent::__construct($context, $connectionName);
22+
}
23+
24+
/**
25+
* Initialize resource model
26+
*
27+
* @return void
28+
*/
29+
protected function _construct()
30+
{
31+
$this->_init('catalog_product_entity', 'entity_id');
32+
}
33+
34+
/**
35+
* Get category product positions
36+
*
37+
* @param int $categoryId
38+
* @return array
39+
*/
40+
public function getPositions(int $categoryId)
41+
{
42+
$connection = $this->getConnection();
43+
44+
$select = $connection->select()->from(
45+
['cpe' => $this->getTable('catalog_product_entity')],
46+
'entity_id'
47+
)->joinLeft(
48+
['ccp' => $this->getTable('catalog_category_product')],
49+
'ccp.product_id=cpe.entity_id'
50+
)->where(
51+
'ccp.category_id = ?',
52+
$categoryId
53+
)->order(
54+
'ccp.position ' . \Magento\Framework\DB\Select::SQL_ASC
55+
);
56+
57+
return array_flip($connection->fetchCol($select));
58+
}
59+
}

app/code/Magento/Catalog/Model/ResourceModel/Category.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,18 @@ protected function _saveCategoryProducts($category)
410410
* Update product positions in category
411411
*/
412412
if (!empty($update)) {
413+
$newPositions = [];
413414
foreach ($update as $productId => $position) {
414-
$where = ['category_id = ?' => (int)$id, 'product_id = ?' => (int)$productId];
415-
$bind = ['position' => (int)$position];
415+
$delta = $position - $oldProducts[$productId];
416+
if (!isset($newPositions[$delta])) {
417+
$newPositions[$delta] = [];
418+
}
419+
$newPositions[$delta][] = $productId;
420+
}
421+
422+
foreach ($newPositions as $delta => $productIds) {
423+
$bind = ['position' => new \Zend_Db_Expr("position + ({$delta})")];
424+
$where = ['category_id = ?' => (int)$id, 'product_id IN (?)' => $productIds];
416425
$connection->update($this->getCategoryProductTable(), $bind, $where);
417426
}
418427
}
@@ -423,6 +432,8 @@ protected function _saveCategoryProducts($category)
423432
'catalog_category_change_products',
424433
['category' => $category, 'product_ids' => $productIds]
425434
);
435+
436+
$category->setChangedProductIds($productIds);
426437
}
427438

428439
if (!empty($insert) || !empty($update) || !empty($delete)) {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Catalog\Test\Unit\Model\Category\Product;
7+
8+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
9+
use Magento\Catalog\Model\Category\Product\PositionResolver;
10+
use Magento\Framework\Model\ResourceModel\Db\Context;
11+
use Magento\Framework\App\ResourceConnection;
12+
use Magento\Framework\DB\Adapter\AdapterInterface;
13+
use Magento\Framework\DB\Select;
14+
15+
class PositionResolverTest extends \PHPUnit\Framework\TestCase
16+
{
17+
/**
18+
* @var Context|\PHPUnit_Framework_MockObject_MockObject
19+
*/
20+
private $context;
21+
22+
/**
23+
* @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject
24+
*/
25+
private $resources;
26+
27+
/**
28+
* @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
29+
*/
30+
private $connection;
31+
32+
/**
33+
* @var Select|\PHPUnit_Framework_MockObject_MockObject
34+
*/
35+
private $select;
36+
37+
/**
38+
* @var PositionResolver
39+
*/
40+
private $model;
41+
42+
/**
43+
* @var array
44+
*/
45+
private $positions = [
46+
'3' => 100,
47+
'2' => 101,
48+
'1' => 102
49+
];
50+
51+
/**
52+
* @var array
53+
*/
54+
private $flippedPositions = [
55+
'100' => 3,
56+
'101' => 2,
57+
'102' => 1
58+
];
59+
60+
/**
61+
* @var int
62+
*/
63+
private $categoryId = 1;
64+
65+
protected function setUp()
66+
{
67+
$this->context = $this->getMockBuilder(Context::class)
68+
->disableOriginalConstructor()
69+
->getMock();
70+
71+
$this->resources = $this->getMockBuilder(ResourceConnection::class)
72+
->disableOriginalConstructor()
73+
->getMock();
74+
75+
$this->connection = $this->getMockBuilder(AdapterInterface::class)
76+
->disableOriginalConstructor()
77+
->getMockForAbstractClass();
78+
79+
$this->select = $this->getMockBuilder(Select::class)
80+
->disableOriginalConstructor()
81+
->getMock();
82+
83+
$this->model = (new ObjectManager($this))->getObject(
84+
PositionResolver::class,
85+
[
86+
'context' => $this->context,
87+
null,
88+
'_resources' => $this->resources
89+
]
90+
);
91+
}
92+
93+
public function testGetPositions()
94+
{
95+
$this->resources->expects($this->once())
96+
->method('getConnection')
97+
->willReturn($this->connection);
98+
99+
$this->connection->expects($this->once())
100+
->method('select')
101+
->willReturn($this->select);
102+
$this->select->expects($this->once())
103+
->method('from')
104+
->willReturnSelf();
105+
$this->select->expects($this->once())
106+
->method('where')
107+
->willReturnSelf();
108+
$this->select->expects($this->once())
109+
->method('order')
110+
->willReturnSelf();
111+
$this->select->expects($this->once())
112+
->method('joinLeft')
113+
->willReturnSelf();
114+
$this->connection->expects($this->once())
115+
->method('fetchCol')
116+
->willReturn($this->positions);
117+
118+
$this->assertEquals($this->flippedPositions, $this->model->getPositions($this->categoryId));
119+
}
120+
}

0 commit comments

Comments
 (0)