src/Core/Checkout/Shipping/Validator/ShippingMethodValidator.php line 45

  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.  * @internal
  17.  */
  18. #[Package('checkout')]
  19. class ShippingMethodValidator implements EventSubscriberInterface
  20. {
  21.     final public const VIOLATION_TAX_TYPE_INVALID 'tax_type_invalid';
  22.     final public const VIOLATION_TAX_ID_REQUIRED 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
  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.         $allowTypes = [
  41.             ShippingMethodEntity::TAX_TYPE_FIXED,
  42.             ShippingMethodEntity::TAX_TYPE_AUTO,
  43.             ShippingMethodEntity::TAX_TYPE_HIGHEST,
  44.         ];
  45.         $writeCommands $event->getCommands();
  46.         foreach ($writeCommands as $command) {
  47.             $violations = new ConstraintViolationList();
  48.             if (!$command instanceof InsertCommand && !$command instanceof UpdateCommand) {
  49.                 continue;
  50.             }
  51.             if ($command->getDefinition()->getClass() !== ShippingMethodDefinition::class) {
  52.                 continue;
  53.             }
  54.             $shippingMethod $this->findShippingMethod($command->getPrimaryKey()['id']);
  55.             $payload $command->getPayload();
  56.             /** @var string|null $taxType */
  57.             $taxType $this->getValue($payload'tax_type'$shippingMethod);
  58.             /** @var string|null $taxId */
  59.             $taxId $this->getValue($payload'tax_id'$shippingMethod);
  60.             if ($taxType && !\in_array($taxType$allowTypestrue)) {
  61.                 $violations->add(
  62.                     $this->buildViolation(
  63.                         'The selected tax type {{ type }} is invalid.',
  64.                         ['{{ type }}' => $taxType],
  65.                         '/taxType',
  66.                         $taxType,
  67.                         self::VIOLATION_TAX_TYPE_INVALID
  68.                     )
  69.                 );
  70.             }
  71.             if ($taxType === ShippingMethodEntity::TAX_TYPE_FIXED && !$taxId) {
  72.                 $violations->add(
  73.                     $this->buildViolation(
  74.                         'The defined tax rate is required when fixed tax present',
  75.                         ['{{ taxId }}' => null],
  76.                         '/taxId',
  77.                         $taxType,
  78.                         self::VIOLATION_TAX_ID_REQUIRED
  79.                     )
  80.                 );
  81.             }
  82.             if ($violations->count() > 0) {
  83.                 $event->getExceptions()->add(new WriteConstraintViolationException($violations$command->getPath()));
  84.             }
  85.         }
  86.     }
  87.     /**
  88.      * @return array<string, mixed>
  89.      */
  90.     private function findShippingMethod(string $shippingMethodId): array
  91.     {
  92.         $shippingMethod $this->connection->executeQuery(
  93.             'SELECT `tax_type`, `tax_id` FROM `shipping_method` WHERE `id` = :id',
  94.             ['id' => $shippingMethodId]
  95.         );
  96.         return $shippingMethod->fetchAssociative() ?: [];
  97.     }
  98.     /**
  99.      * @param array<string, mixed> $parameters
  100.      */
  101.     private function buildViolation(
  102.         string $messageTemplate,
  103.         array $parameters,
  104.         string $propertyPath,
  105.         string $invalidValue,
  106.         string $code
  107.     ): ConstraintViolationInterface {
  108.         return new ConstraintViolation(
  109.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  110.             $messageTemplate,
  111.             $parameters,
  112.             null,
  113.             $propertyPath,
  114.             $invalidValue,
  115.             null,
  116.             $code
  117.         );
  118.     }
  119.     /**
  120.      * Gets a value from an array. It also does clean checks if
  121.      * the key is set, and also provides the option for default values.
  122.      *
  123.      * @param array<string, mixed> $data  the data array
  124.      * @param string               $key   the requested key in the array
  125.      * @param array<string, mixed> $dbRow the db row of from the database
  126.      *
  127.      * @return mixed the object found in the key, or the default value
  128.      */
  129.     private function getValue(array $datastring $key, array $dbRow)
  130.     {
  131.         // try in our actual data set
  132.         if (isset($data[$key])) {
  133.             return $data[$key];
  134.         }
  135.         // try in our db row fallback
  136.         if (isset($dbRow[$key])) {
  137.             return $dbRow[$key];
  138.         }
  139.         // use default
  140.         return null;
  141.     }
  142. }