custom/plugins/PickwareErpStarter/src/StockApi/Subscriber/ProductStockSubscriber.php line 92

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Pickware GmbH. All rights reserved.
  4.  * This file is part of software that is released under a proprietary license.
  5.  * You must not copy, modify, distribute, make publicly available, or execute
  6.  * its contents or parts thereof without express permission by the copyright
  7.  * holder, unless otherwise permitted by law.
  8.  */
  9. declare(strict_types=1);
  10. namespace Pickware\PickwareErpStarter\StockApi\Subscriber;
  11. use Pickware\DalBundle\EntityPreWriteValidationEvent;
  12. use Pickware\DalBundle\EntityPreWriteValidationEventDispatcher;
  13. use Pickware\PickwareErpStarter\Config\Config;
  14. use Pickware\PickwareErpStarter\StockApi\StockLocationReference;
  15. use Pickware\PickwareErpStarter\StockApi\TotalStockWriter;
  16. use Shopware\Core\Content\Product\ProductDefinition;
  17. use Shopware\Core\Content\Product\ProductEvents;
  18. use Shopware\Core\Defaults;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  21. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  22. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  23. use Symfony\Component\Validator\ConstraintViolation;
  24. use Symfony\Component\Validator\ConstraintViolationList;
  25. /**
  26.  * Listens to changes made to the field "stock" of a product and initiates the corresponding absolute stock change.
  27.  */
  28. class ProductStockSubscriber implements EventSubscriberInterface
  29. {
  30.     private Config $config;
  31.     private TotalStockWriter $totalStockWriter;
  32.     public function __construct(Config $configTotalStockWriter $totalStockWriter)
  33.     {
  34.         $this->config $config;
  35.         $this->totalStockWriter $totalStockWriter;
  36.     }
  37.     public static function getSubscribedEvents(): array
  38.     {
  39.         return [
  40.             ProductEvents::PRODUCT_WRITTEN_EVENT => 'afterProductWritten',
  41.             // ProductEvents::PRODUCT_WRITTEN_EVENT is triggered very late in a write operation an therefore is not in
  42.             // the transaction anymore. If the stock of a product is set to a negative value, the TotalStockWriter would
  43.             // throw an exception, but since this is not happening in the transaction anymore, the changes were already
  44.             // written to the database and won't be reverted. Instead, negative product stock is avoided by adding a
  45.             // pre-write-validation.
  46.             EntityPreWriteValidationEventDispatcher::getEventName(ProductDefinition::ENTITY_NAME) => 'preWriteValidation',
  47.         ];
  48.     }
  49.     public function preWriteValidation($event): void
  50.     {
  51.         if (!($event instanceof EntityPreWriteValidationEvent)) {
  52.             // The subscriber is probably instantiated in its old version (with the Shopware PreWriteValidationEvent) in
  53.             // the container and will be updated on the next container rebuild (next request). Early return.
  54.             return;
  55.         }
  56.         // Filter out all WriteCommand for products that will set the stock to a negative value
  57.         $invalidWriteCommands array_filter($event->getCommands(), function (WriteCommand $writeCommand) {
  58.             $payload $writeCommand->getPayload();
  59.             return isset($payload['stock']) && $payload['stock'] < 0;
  60.         });
  61.         if (count($invalidWriteCommands) === 0) {
  62.             return;
  63.         }
  64.         // Add violations for that WriteCommands to the Event
  65.         $violations = new ConstraintViolationList();
  66.         foreach ($invalidWriteCommands as $invalidWriteCommand) {
  67.             $message 'The value for property "stock" is not allowed to be lower than 0.';
  68.             $violation = new ConstraintViolation(
  69.                 $message// $message
  70.                 $message// $messageTemplate,
  71.                 [], // $parameters,
  72.                 null// $root
  73.                 $invalidWriteCommand->getPath() . '/stock'// $propertyPath
  74.                 $invalidWriteCommand->getPayload()['stock'], // $invalidValue
  75.             );
  76.             $violations->add($violation);
  77.         }
  78.         $event->addViolation(new WriteConstraintViolationException($violations));
  79.     }
  80.     public function afterProductWritten(EntityWrittenEvent $event): void
  81.     {
  82.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  83.             return;
  84.         }
  85.         if (!$this->config->isStockInitialized()) {
  86.             return;
  87.         }
  88.         $writeResults $event->getWriteResults();
  89.         $newProductStocks = [];
  90.         $existingProductStocks = [];
  91.         foreach ($writeResults as $writeResult) {
  92.             $payload $writeResult->getPayload();
  93.             // Filter out instances of EntityWriteResult with empty payload. Somehow they are introduced by a bug in
  94.             // the Shopware DAL.
  95.             if (count($payload) === 0) {
  96.                 continue;
  97.             }
  98.             if ($payload['versionId'] !== Defaults::LIVE_VERSION) {
  99.                 continue;
  100.             }
  101.             if (!array_key_exists('stock'$payload)) {
  102.                 continue;
  103.             }
  104.             $isNewProduct $writeResult->getExistence() && !$writeResult->getExistence()->exists();
  105.             $productId $payload['id'];
  106.             if ($isNewProduct) {
  107.                 $newProductStocks[$productId] = $payload['stock'];
  108.             } else {
  109.                 $existingProductStocks[$productId] = $payload['stock'];
  110.             }
  111.         }
  112.         if (count($existingProductStocks) > 0) {
  113.             $this->totalStockWriter->setTotalStockForProducts(
  114.                 $existingProductStocks,
  115.                 StockLocationReference::productTotalStockChange(),
  116.                 $event->getContext(),
  117.             );
  118.         }
  119.         if (count($newProductStocks) > 0) {
  120.             $this->totalStockWriter->setTotalStockForProducts(
  121.                 $newProductStocks,
  122.                 StockLocationReference::initialization(),
  123.                 $event->getContext(),
  124.             );
  125.         }
  126.     }
  127. }