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

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\PickwareErpStarter\Config\Config;
  12. use Pickware\PickwareErpStarter\StockApi\StockLocationReference;
  13. use Pickware\PickwareErpStarter\StockApi\TotalStockWriter;
  14. use Shopware\Core\Content\Product\ProductDefinition;
  15. use Shopware\Core\Content\Product\ProductEvents;
  16. use Shopware\Core\Defaults;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  20. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  21. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  22. use Symfony\Component\Validator\ConstraintViolation;
  23. use Symfony\Component\Validator\ConstraintViolationList;
  24. /**
  25.  * Listens to changes made to the field "stock" of a product and initiates the corresponding absolute stock change.
  26.  */
  27. class ProductStockSubscriber implements EventSubscriberInterface
  28. {
  29.     /**
  30.      * @var Config
  31.      */
  32.     private $config;
  33.     /**
  34.      * @var TotalStockWriter
  35.      */
  36.     private $totalStockWriter;
  37.     public function __construct(Config $configTotalStockWriter $totalStockWriter)
  38.     {
  39.         $this->config $config;
  40.         $this->totalStockWriter $totalStockWriter;
  41.     }
  42.     public static function getSubscribedEvents(): array
  43.     {
  44.         return [
  45.             ProductEvents::PRODUCT_WRITTEN_EVENT => 'afterProductWritten',
  46.             // ProductEvents::PRODUCT_WRITTEN_EVENT is triggered very late in a write operation an therefore is not in
  47.             // the transaction anymore. If the stock of a product is set to a negative value, the TotalStockWriter would
  48.             // throw an exception, but since this is not happening in the transaction anymore, the changes were already
  49.             // written to the database and won't be reverted. Instead negative product stock is avoided by adding a
  50.             // pre-write-validation.
  51.             PreWriteValidationEvent::class => 'preWriteValidation',
  52.         ];
  53.     }
  54.     public function preWriteValidation(PreWriteValidationEvent $event): void
  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.             if ($writeCommand->getDefinition()->getClass() !== ProductDefinition::class) {
  59.                 return false;
  60.             }
  61.             $payload $writeCommand->getPayload();
  62.             return isset($payload['stock']) && $payload['stock'] < 0;
  63.         });
  64.         if (count($invalidWriteCommands) === 0) {
  65.             return;
  66.         }
  67.         // Add violations for that WriteCommands to the Event
  68.         $violations = new ConstraintViolationList();
  69.         foreach ($invalidWriteCommands as $invalidWriteCommand) {
  70.             $message 'The value for property "stock" is not allowed to be lower than 0.';
  71.             $violation = new ConstraintViolation(
  72.                 $message// $message
  73.                 $message// $messageTemplate,
  74.                 [], // $parameters,
  75.                 null// $root
  76.                 $invalidWriteCommand->getPath() . '/stock'// $propertyPath
  77.                 $invalidWriteCommand->getPayload()['stock'], // $invalidValue
  78.             );
  79.             $violations->add($violation);
  80.         }
  81.         $event->getExceptions()->add(new WriteConstraintViolationException($violations));
  82.     }
  83.     public function afterProductWritten(EntityWrittenEvent $event): void
  84.     {
  85.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  86.             return;
  87.         }
  88.         if (!$this->config->isStockInitialized()) {
  89.             return;
  90.         }
  91.         $writeResults $event->getWriteResults();
  92.         $newProductStocks = [];
  93.         $existingProductStocks = [];
  94.         foreach ($writeResults as $writeResult) {
  95.             $payload $writeResult->getPayload();
  96.             // Filter out instances of EntityWriteResult with empty payload. Somehow they are introduced by a bug in
  97.             // the Shopware DAL.
  98.             if (count($payload) === 0) {
  99.                 continue;
  100.             }
  101.             if ($payload['versionId'] !== Defaults::LIVE_VERSION) {
  102.                 continue;
  103.             }
  104.             if (!array_key_exists('stock'$payload)) {
  105.                 continue;
  106.             }
  107.             $isNewProduct $writeResult->getExistence() && !$writeResult->getExistence()->exists();
  108.             $productId $payload['id'];
  109.             if ($isNewProduct) {
  110.                 $newProductStocks[$productId] = $payload['stock'];
  111.             } else {
  112.                 $existingProductStocks[$productId] = $payload['stock'];
  113.             }
  114.         }
  115.         if (count($existingProductStocks) > 0) {
  116.             $this->totalStockWriter->setTotalStockForProducts(
  117.                 $existingProductStocks,
  118.                 StockLocationReference::productTotalStockChange(),
  119.                 $event->getContext(),
  120.             );
  121.         }
  122.         if (count($newProductStocks) > 0) {
  123.             $this->totalStockWriter->setTotalStockForProducts(
  124.                 $newProductStocks,
  125.                 StockLocationReference::initialization(),
  126.                 $event->getContext(),
  127.             );
  128.         }
  129.     }
  130. }