src/Elasticsearch/Product/CustomFieldUpdater.php line 37

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Elasticsearch\Product;
  3. use OpenSearch\Client;
  4. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  5. use Shopware\Core\Framework\Log\Package;
  6. use Shopware\Core\System\CustomField\CustomFieldDefinition;
  7. use Shopware\Core\System\CustomField\CustomFieldTypes;
  8. use Shopware\Elasticsearch\Framework\ElasticsearchHelper;
  9. use Shopware\Elasticsearch\Framework\ElasticsearchOutdatedIndexDetector;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. /**
  12.  * @internal
  13.  */
  14. #[Package('core')]
  15. class CustomFieldUpdater implements EventSubscriberInterface
  16. {
  17.     /**
  18.      * @internal
  19.      */
  20.     public function __construct(
  21.         private readonly ElasticsearchOutdatedIndexDetector $indexDetector,
  22.         private readonly Client $client,
  23.         private readonly ElasticsearchHelper $elasticsearchHelper
  24.     ) {
  25.     }
  26.     public static function getSubscribedEvents(): array
  27.     {
  28.         return [
  29.             EntityWrittenContainerEvent::class => 'onNewCustomFieldCreated',
  30.         ];
  31.     }
  32.     public function onNewCustomFieldCreated(EntityWrittenContainerEvent $containerEvent): void
  33.     {
  34.         $event $containerEvent->getEventByEntityName(CustomFieldDefinition::ENTITY_NAME);
  35.         if ($event === null) {
  36.             return;
  37.         }
  38.         if (!$this->elasticsearchHelper->allowIndexing()) {
  39.             return;
  40.         }
  41.         $newCreatedFields = [];
  42.         foreach ($event->getWriteResults() as $writeResult) {
  43.             $existence $writeResult->getExistence();
  44.             if ($existence && $existence->exists()) {
  45.                 continue;
  46.             }
  47.             /** @var array<mixed> $esType */
  48.             $esType self::getTypeFromCustomFieldType($writeResult->getProperty('type'));
  49.             $newCreatedFields[(string) $writeResult->getProperty('name')] = $esType;
  50.         }
  51.         if (\count($newCreatedFields) === 0) {
  52.             return;
  53.         }
  54.         $this->createNewFieldsInIndices($newCreatedFields);
  55.     }
  56.     /**
  57.      * @return array<mixed>
  58.      */
  59.     public static function getTypeFromCustomFieldType(string $type): array
  60.     {
  61.         return match ($type) {
  62.             CustomFieldTypes::INT => [
  63.                 'type' => 'long',
  64.             ],
  65.             CustomFieldTypes::FLOAT => [
  66.                 'type' => 'double',
  67.             ],
  68.             CustomFieldTypes::BOOL => [
  69.                 'type' => 'boolean',
  70.             ],
  71.             CustomFieldTypes::DATETIME => [
  72.                 'type' => 'date',
  73.                 'format' => 'yyyy-MM-dd HH:mm:ss.000',
  74.                 'ignore_malformed' => true,
  75.             ],
  76.             CustomFieldTypes::PRICECustomFieldTypes::JSON => [
  77.                 'type' => 'object',
  78.                 'dynamic' => true,
  79.             ],
  80.             CustomFieldTypes::HTMLCustomFieldTypes::TEXT => [
  81.                 'type' => 'text',
  82.             ],
  83.             default => [
  84.                 'type' => 'keyword',
  85.             ],
  86.         };
  87.     }
  88.     /**
  89.      * @param array<string, array<mixed>> $newCreatedFields
  90.      */
  91.     private function createNewFieldsInIndices(array $newCreatedFields): void
  92.     {
  93.         $indices $this->indexDetector->getAllUsedIndices();
  94.         foreach ($indices as $indexName) {
  95.             $body = [
  96.                 'properties' => [
  97.                     'customFields' => [
  98.                         'properties' => $newCreatedFields,
  99.                     ],
  100.                 ],
  101.             ];
  102.             // For some reason, we need to include the includes to prevent merge conflicts.
  103.             // This error can happen for example after updating from version <6.4.
  104.             $current $this->client->indices()->get(['index' => $indexName]);
  105.             $includes $current[$indexName]['mappings']['_source']['includes'] ?? [];
  106.             if ($includes !== []) {
  107.                 $body['_source'] = [
  108.                     'includes' => $includes,
  109.                 ];
  110.             }
  111.             $this->client->indices()->putMapping([
  112.                 'index' => $indexName,
  113.                 'body' => $body,
  114.             ]);
  115.         }
  116.     }
  117. }