@@ -68,7 +68,9 @@ class DbalProductValueChangedEventProjector
             $this->insert($productId, $attributeId, $phrase);
         } elseif ($value instanceof StringCollectionValue) {
             foreach ($value->getValue() as $language => $phrase) {
-                $this->insert($productId, $attributeId, $phrase, $language);
+                if ($phrase) {
+                    $this->insert($productId, $attributeId, $phrase, $language);
+                }
             }
         } elseif ($value instanceof TranslatableStringValue) {
             $translation = $value->getValue();
<?php

/**
 * Copyright © Bold Brand Commerce Sp. z o.o. All rights reserved.
 * See LICENSE.txt for license details.
 */

declare(strict_types = 1);

namespace Ergonode\Product\Infrastructure\Persistence\Projector;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Ergonode\Product\Domain\Event\ProductValueChangedEvent;
use Ergonode\SharedKernel\Domain\Aggregate\AttributeId;
use Ergonode\Value\Domain\ValueObject\StringCollectionValue;
use Ergonode\Value\Domain\ValueObject\StringValue;
use Ergonode\Value\Domain\ValueObject\TranslatableStringValue;
use Ergonode\Value\Domain\ValueObject\ValueInterface;
use Ramsey\Uuid\Uuid;

/**
 */
class DbalProductValueChangedEventProjector
{
    private const TABLE_PRODUCT_VALUE = 'product_value';
    private const TABLE_VALUE_TRANSLATION = 'value_translation';

    /**
     * @var Connection
     */
    private Connection $connection;

    /**
     * @param Connection $connection
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * @param ProductValueChangedEvent $event
     *
     * @throws DBALException
     */
    public function __invoke(ProductValueChangedEvent $event): void
    {
        $productId = $event->getAggregateId()->getValue();
        $attributeId = AttributeId::fromKey($event->getAttributeCode()->getValue())->getValue();

        $this->delete($productId, $attributeId);
        $this->insertValue($productId, $attributeId, $event->getTo());
    }

    /**
     * @param string         $productId
     * @param string         $attributeId
     * @param ValueInterface $value
     *
     * @throws DBALException
     */
    private function insertValue(string $productId, string $attributeId, ValueInterface $value): void
    {
        if ($value instanceof StringValue) {
            $array = $value->getValue();
            $phrase = reset($array);
            $this->insert($productId, $attributeId, $phrase);
        } elseif ($value instanceof StringCollectionValue) {
            foreach ($value->getValue() as $language => $phrase) {
                $this->insert($productId, $attributeId, $phrase, $language);
            }
        } elseif ($value instanceof TranslatableStringValue) {
            $translation = $value->getValue();
            foreach ($translation as $language => $phrase) {
                $this->insert($productId, $attributeId, $phrase, $language);
            }
        } else {
            throw new \RuntimeException(
                sprintf(sprintf('Unknown Value class "%s"', \get_class($value->getValue())))
            );
        }
    }

    /**
     * @param string      $productId
     * @param string      $attributeId
     * @param string|null $value
     * @param string|null $language
     *
     * @throws DBALException
     */
    private function insert(string $productId, string $attributeId, ?string $value, ?string $language = null): void
    {
        $valueId = Uuid::uuid5(ValueInterface::NAMESPACE, implode('|', [$value, $language]));

        $qb = $this->connection->createQueryBuilder();
        $result = $qb->select('*')
            ->from(self::TABLE_VALUE_TRANSLATION)
            ->where($qb->expr()->eq('id', ':id'))
            ->setParameter(':id', $valueId->toString())
            ->execute()
            ->fetch();

        if (false === $result) {
            $this->connection->executeQuery(
                'INSERT INTO value_translation (id, value_id, value, language) '.
                ' VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING',
                [$valueId->toString(), $valueId->toString(), $value, $language ?: null]
            );
        }

        $this->connection->insert(
            self::TABLE_PRODUCT_VALUE,
            [
                'product_id' => $productId,
                'attribute_id' => $attributeId,
                'value_id' => $valueId,
            ]
        );
    }

    /**
     * @param string $productId
     * @param string $attributeId
     *
     * @throws DBALException
     */
    private function delete(string $productId, string $attributeId): void
    {
        $this->connection->delete(
            self::TABLE_PRODUCT_VALUE,
            [
                'product_id' => $productId,
                'attribute_id' => $attributeId,
            ]
        );
    }
}

This is not a solution, this only ensures that an empty value is not saved in this projector. Empty value will remain in the domain object and will appear in other projectors based on this event.