Skip to content

Commit e781520

Browse files
merge magento/2.3-develop into magento-trigger/MC-17627
2 parents 4faa4c9 + b298a37 commit e781520

File tree

79 files changed

+5020
-622
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+5020
-622
lines changed
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\DataProvider;
9+
10+
use Magento\Eav\Model\Config;
11+
use Magento\Framework\App\ResourceConnection;
12+
use Magento\Framework\DB\Select;
13+
use Magento\Framework\EntityManager\MetadataPool;
14+
15+
/**
16+
* Generic for build Select object to fetch eav attributes for provided entity type
17+
*/
18+
class AttributeQuery
19+
{
20+
/**
21+
* @var ResourceConnection
22+
*/
23+
private $resourceConnection;
24+
25+
/**
26+
* @var MetadataPool
27+
*/
28+
private $metadataPool;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $entityType;
34+
35+
/**
36+
* List of attributes that need to be added/removed to fetch
37+
*
38+
* @var array
39+
*/
40+
private $linkedAttributes;
41+
42+
/**
43+
* @var array
44+
*/
45+
private const SUPPORTED_BACKEND_TYPES = [
46+
'int',
47+
'decimal',
48+
'text',
49+
'varchar',
50+
'datetime',
51+
];
52+
53+
/**
54+
* @var int[]
55+
*/
56+
private $entityTypeIdMap;
57+
58+
/**
59+
* @var Config
60+
*/
61+
private $eavConfig;
62+
63+
/**
64+
* @param string $entityType
65+
* @param ResourceConnection $resourceConnection
66+
* @param MetadataPool $metadataPool
67+
* @param Config $eavConfig
68+
* @param array $linkedAttributes
69+
*/
70+
public function __construct(
71+
string $entityType,
72+
ResourceConnection $resourceConnection,
73+
MetadataPool $metadataPool,
74+
Config $eavConfig,
75+
array $linkedAttributes = []
76+
) {
77+
$this->resourceConnection = $resourceConnection;
78+
$this->metadataPool = $metadataPool;
79+
$this->entityType = $entityType;
80+
$this->linkedAttributes = $linkedAttributes;
81+
$this->eavConfig = $eavConfig;
82+
}
83+
84+
/**
85+
* Form and return query to get eav entity $attributes for given $entityIds.
86+
*
87+
* If eav entities were not found, then data is fetching from $entityTableName.
88+
*
89+
* @param array $entityIds
90+
* @param array $attributes
91+
* @param int $storeId
92+
* @return Select
93+
* @throws \Zend_Db_Select_Exception
94+
* @throws \Exception
95+
*/
96+
public function getQuery(array $entityIds, array $attributes, int $storeId): Select
97+
{
98+
/** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
99+
$metadata = $this->metadataPool->getMetadata($this->entityType);
100+
$entityTableName = $metadata->getEntityTable();
101+
102+
/** @var \Magento\Framework\DB\Adapter\AdapterInterface $connection */
103+
$connection = $this->resourceConnection->getConnection();
104+
$entityTableAttributes = \array_keys($connection->describeTable($entityTableName));
105+
106+
$attributeMetadataTable = $this->resourceConnection->getTableName('eav_attribute');
107+
$eavAttributes = $this->getEavAttributeCodes($attributes, $entityTableAttributes);
108+
$entityTableAttributes = \array_intersect($attributes, $entityTableAttributes);
109+
110+
$eavAttributesMetaData = $this->getAttributesMetaData($connection, $attributeMetadataTable, $eavAttributes);
111+
112+
if ($eavAttributesMetaData) {
113+
$select = $this->getEavAttributes(
114+
$connection,
115+
$metadata,
116+
$entityTableAttributes,
117+
$entityIds,
118+
$eavAttributesMetaData,
119+
$entityTableName,
120+
$storeId
121+
);
122+
} else {
123+
$select = $this->getAttributesFromEntityTable(
124+
$connection,
125+
$entityTableAttributes,
126+
$entityIds,
127+
$entityTableName
128+
);
129+
}
130+
131+
return $select;
132+
}
133+
134+
/**
135+
* Form and return query to get entity $entityTableAttributes for given $entityIds
136+
*
137+
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
138+
* @param array $entityTableAttributes
139+
* @param array $entityIds
140+
* @param string $entityTableName
141+
* @return Select
142+
*/
143+
private function getAttributesFromEntityTable(
144+
\Magento\Framework\DB\Adapter\AdapterInterface $connection,
145+
array $entityTableAttributes,
146+
array $entityIds,
147+
string $entityTableName
148+
): Select {
149+
$select = $connection->select()
150+
->from(['e' => $entityTableName], $entityTableAttributes)
151+
->where('e.entity_id IN (?)', $entityIds);
152+
153+
return $select;
154+
}
155+
156+
/**
157+
* Return ids of eav attributes by $eavAttributeCodes.
158+
*
159+
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
160+
* @param string $attributeMetadataTable
161+
* @param array $eavAttributeCodes
162+
* @return array
163+
*/
164+
private function getAttributesMetaData(
165+
\Magento\Framework\DB\Adapter\AdapterInterface $connection,
166+
string $attributeMetadataTable,
167+
array $eavAttributeCodes
168+
): array {
169+
$eavAttributeIdsSelect = $connection->select()
170+
->from(['a' => $attributeMetadataTable], ['attribute_id', 'backend_type', 'attribute_code'])
171+
->where('a.attribute_code IN (?)', $eavAttributeCodes)
172+
->where('a.entity_type_id = ?', $this->getEntityTypeId());
173+
174+
return $connection->fetchAssoc($eavAttributeIdsSelect);
175+
}
176+
177+
/**
178+
* Form and return query to get eav entity $attributes for given $entityIds.
179+
*
180+
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
181+
* @param \Magento\Framework\EntityManager\EntityMetadataInterface $metadata
182+
* @param array $entityTableAttributes
183+
* @param array $entityIds
184+
* @param array $eavAttributesMetaData
185+
* @param string $entityTableName
186+
* @param int $storeId
187+
* @return Select
188+
* @throws \Zend_Db_Select_Exception
189+
*/
190+
private function getEavAttributes(
191+
\Magento\Framework\DB\Adapter\AdapterInterface $connection,
192+
\Magento\Framework\EntityManager\EntityMetadataInterface $metadata,
193+
array $entityTableAttributes,
194+
array $entityIds,
195+
array $eavAttributesMetaData,
196+
string $entityTableName,
197+
int $storeId
198+
): Select {
199+
$selects = [];
200+
$attributeValueExpression = $connection->getCheckSql(
201+
$connection->getIfNullSql('store_eav.value_id', -1) . ' > 0',
202+
'store_eav.value',
203+
'eav.value'
204+
);
205+
$linkField = $metadata->getLinkField();
206+
$attributesPerTable = $this->getAttributeCodeTables($entityTableName, $eavAttributesMetaData);
207+
foreach ($attributesPerTable as $attributeTable => $eavAttributes) {
208+
$attributeCodeExpression = $this->buildAttributeCodeExpression($eavAttributes);
209+
210+
$selects[] = $connection->select()
211+
->from(['e' => $entityTableName], $entityTableAttributes)
212+
->joinLeft(
213+
['eav' => $this->resourceConnection->getTableName($attributeTable)],
214+
\sprintf('e.%1$s = eav.%1$s', $linkField) .
215+
$connection->quoteInto(' AND eav.attribute_id IN (?)', \array_keys($eavAttributesMetaData)) .
216+
$connection->quoteInto(' AND eav.store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID),
217+
[]
218+
)
219+
->joinLeft(
220+
['store_eav' => $this->resourceConnection->getTableName($attributeTable)],
221+
\sprintf(
222+
'e.%1$s = store_eav.%1$s AND store_eav.attribute_id = ' .
223+
'eav.attribute_id and store_eav.store_id = %2$d',
224+
$linkField,
225+
$storeId
226+
),
227+
[]
228+
)
229+
->where('e.entity_id IN (?)', $entityIds)
230+
->columns(
231+
[
232+
'attribute_code' => $attributeCodeExpression,
233+
'value' => $attributeValueExpression
234+
]
235+
);
236+
}
237+
238+
return $connection->select()->union($selects, Select::SQL_UNION_ALL);
239+
}
240+
241+
/**
242+
* Build expression for attribute code field.
243+
*
244+
* An example:
245+
*
246+
* ```
247+
* CASE
248+
* WHEN eav.attribute_id = '73' THEN 'name'
249+
* WHEN eav.attribute_id = '121' THEN 'url_key'
250+
* END
251+
* ```
252+
*
253+
* @param array $eavAttributes
254+
* @return \Zend_Db_Expr
255+
*/
256+
private function buildAttributeCodeExpression(array $eavAttributes): \Zend_Db_Expr
257+
{
258+
$dbConnection = $this->resourceConnection->getConnection();
259+
$expressionParts = ['CASE'];
260+
261+
foreach ($eavAttributes as $attribute) {
262+
$expressionParts[]=
263+
$dbConnection->quoteInto('WHEN eav.attribute_id = ?', $attribute['attribute_id'], \Zend_Db::INT_TYPE) .
264+
$dbConnection->quoteInto(' THEN ?', $attribute['attribute_code'], 'string');
265+
}
266+
267+
$expressionParts[]= 'END';
268+
269+
return new \Zend_Db_Expr(implode(' ', $expressionParts));
270+
}
271+
272+
/**
273+
* Get list of attribute tables.
274+
*
275+
* Returns result in the following format: *
276+
* ```
277+
* $attributeAttributeCodeTables = [
278+
* 'm2_catalog_product_entity_varchar' =>
279+
* '45' => [
280+
* 'attribute_id' => 45,
281+
* 'backend_type' => 'varchar',
282+
* 'name' => attribute_code,
283+
* ]
284+
* ]
285+
* ];
286+
* ```
287+
*
288+
* @param string $entityTable
289+
* @param array $eavAttributesMetaData
290+
* @return array
291+
*/
292+
private function getAttributeCodeTables($entityTable, $eavAttributesMetaData): array
293+
{
294+
$attributeAttributeCodeTables = [];
295+
$metaTypes = \array_unique(\array_column($eavAttributesMetaData, 'backend_type'));
296+
297+
foreach ($metaTypes as $type) {
298+
if (\in_array($type, self::SUPPORTED_BACKEND_TYPES, true)) {
299+
$tableName = \sprintf('%s_%s', $entityTable, $type);
300+
$attributeAttributeCodeTables[$tableName] = array_filter(
301+
$eavAttributesMetaData,
302+
function ($attribute) use ($type) {
303+
return $attribute['backend_type'] === $type;
304+
}
305+
);
306+
}
307+
}
308+
309+
return $attributeAttributeCodeTables;
310+
}
311+
312+
/**
313+
* Get EAV attribute codes
314+
* Remove attributes from entity table and attributes from exclude list
315+
* Add linked attributes to output
316+
*
317+
* @param array $attributes
318+
* @param array $entityTableAttributes
319+
* @return array
320+
*/
321+
private function getEavAttributeCodes($attributes, $entityTableAttributes): array
322+
{
323+
$attributes = \array_diff($attributes, $entityTableAttributes);
324+
$unusedAttributeList = [];
325+
$newAttributes = [];
326+
foreach ($this->linkedAttributes as $attribute => $linkedAttributes) {
327+
if (null === $linkedAttributes) {
328+
$unusedAttributeList[] = $attribute;
329+
} elseif (\is_array($linkedAttributes) && \in_array($attribute, $attributes, true)) {
330+
$newAttributes[] = $linkedAttributes;
331+
}
332+
}
333+
$attributes = \array_diff($attributes, $unusedAttributeList);
334+
335+
return \array_unique(\array_merge($attributes, ...$newAttributes));
336+
}
337+
338+
/**
339+
* Retrieve entity type id
340+
*
341+
* @return int
342+
* @throws \Exception
343+
*/
344+
private function getEntityTypeId(): int
345+
{
346+
if (!isset($this->entityTypeIdMap[$this->entityType])) {
347+
$this->entityTypeIdMap[$this->entityType] = (int)$this->eavConfig->getEntityType(
348+
$this->metadataPool->getMetadata($this->entityType)->getEavEntityType()
349+
)->getId();
350+
}
351+
352+
return $this->entityTypeIdMap[$this->entityType];
353+
}
354+
}

0 commit comments

Comments
 (0)