Passed
Push — trunk ( ff423b...970276 )
by Christian
13:19 queued 19s
created

EsProductDefinition::fetch()   B

Complexity

Conditions 8
Paths 25

Size

Total Lines 101
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 85
c 0
b 0
f 0
nc 25
nop 2
dl 0
loc 101
rs 7.0828

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Elasticsearch\Product;
4
5
use Doctrine\DBAL\ArrayParameterType;
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\Exception;
8
use OpenSearchDSL\Query\Compound\BoolQuery;
9
use Shopware\Core\Content\Product\ProductDefinition;
10
use Shopware\Core\Framework\Context;
11
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\SqlHelper;
12
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
13
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
14
use Shopware\Core\Framework\Log\Package;
15
use Shopware\Core\Framework\Uuid\Uuid;
16
use Shopware\Elasticsearch\Framework\AbstractElasticsearchDefinition;
17
use Shopware\Elasticsearch\Framework\ElasticsearchFieldBuilder;
18
use Shopware\Elasticsearch\Framework\ElasticsearchFieldMapper;
19
use Shopware\Elasticsearch\Framework\ElasticsearchIndexingUtils;
20
21
/**
22
 * @internal
23
 *
24
 * @decrecated tag:v6.6.0 - Will be removed, please transfer getMapping and fetch method to ElasticsearchProductDefinition
25
 */
26
#[Package('core')]
27
class EsProductDefinition extends AbstractElasticsearchDefinition
28
{
29
    /**
30
     * @internal
31
     */
32
    public function __construct(
33
        private readonly EntityDefinition $definition,
34
        private readonly Connection $connection,
35
        private readonly AbstractProductSearchQueryBuilder $searchQueryBuilder,
36
        private readonly ElasticsearchFieldBuilder $fieldBuilder,
37
        private readonly ElasticsearchFieldMapper $fieldMapper
38
    ) {
39
    }
40
41
    public function getEntityDefinition(): EntityDefinition
42
    {
43
        return $this->definition;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function getMapping(Context $context): array
50
    {
51
        $languageFields = $this->fieldBuilder->translated(self::getTextFieldConfig());
52
53
        $properties = [
54
            'id' => self::KEYWORD_FIELD,
55
            'name' => $languageFields,
56
            'description' => $languageFields,
57
            'metaTitle' => $languageFields,
58
            'metaDescription' => $languageFields,
59
            'customSearchKeywords' => $languageFields,
60
            'categories' => ElasticsearchFieldBuilder::nested([
61
                'name' => $languageFields,
62
            ]),
63
            'manufacturer' => ElasticsearchFieldBuilder::nested([
64
                'name' => $languageFields,
65
            ]),
66
            'options' => ElasticsearchFieldBuilder::nested([
67
                'groupId' => self::KEYWORD_FIELD,
68
                'name' => $languageFields,
69
            ]),
70
            'properties' => ElasticsearchFieldBuilder::nested([
71
                'groupId' => self::KEYWORD_FIELD,
72
                'name' => $languageFields,
73
            ]),
74
            'parentId' => self::KEYWORD_FIELD,
75
            'active' => self::BOOLEAN_FIELD,
76
            'available' => self::BOOLEAN_FIELD,
77
            'isCloseout' => self::BOOLEAN_FIELD,
78
            'categoriesRo' => ElasticsearchFieldBuilder::nested(),
79
            'childCount' => self::INT_FIELD,
80
            'categoryTree' => self::KEYWORD_FIELD,
81
            'categoryIds' => self::KEYWORD_FIELD,
82
            'propertyIds' => self::KEYWORD_FIELD,
83
            'optionIds' => self::KEYWORD_FIELD,
84
            'tagIds' => self::KEYWORD_FIELD,
85
            'autoIncrement' => self::INT_FIELD,
86
            'manufacturerId' => self::KEYWORD_FIELD,
87
            'manufacturerNumber' => self::getTextFieldConfig(),
88
            'displayGroup' => self::KEYWORD_FIELD,
89
            'ean' => self::getTextFieldConfig(),
90
            'height' => self::FLOAT_FIELD,
91
            'length' => self::FLOAT_FIELD,
92
            'markAsTopseller' => self::BOOLEAN_FIELD,
93
            'productNumber' => self::getTextFieldConfig(),
94
            'ratingAverage' => self::FLOAT_FIELD,
95
            'releaseDate' => ElasticsearchFieldBuilder::datetime(),
96
            'createdAt' => ElasticsearchFieldBuilder::datetime(),
97
            'sales' => self::INT_FIELD,
98
            'stock' => self::INT_FIELD,
99
            'availableStock' => self::INT_FIELD,
100
            'shippingFree' => self::BOOLEAN_FIELD,
101
            'taxId' => self::KEYWORD_FIELD,
102
            'tags' => ElasticsearchFieldBuilder::nested(['name' => self::getTextFieldConfig()]),
103
            'visibilities' => ElasticsearchFieldBuilder::nested([
104
                'id' => null,
105
                'salesChannelId' => self::KEYWORD_FIELD,
106
                'visibility' => self::INT_FIELD,
107
            ]),
108
            'coverId' => self::KEYWORD_FIELD,
109
            'weight' => self::FLOAT_FIELD,
110
            'width' => self::FLOAT_FIELD,
111
            'states' => self::KEYWORD_FIELD,
112
            'customFields' => $this->fieldBuilder->customFields($this->getEntityDefinition()->getEntityName(), $context),
113
        ];
114
115
        return [
116
            '_source' => ['includes' => ['id', 'autoIncrement']],
117
            'dynamic_templates' => [
118
                [
119
                    'cheapest_price' => [
120
                        'match' => 'cheapest_price_rule*',
121
                        'mapping' => ['type' => 'double'],
122
                    ],
123
                ],
124
                [
125
                    'price_percentage' => [
126
                        'path_match' => 'price.*.percentage.*',
127
                        'mapping' => ['type' => 'double'],
128
                    ],
129
                ],
130
                [
131
                    'long_to_double' => [
132
                        'match_mapping_type' => 'long',
133
                        'mapping' => ['type' => 'double'],
134
                    ],
135
                ],
136
            ],
137
            'properties' => $properties,
138
        ];
139
    }
140
141
    public function buildTermQuery(Context $context, Criteria $criteria): BoolQuery
142
    {
143
        return $this->searchQueryBuilder->build($criteria, $context);
144
    }
145
146
    /**
147
     * {@inheritDoc}
148
     *
149
     * @throws \JsonException
150
     */
151
    public function fetch(array $ids, Context $context): array
152
    {
153
        $data = $this->fetchProducts($ids, $context);
154
        $documents = [];
155
156
        $groupIds = [];
157
158
        foreach ($data as $row) {
159
            foreach (ElasticsearchIndexingUtils::parseJson($row, 'propertyIds') as $id) {
160
                $groupIds[(string) $id] = true;
161
            }
162
            foreach (ElasticsearchIndexingUtils::parseJson($row, 'optionIds') as $id) {
163
                $groupIds[(string) $id] = true;
164
            }
165
        }
166
167
        $groups = $this->fetchProperties(\array_keys($groupIds));
168
169
        foreach ($data as $id => $item) {
170
            $translationParent = ElasticsearchIndexingUtils::parseJson($item, 'translation_parent');
171
            $translation = ElasticsearchIndexingUtils::parseJson($item, 'translation');
172
173
            $documents[$id] = [
174
                'id' => $id,
175
                'autoIncrement' => (float) $item['autoIncrement'],
176
                'ratingAverage' => (float) $item['ratingAverage'],
177
                'active' => (bool) $item['active'],
178
                'available' => (bool) $item['available'],
179
                'isCloseout' => (bool) $item['isCloseout'],
180
                'shippingFree' => (bool) $item['shippingFree'],
181
                'markAsTopseller' => (bool) $item['markAsTopseller'],
182
                'visibilities' => array_map(function (array $visibility) {
183
                    return array_merge([
184
                        '_count' => 1,
185
                    ], $visibility);
186
                }, ElasticsearchIndexingUtils::parseJson($item, 'visibilities')),
187
                'availableStock' => (int) $item['availableStock'],
188
                'productNumber' => $item['productNumber'],
189
                'ean' => $item['ean'],
190
                'displayGroup' => $item['displayGroup'],
191
                'sales' => (int) $item['sales'],
192
                'stock' => (int) $item['stock'],
193
                'weight' => (float) $item['weight'],
194
                'width' => (float) $item['width'],
195
                'length' => (float) $item['length'],
196
                'height' => (float) $item['height'],
197
                'manufacturerId' => $item['productManufacturerId'],
198
                'manufacturerNumber' => $item['manufacturerNumber'],
199
                'releaseDate' => isset($item['releaseDate']) ? (new \DateTime($item['releaseDate']))->format('c') : null,
200
                'createdAt' => isset($item['createdAt']) ? (new \DateTime($item['createdAt']))->format('c') : null,
201
                'categoryTree' => ElasticsearchIndexingUtils::parseJson($item, 'categoryTree'),
202
                'categoriesRo' => array_values(array_map(fn (string $categoryId) => ['id' => $categoryId, '_count' => 1], ElasticsearchIndexingUtils::parseJson($item, 'categoryTree'))),
203
                'taxId' => $item['taxId'],
204
                'tags' => array_filter(array_map(function (array $tag) {
205
                    return empty($tag['id']) ? null : [
206
                        'id' => $tag['id'],
207
                        'name' => ElasticsearchIndexingUtils::stripText($tag['name'] ?? ''),
208
                        '_count' => 1,
209
                    ];
210
                }, ElasticsearchIndexingUtils::parseJson($item, 'tags'))),
211
                'parentId' => $item['parentId'],
212
                'coverId' => $item['coverId'],
213
                'childCount' => (int) $item['childCount'],
214
                'categories' => ElasticsearchFieldMapper::toManyAssociations(items: ElasticsearchIndexingUtils::parseJson($item, 'categories'), translatedFields: ['name']),
215
                'manufacturer' => [
216
                    'id' => $item['productManufacturerId'],
217
                    'name' => ElasticsearchFieldMapper::translated(field: 'name', items: ElasticsearchIndexingUtils::parseJson($item, 'manufacturer_translation')),
218
                    '_count' => 1,
219
                ],
220
                'properties' => array_values(array_map(function (string $propertyId) use ($groups) {
221
                    return array_merge([
222
                        'id' => $propertyId,
223
                        '_count' => 1,
224
                    ], $groups[$propertyId]);
225
                }, ElasticsearchIndexingUtils::parseJson($item, 'propertyIds'))),
226
                'options' => array_values(array_map(function (string $optionId) use ($groups) {
227
                    return array_merge([
228
                        'id' => $optionId,
229
                        '_count' => 1,
230
                    ], $groups[$optionId]);
231
                }, ElasticsearchIndexingUtils::parseJson($item, 'optionIds'))),
232
                'categoryIds' => ElasticsearchIndexingUtils::parseJson($item, 'categoryIds'),
233
                'optionIds' => ElasticsearchIndexingUtils::parseJson($item, 'optionIds'),
234
                'propertyIds' => ElasticsearchIndexingUtils::parseJson($item, 'propertyIds'),
235
                'tagIds' => ElasticsearchIndexingUtils::parseJson($item, 'tagIds'),
236
                'states' => ElasticsearchIndexingUtils::parseJson($item, 'states'),
237
                'customFields' => $this->mapCustomFields(
238
                    variantCustomFields: ElasticsearchFieldMapper::translated(field: 'customFields', items: $translation, stripText: false),
239
                    parentCustomFields: ElasticsearchFieldMapper::translated(field: 'customFields', items: $translationParent, stripText: false),
240
                    context: $context
241
                ),
242
                'name' => ElasticsearchFieldMapper::translated(field: 'name', items: $translation, fallbackItems: $translationParent),
243
                'description' => ElasticsearchFieldMapper::translated(field: 'description', items: $translation, fallbackItems: $translationParent),
244
                'metaTitle' => ElasticsearchFieldMapper::translated(field: 'metaTitle', items: $translation, fallbackItems: $translationParent),
245
                'metaDescription' => ElasticsearchFieldMapper::translated(field: 'metaDescription', items: $translation, fallbackItems: $translationParent),
246
                'customSearchKeywords' => ElasticsearchFieldMapper::translated(field: 'customSearchKeywords', items: $translation, fallbackItems: $translationParent),
247
                ...$this->mapCheapestPrice(ElasticsearchIndexingUtils::parseJson($item, 'cheapest_price_accessor')),
248
            ];
249
        }
250
251
        return $documents;
252
    }
253
254
    /**
255
     * @param array<string> $ids
256
     *
257
     * @throws Exception
258
     *
259
     * @return array<string, array<string, string>>
260
     */
261
    private function fetchProducts(array $ids, Context $context): array
262
    {
263
        $sql = <<<'SQL'
264
SELECT
265
    LOWER(HEX(p.id)) AS id,
266
    IFNULL(p.active, pp.active) AS active,
267
    p.available AS available,
268
    #tags#,
269
    #visibilities#,
270
    #translation#,
271
    #translation_parent#,
272
    #categories#,
273
    #manufacturer_translation#,
274
    IFNULL(p.manufacturer_number, pp.manufacturer_number) AS manufacturerNumber,
275
    IFNULL(p.available_stock, pp.available_stock) AS availableStock,
276
    IFNULL(p.rating_average, pp.rating_average) AS ratingAverage,
277
    p.product_number as productNumber,
278
    p.sales,
279
    LOWER(HEX(IFNULL(p.product_manufacturer_id, pp.product_manufacturer_id))) AS productManufacturerId,
280
    IFNULL(p.shipping_free, pp.shipping_free) AS shippingFree,
281
    IFNULL(p.is_closeout, pp.is_closeout) AS isCloseout,
282
    LOWER(HEX(IFNULL(p.product_media_id, pp.product_media_id))) AS coverId,
283
    IFNULL(p.weight, pp.weight) AS weight,
284
    IFNULL(p.length, pp.length) AS length,
285
    IFNULL(p.height, pp.height) AS height,
286
    IFNULL(p.width, pp.width) AS width,
287
    IFNULL(p.release_date, pp.release_date) AS releaseDate,
288
    IFNULL(p.created_at, pp.created_at) AS createdAt,
289
    IFNULL(p.category_tree, pp.category_tree) AS categoryTree,
290
    IFNULL(p.category_ids, pp.category_ids) AS categoryIds,
291
    IFNULL(p.option_ids, pp.option_ids) AS optionIds,
292
    IFNULL(p.property_ids, pp.property_ids) AS propertyIds,
293
    IFNULL(p.tag_ids, pp.tag_ids) AS tagIds,
294
    LOWER(HEX(IFNULL(p.tax_id, pp.tax_id))) AS taxId,
295
    IFNULL(p.stock, pp.stock) AS stock,
296
    IFNULL(p.ean, pp.ean) AS ean,
297
    IFNULL(p.mark_as_topseller, pp.mark_as_topseller) AS markAsTopseller,
298
    p.auto_increment as autoIncrement,
299
    p.display_group as displayGroup,
300
    IFNULL(p.cheapest_price_accessor, pp.cheapest_price_accessor) as cheapest_price_accessor,
301
    LOWER(HEX(p.parent_id)) as parentId,
302
    p.child_count as childCount,
303
    p.states
304
305
FROM product p
306
    LEFT JOIN product pp ON(p.parent_id = pp.id AND pp.version_id = :liveVersionId)
307
    LEFT JOIN product_visibility ON(product_visibility.product_id = p.visibilities AND product_visibility.product_version_id = p.version_id)
308
    LEFT JOIN product_translation product_main ON product_main.product_id = p.id AND product_main.product_version_id = p.version_id
309
    LEFT JOIN product_translation product_parent ON product_parent.product_id = p.parent_id AND product_parent.product_version_id = p.version_id
310
    LEFT JOIN product_manufacturer_translation ON product_manufacturer_translation.product_manufacturer_id = IFNULL(p.product_manufacturer_id, pp.product_manufacturer_id) AND product_manufacturer_translation.product_manufacturer_version_id = p.version_id
311
    LEFT JOIN product_tag ON (product_tag.product_id = p.tags AND product_tag.product_version_id = p.version_id)
312
    LEFT JOIN tag ON (tag.id = product_tag.tag_id)
313
    LEFT JOIN product_category ON (product_category.product_id = p.categories AND product_category.product_version_id = p.version_id)
314
    LEFT JOIN category_translation ON category_translation.category_id = product_category.category_id AND category_translation.category_version_id = product_category.category_version_id AND category_translation.name IS NOT NULL
315
316
WHERE p.id IN (:ids) AND p.version_id = :liveVersionId AND (p.child_count = 0 OR p.parent_id IS NOT NULL OR JSON_EXTRACT(`p`.`variant_listing_config`, "$.displayParent") = 1)
317
318
GROUP BY p.id
319
SQL;
320
321
        $mapping = [
322
            '#tags#' => SqlHelper::objectArray([
323
                'name' => 'tag.name',
324
                'id' => 'LOWER(HEX(tag.id))',
325
            ], 'tags'),
326
            '#visibilities#' => SqlHelper::objectArray([
327
                'visibility' => 'product_visibility.visibility',
328
                'salesChannelId' => 'LOWER(HEX(product_visibility.sales_channel_id))',
329
            ], 'visibilities'),
330
            '#translation#' => SqlHelper::objectArray([
331
                'languageId' => 'LOWER(HEX(product_main.language_id))',
332
                'name' => 'product_main.name',
333
                'description' => 'product_main.description',
334
                'metaTitle' => 'product_main.meta_title',
335
                'metaDescription' => 'product_main.meta_description',
336
                'customFields' => 'product_main.custom_fields',
337
                'customSearchKeywords' => 'product_main.custom_search_keywords',
338
            ], 'translation'),
339
            '#translation_parent#' => SqlHelper::objectArray([
340
                'languageId' => 'LOWER(HEX(product_parent.language_id))',
341
                'name' => 'product_parent.name',
342
                'description' => 'product_parent.description',
343
                'metaTitle' => 'product_parent.meta_title',
344
                'metaDescription' => 'product_parent.meta_description',
345
                'customFields' => 'product_parent.custom_fields',
346
                'customSearchKeywords' => 'product_parent.custom_search_keywords',
347
            ], 'translation_parent'),
348
            '#categories#' => SqlHelper::objectArray([
349
                'languageId' => 'LOWER(HEX(category_translation.language_id))',
350
                'id' => 'LOWER(HEX(category_translation.category_id))',
351
                'name' => 'category_translation.name',
352
            ], 'categories'),
353
            '#manufacturer_translation#' => SqlHelper::objectArray([
354
                'languageId' => 'LOWER(HEX(product_manufacturer_translation.language_id))',
355
                'id' => 'LOWER(HEX(product_manufacturer_translation.product_manufacturer_id))',
356
                'name' => 'product_manufacturer_translation.name',
357
            ], 'manufacturer_translation'),
358
        ];
359
360
        /** @var string $sql */
361
        $sql = str_replace(array_keys($mapping), array_values($mapping), $sql);
362
        /** @var array<string, array<string, string>> $result */
363
        $result = $this->connection->fetchAllAssociativeIndexed(
364
            $sql,
365
            [
366
                'ids' => $ids,
367
                'liveVersionId' => Uuid::fromHexToBytes($context->getVersionId()),
368
            ],
369
            [
370
                'ids' => ArrayParameterType::STRING,
371
            ]
372
        );
373
374
        return $result;
375
    }
376
377
    /**
378
     * @param list<string> $propertyIds
0 ignored issues
show
Bug introduced by
The type Shopware\Elasticsearch\Product\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
379
     *
380
     * @return array<string, array{id: string, groupId: string, translations?: string, name: array<int|string, string>}>
381
     */
382
    private function fetchProperties(array $propertyIds): array
383
    {
384
        if (empty($propertyIds)) {
385
            return [];
386
        }
387
388
        $sql = <<<'SQL'
389
SELECT
390
       LOWER(HEX(id)) as id,
391
       LOWER(HEX(property_group_id)) as groupId,
392
       #translations#
393
FROM property_group_option
394
         LEFT JOIN property_group_option_translation
395
            ON property_group_option_translation.property_group_option_id = property_group_option.id
396
397
WHERE property_group_option.id in (:ids)
398
GROUP BY property_group_option.id
399
SQL;
400
401
        /** @var array<string, array{id: string, groupId: string, translations: string}> $options */
402
        $options = $this->connection->fetchAllAssociativeIndexed(
403
            str_replace(
404
                '#translations#',
405
                SqlHelper::objectArray([
406
                    'languageId' => 'LOWER(HEX(property_group_option_translation.language_id))',
407
                    'name' => 'property_group_option_translation.name',
408
                ], 'translations'),
409
                $sql
410
            ),
411
            [
412
                'ids' => Uuid::fromHexToBytesList($propertyIds),
413
            ],
414
            [
415
                'ids' => ArrayParameterType::STRING,
416
            ]
417
        );
418
419
        foreach ($options as $optionId => $option) {
420
            $translation = ElasticsearchIndexingUtils::parseJson($option, 'translations');
421
422
            $options[$optionId]['name'] = $option['name'] ?? [];
423
            foreach ($translation as $item) {
424
                $options[$optionId]['name'][$item['languageId']] = ElasticsearchIndexingUtils::stripText($item['name'] ?? '');
425
            }
426
427
            unset($options[$optionId]['translations']);
428
        }
429
430
        return $options;
431
    }
432
433
    /**
434
     * @param array<string, array<string, array{gross: float, net: float, percentage: array{gross: float, net: float}}>> $cheapestPriceAccessor
435
     *
436
     * @return array<string, float>
437
     */
438
    private function mapCheapestPrice(array $cheapestPriceAccessor): array
439
    {
440
        $mapped = [];
441
442
        foreach ($cheapestPriceAccessor as $rule => $cheapestPriceCurrencies) {
443
            foreach ($cheapestPriceCurrencies as $currency => $taxes) {
444
                $key = 'cheapest_price_' . $rule . '_' . $currency . '_gross';
445
                $mapped[$key] = $taxes['gross'];
446
447
                $key = 'cheapest_price_' . $rule . '_' . $currency . '_net';
448
                $mapped[$key] = $taxes['net'];
449
450
                if (empty($taxes['percentage'])) {
451
                    continue;
452
                }
453
454
                $key = 'cheapest_price_' . $rule . '_' . $currency . '_gross_percentage';
455
                $mapped[$key] = $taxes['percentage']['gross'];
456
457
                $key = 'cheapest_price_' . $rule . '_' . $currency . '_net_percentage';
458
                $mapped[$key] = $taxes['percentage']['net'];
459
            }
460
        }
461
462
        return $mapped;
463
    }
464
465
    /**
466
     * @param array<string, mixed> $variantCustomFields
467
     * @param array<string, mixed> $parentCustomFields
468
     *
469
     * @throws \JsonException
470
     *
471
     * @return array<string, array<string, mixed>>
472
     */
473
    private function mapCustomFields(array $variantCustomFields, array $parentCustomFields, Context $context): array
474
    {
475
        $customFields = [];
476
477
        $customFieldsLanguageIds = array_unique(array_merge(array_keys($parentCustomFields), array_keys($variantCustomFields)));
478
479
        foreach ($customFieldsLanguageIds as $languageId) {
480
            $merged = [];
481
482
            $chains = [
483
                $parentCustomFields[$languageId] ?? [],
484
                $variantCustomFields[$languageId] ?? [],
485
            ];
486
487
            /** @var array<mixed>|string $chain */
488
            foreach ($chains as $chain) {
489
                // chain is empty string, when no custom fields are set
490
                if ($chain === '') {
491
                    $chain = [];
492
                }
493
494
                if (\is_string($chain)) {
495
                    $chain = json_decode($chain, true, 512, \JSON_THROW_ON_ERROR);
496
                }
497
498
                foreach ($chain as $k => $v) {
499
                    if ($v === null) {
500
                        continue;
501
                    }
502
503
                    $merged[$k] = $v;
504
                }
505
            }
506
507
            $customFields[$languageId] = $merged;
508
        }
509
510
        return $this->fieldMapper->customFields(ProductDefinition::ENTITY_NAME, $customFields, $context);
511
    }
512
}
513