Skip to content

Commit db15b21

Browse files
author
Oleksandr Gorkun
committed
MC-22139: Introduce batch GraphQL resolvers
1 parent 5e07374 commit db15b21

File tree

40 files changed

+2552
-111
lines changed

40 files changed

+2552
-111
lines changed

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,12 +1326,11 @@ public function getSpecialToDate()
13261326
public function getRelatedProducts()
13271327
{
13281328
if (!$this->hasRelatedProducts()) {
1329-
$products = [];
1330-
$collection = $this->getRelatedProductCollection();
1331-
foreach ($collection as $product) {
1332-
$products[] = $product;
1329+
//Loading all linked products.
1330+
$this->getProductLinks();
1331+
if (!$this->hasRelatedProducts()) {
1332+
$this->setRelatedProducts([]);
13331333
}
1334-
$this->setRelatedProducts($products);
13351334
}
13361335
return $this->getData('related_products');
13371336
}
@@ -1388,12 +1387,13 @@ public function getRelatedLinkCollection()
13881387
public function getUpSellProducts()
13891388
{
13901389
if (!$this->hasUpSellProducts()) {
1391-
$products = [];
1392-
foreach ($this->getUpSellProductCollection() as $product) {
1393-
$products[] = $product;
1390+
//Loading all linked products.
1391+
$this->getProductLinks();
1392+
if (!$this->hasUpSellProducts()) {
1393+
$this->setUpSellProducts([]);
13941394
}
1395-
$this->setUpSellProducts($products);
13961395
}
1396+
13971397
return $this->getData('up_sell_products');
13981398
}
13991399

@@ -1449,12 +1449,13 @@ public function getUpSellLinkCollection()
14491449
public function getCrossSellProducts()
14501450
{
14511451
if (!$this->hasCrossSellProducts()) {
1452-
$products = [];
1453-
foreach ($this->getCrossSellProductCollection() as $product) {
1454-
$products[] = $product;
1452+
//Loading all linked products.
1453+
$this->getProductLinks();
1454+
if (!$this->hasCrossSellProducts()) {
1455+
$this->setCrossSellProducts([]);
14551456
}
1456-
$this->setCrossSellProducts($products);
14571457
}
1458+
14581459
return $this->getData('cross_sell_products');
14591460
}
14601461

@@ -1510,7 +1511,11 @@ public function getCrossSellLinkCollection()
15101511
public function getProductLinks()
15111512
{
15121513
if ($this->_links === null) {
1513-
$this->_links = $this->getLinkRepository()->getList($this);
1514+
if ($this->getSku() && $this->getId()) {
1515+
$this->_links = $this->getLinkRepository()->getList($this);
1516+
} else {
1517+
$this->_links = [];
1518+
}
15141519
}
15151520
return $this->_links;
15161521
}

app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
* See COPYING.txt for license details.
55
*/
66

7+
declare(strict_types=1);
8+
79
namespace Magento\Catalog\Model\Product\Link;
810

911
use Magento\Catalog\Api\ProductLinkRepositoryInterface;
1012
use Magento\Catalog\Model\ResourceModel\Product\Link;
1113
use Magento\Framework\EntityManager\MetadataPool;
14+
use Magento\Catalog\Api\Data\ProductLinkInterface;
1215

1316
/**
14-
* Class SaveProductLinks
17+
* Save product links.
1518
*/
1619
class SaveHandler
1720
{
@@ -47,22 +50,24 @@ public function __construct(
4750
}
4851

4952
/**
50-
* @param string $entityType
51-
* @param object $entity
53+
* Save product links for the product.
54+
*
55+
* @param string $entityType Product type.
56+
* @param \Magento\Catalog\Api\Data\ProductInterface $entity
5257
* @return \Magento\Catalog\Api\Data\ProductInterface
5358
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5459
*/
5560
public function execute($entityType, $entity)
5661
{
5762
$link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField());
5863
if ($this->linkResource->hasProductLinks($link)) {
59-
/** @var \Magento\Catalog\Api\Data\ProductInterface $entity */
6064
foreach ($this->productLinkRepository->getList($entity) as $link) {
6165
$this->productLinkRepository->delete($link);
6266
}
6367
}
6468

6569
// Build links per type
70+
/** @var ProductLinkInterface[][] $linksByType */
6671
$linksByType = [];
6772
foreach ($entity->getProductLinks() as $link) {
6873
$linksByType[$link->getLinkType()][] = $link;
@@ -71,13 +76,17 @@ public function execute($entityType, $entity)
7176
// Set array position as a fallback position if necessary
7277
foreach ($linksByType as $linkType => $links) {
7378
if (!$this->hasPosition($links)) {
74-
array_walk($linksByType[$linkType], function ($productLink, $position) {
75-
$productLink->setPosition(++$position);
76-
});
79+
array_walk(
80+
$linksByType[$linkType],
81+
function (ProductLinkInterface $productLink, $position) {
82+
$productLink->setPosition(++$position);
83+
}
84+
);
7785
}
7886
}
7987

8088
// Flatten multi-dimensional linksByType in ProductLinks
89+
/** @var ProductLinkInterface[] $productLinks */
8190
$productLinks = array_reduce($linksByType, 'array_merge', []);
8291

8392
if (count($productLinks) > 0) {
@@ -90,13 +99,14 @@ public function execute($entityType, $entity)
9099

91100
/**
92101
* Check if at least one link without position
93-
* @param array $links
102+
*
103+
* @param ProductLinkInterface[] $links
94104
* @return bool
95105
*/
96-
private function hasPosition(array $links)
106+
private function hasPosition(array $links): bool
97107
{
98108
foreach ($links as $link) {
99-
if (!array_key_exists('position', $link->getData())) {
109+
if ($link->getPosition() === null) {
100110
return false;
101111
}
102112
}

app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php

Lines changed: 147 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\Catalog\Model\ProductLink;
88

9+
use Magento\Catalog\Model\Product;
910
use Magento\Catalog\Model\ProductLink\Converter\ConverterPool;
1011
use Magento\Framework\Exception\NoSuchEntityException;
1112

@@ -19,6 +20,11 @@ class CollectionProvider
1920
*/
2021
protected $providers;
2122

23+
/**
24+
* @var MapProviderInterface[]
25+
*/
26+
private $mapProviders;
27+
2228
/**
2329
* @var ConverterPool
2430
*/
@@ -27,43 +33,169 @@ class CollectionProvider
2733
/**
2834
* @param ConverterPool $converterPool
2935
* @param CollectionProviderInterface[] $providers
36+
* @param MapProviderInterface[] $mapProviders
3037
*/
31-
public function __construct(ConverterPool $converterPool, array $providers = [])
38+
public function __construct(ConverterPool $converterPool, array $providers = [], array $mapProviders = [])
3239
{
3340
$this->converterPool = $converterPool;
3441
$this->providers = $providers;
42+
$this->mapProviders = $mapProviders;
43+
}
44+
45+
/**
46+
* Extract link data from linked products.
47+
*
48+
* @param Product[] $linkedProducts
49+
* @param string $type
50+
* @return array
51+
*/
52+
private function prepareList(array $linkedProducts, string $type): array
53+
{
54+
$converter = $this->converterPool->getConverter($type);
55+
$links = [];
56+
foreach ($linkedProducts as $item) {
57+
$itemId = $item->getId();
58+
$links[$itemId] = $converter->convert($item);
59+
$links[$itemId]['position'] = $links[$itemId]['position'] ?? 0;
60+
$links[$itemId]['link_type'] = $type;
61+
}
62+
63+
return $links;
3564
}
3665

3766
/**
3867
* Get product collection by link type
3968
*
40-
* @param \Magento\Catalog\Model\Product $product
69+
* @param Product $product
4170
* @param string $type
4271
* @return array
4372
* @throws NoSuchEntityException
4473
*/
45-
public function getCollection(\Magento\Catalog\Model\Product $product, $type)
74+
public function getCollection(Product $product, $type)
4675
{
4776
if (!isset($this->providers[$type])) {
4877
throw new NoSuchEntityException(__("The collection provider isn't registered."));
4978
}
5079

5180
$products = $this->providers[$type]->getLinkedProducts($product);
52-
$converter = $this->converterPool->getConverter($type);
53-
$sorterItems = [];
54-
foreach ($products as $item) {
55-
$itemId = $item->getId();
56-
$sorterItems[$itemId] = $converter->convert($item);
57-
$sorterItems[$itemId]['position'] = $sorterItems[$itemId]['position'] ?? 0;
81+
82+
$linkData = $this->prepareList($products, $type);
83+
usort(
84+
$linkData,
85+
function (array $itemA, array $itemB): int {
86+
$posA = (int)$itemA['position'];
87+
$posB = (int)$itemB['position'];
88+
89+
return $posA <=> $posB;
90+
}
91+
);
92+
93+
return $linkData;
94+
}
95+
96+
/**
97+
* Load maps from map providers.
98+
*
99+
* @param array $map
100+
* @param array $typeProcessors
101+
* @param Product[] $products
102+
* @return void
103+
*/
104+
private function retrieveMaps(array &$map, array $typeProcessors, array $products): void
105+
{
106+
/**
107+
* @var MapProviderInterface $processor
108+
* @var string[] $types
109+
*/
110+
foreach ($typeProcessors as $processorIndex => $types) {
111+
$typeMap = $this->mapProviders[$processorIndex]->fetchMap($products, $types);
112+
/**
113+
* @var string $sku
114+
* @var Product[][] $links
115+
*/
116+
foreach ($typeMap as $sku => $links) {
117+
$linkData = [];
118+
foreach ($links as $linkType => $linkedProducts) {
119+
$linkData[] = $this->prepareList($linkedProducts, $linkType);
120+
}
121+
if ($linkData) {
122+
$existing = [];
123+
if (array_key_exists($sku, $map)) {
124+
$existing = $map[$sku];
125+
}
126+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
127+
$map[$sku] = array_merge($existing, ...$linkData);
128+
}
129+
}
58130
}
131+
}
59132

60-
usort($sorterItems, function ($itemA, $itemB) {
61-
$posA = (int)$itemA['position'];
62-
$posB = (int)$itemB['position'];
133+
/**
134+
* Load links for each product separately.
135+
*
136+
* @param \SplObjectStorage $map
137+
* @param string[] $types
138+
* @param Product[] $products
139+
* @return void
140+
* @throws NoSuchEntityException
141+
*/
142+
private function retrieveSingles(array &$map, array $types, array $products): void
143+
{
144+
foreach ($products as $product) {
145+
$linkData = [];
146+
foreach ($types as $type) {
147+
$linkData[] = $this->getCollection($product, $type);
148+
}
149+
$linkData = array_filter($linkData);
150+
if ($linkData) {
151+
$existing = [];
152+
if (array_key_exists($product->getSku(), $map)) {
153+
$existing = $map[$product->getSku()];
154+
}
155+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
156+
$map[$product->getSku()] = array_merge($existing, ...$linkData);
157+
}
158+
}
159+
}
160+
161+
/**
162+
* Load map of linked product data.
163+
*
164+
* Link data consists of link_type, type, sku, position, extension attributes? and custom_attributes?.
165+
*
166+
* @param Product[] $products
167+
* @param array $types Keys - string names, values - codes.
168+
* @return array Keys - SKUs, values containing link data.
169+
* @throws NoSuchEntityException
170+
* @throws \InvalidArgumentException
171+
*/
172+
public function getMap(array $products, array $types): array
173+
{
174+
if (!$types) {
175+
throw new \InvalidArgumentException('Types are required');
176+
}
177+
$map = [];
178+
$typeProcessors = [];
179+
/** @var string[] $singleProcessors */
180+
$singleProcessors = [];
181+
//Finding map processors
182+
foreach ($types as $type => $typeCode) {
183+
foreach ($this->mapProviders as $i => $mapProvider) {
184+
if ($mapProvider->canProcessLinkType($type)) {
185+
if (!array_key_exists($i, $typeProcessors)) {
186+
$typeProcessors[$i] = [];
187+
}
188+
$typeProcessors[$i][$type] = $typeCode;
189+
continue 2;
190+
}
191+
}
192+
//No map processor found, will process 1 by 1
193+
$singleProcessors[] = $type;
194+
}
63195

64-
return $posA <=> $posB;
65-
});
196+
$this->retrieveMaps($map, $typeProcessors, $products);
197+
$this->retrieveSingles($map, $singleProcessors, $products);
66198

67-
return $sorterItems;
199+
return $map;
68200
}
69201
}

0 commit comments

Comments
 (0)