vendor/shopware/core/Content/Product/Cart/ProductLineItemCommandValidator.php line 43

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\Cart;
  3. use Doctrine\DBAL\ArrayParameterType;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  6. use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemDefinition;
  7. use Shopware\Core\Content\Product\Exception\ProductLineItemDifferentIdException;
  8. use Shopware\Core\Content\Product\Exception\ProductLineItemInconsistentException;
  9. use Shopware\Core\Defaults;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\SetNullOnDeleteCommand;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  14. use Shopware\Core\Framework\Log\Package;
  15. use Shopware\Core\Framework\Uuid\Uuid;
  16. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  17. /**
  18.  * @internal
  19.  */
  20. #[Package('inventory')]
  21. class ProductLineItemCommandValidator implements EventSubscriberInterface
  22. {
  23.     /**
  24.      * @internal
  25.      */
  26.     public function __construct(private readonly Connection $connection)
  27.     {
  28.     }
  29.     /**
  30.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  31.      */
  32.     public static function getSubscribedEvents(): array
  33.     {
  34.         return [
  35.             PreWriteValidationEvent::class => 'preValidate',
  36.         ];
  37.     }
  38.     public function preValidate(PreWriteValidationEvent $event): void
  39.     {
  40.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  41.             return;
  42.         }
  43.         $products $this->findProducts($event->getCommands());
  44.         foreach ($event->getCommands() as $command) {
  45.             if ($command->getDefinition()->getClass() !== OrderLineItemDefinition::class) {
  46.                 continue;
  47.             }
  48.             if ($command instanceof SetNullOnDeleteCommand) {
  49.                 continue;
  50.             }
  51.             $payload $command->getPayload();
  52.             $lineItemId Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
  53.             $productIdChanged \array_key_exists('product_id'$payload);
  54.             $referenceIdChanged \array_key_exists('referenced_id'$payload);
  55.             $lineItemPayload = isset($payload['payload']) ? json_decode((string) $payload['payload'], true512\JSON_THROW_ON_ERROR) : [];
  56.             $orderNumberChanged \array_key_exists('productNumber'$lineItemPayload);
  57.             if (!$this->isProduct($products$payload$lineItemId)) {
  58.                 continue;
  59.             }
  60.             $somethingChanged $productIdChanged || $referenceIdChanged || $orderNumberChanged;
  61.             $allChanged $productIdChanged && $referenceIdChanged && $orderNumberChanged;
  62.             // has a field changed?
  63.             if (!$somethingChanged) {
  64.                 continue;
  65.             }
  66.             $productId = isset($payload['product_id']) ? Uuid::fromBytesToHex($payload['product_id']) : null;
  67.             $referenceId $payload['referenced_id'] ?? null;
  68.             if ($productId !== $referenceId) {
  69.                 $event->getExceptions()->add(
  70.                     new ProductLineItemDifferentIdException($lineItemId)
  71.                 );
  72.             }
  73.             // all fields updated? everything is consistent
  74.             if ($allChanged) {
  75.                 continue;
  76.             }
  77.             $event->getExceptions()->add(
  78.                 new ProductLineItemInconsistentException($lineItemId)
  79.             );
  80.         }
  81.     }
  82.     /**
  83.      * @param list<WriteCommand> $commands
  84.      *
  85.      * @return array<string, int>
  86.      */
  87.     private function findProducts(array $commands): array
  88.     {
  89.         $ids array_map(function (WriteCommand $command) {
  90.             if ($command->getDefinition()->getClass() !== OrderLineItemDefinition::class) {
  91.                 return null;
  92.             }
  93.             if ($command instanceof UpdateCommand) {
  94.                 return $command->getPrimaryKey()['id'];
  95.             }
  96.             return null;
  97.         }, $commands);
  98.         $ids array_values(array_filter($ids));
  99.         if (empty($ids)) {
  100.             return [];
  101.         }
  102.         /** @var array<string, int> $products */
  103.         $products \array_flip($this->connection->fetchFirstColumn(
  104.             'SELECT DISTINCT LOWER(HEX(id)) FROM order_line_item WHERE id IN (:ids) AND type = \'product\'',
  105.             ['ids' => $ids],
  106.             ['ids' => ArrayParameterType::STRING]
  107.         ));
  108.         return $products;
  109.     }
  110.     /**
  111.      * @param array<string, mixed> $products
  112.      * @param array<string, mixed> $payload
  113.      */
  114.     private function isProduct(array $products, array $payloadstring $lineItemId): bool
  115.     {
  116.         if (isset($payload['type'])) {
  117.             return $payload['type'] === LineItem::PRODUCT_LINE_ITEM_TYPE;
  118.         }
  119.         return isset($products[$lineItemId]);
  120.     }
  121. }