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

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