vendor/shopware/core/Checkout/Shipping/Validator/ShippingMethodValidator.php line 48

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Checkout\Shipping\Validator;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Checkout\Shipping\ShippingMethodDefinition;
  5. use Shopware\Core\Checkout\Shipping\ShippingMethodEntity;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  9. use Shopware\Core\Framework\Log\Package;
  10. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\Validator\ConstraintViolation;
  13. use Symfony\Component\Validator\ConstraintViolationInterface;
  14. use Symfony\Component\Validator\ConstraintViolationList;
  15. /**
  16. * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  17. */
  18. #[Package('checkout')]
  19. class ShippingMethodValidator implements EventSubscriberInterface
  20. {
  21. public const VIOLATION_TAX_TYPE_INVALID = 'tax_type_invalid';
  22. public const VIOLATION_TAX_ID_REQUIRED = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
  23. private Connection $connection;
  24. /**
  25. * @internal
  26. */
  27. public function __construct(Connection $connection)
  28. {
  29. $this->connection = $connection;
  30. }
  31. /**
  32. * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  33. */
  34. public static function getSubscribedEvents()
  35. {
  36. return [
  37. PreWriteValidationEvent::class => 'preValidate',
  38. ];
  39. }
  40. public function preValidate(PreWriteValidationEvent $event): void
  41. {
  42. $allowTypes = [
  43. ShippingMethodEntity::TAX_TYPE_FIXED,
  44. ShippingMethodEntity::TAX_TYPE_AUTO,
  45. ShippingMethodEntity::TAX_TYPE_HIGHEST,
  46. ];
  47. $writeCommands = $event->getCommands();
  48. foreach ($writeCommands as $command) {
  49. $violations = new ConstraintViolationList();
  50. if (!$command instanceof InsertCommand && !$command instanceof UpdateCommand) {
  51. continue;
  52. }
  53. if ($command->getDefinition()->getClass() !== ShippingMethodDefinition::class) {
  54. continue;
  55. }
  56. $shippingMethod = $this->findShippingMethod($command->getPrimaryKey()['id']);
  57. $payload = $command->getPayload();
  58. /** @var string|null $taxType */
  59. $taxType = $this->getValue($payload, 'tax_type', $shippingMethod);
  60. /** @var string|null $taxId */
  61. $taxId = $this->getValue($payload, 'tax_id', $shippingMethod);
  62. if ($taxType && !\in_array($taxType, $allowTypes, true)) {
  63. $violations->add(
  64. $this->buildViolation(
  65. 'The selected tax type {{ type }} is invalid.',
  66. ['{{ type }}' => $taxType],
  67. '/taxType',
  68. $taxType,
  69. self::VIOLATION_TAX_TYPE_INVALID
  70. )
  71. );
  72. }
  73. if ($taxType === ShippingMethodEntity::TAX_TYPE_FIXED && !$taxId) {
  74. $violations->add(
  75. $this->buildViolation(
  76. 'The defined tax rate is required when fixed tax present',
  77. ['{{ taxId }}' => null],
  78. '/taxId',
  79. $taxType,
  80. self::VIOLATION_TAX_ID_REQUIRED
  81. )
  82. );
  83. }
  84. if ($violations->count() > 0) {
  85. $event->getExceptions()->add(new WriteConstraintViolationException($violations, $command->getPath()));
  86. }
  87. }
  88. }
  89. /**
  90. * @return array<string, mixed>
  91. */
  92. private function findShippingMethod(string $shippingMethodId): array
  93. {
  94. $shippingMethod = $this->connection->executeQuery(
  95. 'SELECT `tax_type`, `tax_id` FROM `shipping_method` WHERE `id` = :id',
  96. ['id' => $shippingMethodId]
  97. );
  98. return $shippingMethod->fetchAssociative() ?: [];
  99. }
  100. /**
  101. * @param array<string, mixed> $parameters
  102. */
  103. private function buildViolation(
  104. string $messageTemplate,
  105. array $parameters,
  106. string $propertyPath,
  107. string $invalidValue,
  108. string $code
  109. ): ConstraintViolationInterface {
  110. return new ConstraintViolation(
  111. str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  112. $messageTemplate,
  113. $parameters,
  114. null,
  115. $propertyPath,
  116. $invalidValue,
  117. null,
  118. $code
  119. );
  120. }
  121. /**
  122. * Gets a value from an array. It also does clean checks if
  123. * the key is set, and also provides the option for default values.
  124. *
  125. * @param array<string, mixed> $data the data array
  126. * @param string $key the requested key in the array
  127. * @param array<string, mixed> $dbRow the db row of from the database
  128. *
  129. * @return mixed the object found in the key, or the default value
  130. */
  131. private function getValue(array $data, string $key, array $dbRow)
  132. {
  133. // try in our actual data set
  134. if (isset($data[$key])) {
  135. return $data[$key];
  136. }
  137. // try in our db row fallback
  138. if (isset($dbRow[$key])) {
  139. return $dbRow[$key];
  140. }
  141. // use default
  142. return null;
  143. }
  144. }