vendor/shopware/core/Content/Flow/Dispatching/Action/SetOrderStateAction.php line 89

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Flow\Dispatching\Action;
  3. use Doctrine\DBAL\Connection;
  4. use Psr\Log\LoggerInterface;
  5. use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
  6. use Shopware\Core\Content\Flow\Dispatching\DelayableAction;
  7. use Shopware\Core\Content\Flow\Dispatching\StorableFlow;
  8. use Shopware\Core\Defaults;
  9. use Shopware\Core\Framework\Context;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
  11. use Shopware\Core\Framework\Event\FlowEvent;
  12. use Shopware\Core\Framework\Event\OrderAware;
  13. use Shopware\Core\Framework\Feature;
  14. use Shopware\Core\Framework\Log\Package;
  15. use Shopware\Core\Framework\ShopwareHttpException;
  16. use Shopware\Core\Framework\Uuid\Uuid;
  17. use Shopware\Core\System\StateMachine\Exception\IllegalTransitionException;
  18. use Shopware\Core\System\StateMachine\Exception\StateMachineNotFoundException;
  19. use Symfony\Component\HttpFoundation\ParameterBag;
  20. /**
  21. * @deprecated tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore,
  22. * therefore the actions won't implement the EventSubscriberInterface anymore.
  23. */
  24. #[Package('business-ops')]
  25. class SetOrderStateAction extends FlowAction implements DelayableAction
  26. {
  27. public const FORCE_TRANSITION = 'force_transition';
  28. private const ORDER = 'order';
  29. private const ORDER_DELIVERY = 'order_delivery';
  30. private const ORDER_TRANSACTION = 'order_transaction';
  31. private Connection $connection;
  32. private LoggerInterface $logger;
  33. private OrderService $orderService;
  34. /**
  35. * @internal
  36. */
  37. public function __construct(
  38. Connection $connection,
  39. LoggerInterface $logger,
  40. OrderService $orderService
  41. ) {
  42. $this->connection = $connection;
  43. $this->logger = $logger;
  44. $this->orderService = $orderService;
  45. }
  46. public static function getName(): string
  47. {
  48. return 'action.set.order.state';
  49. }
  50. /**
  51. * @deprecated tag:v6.5.0 - reason:remove-subscriber - Will be removed
  52. *
  53. * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  54. */
  55. public static function getSubscribedEvents()
  56. {
  57. if (Feature::isActive('v6.5.0.0')) {
  58. return [];
  59. }
  60. return [
  61. self::getName() => 'handle',
  62. ];
  63. }
  64. /**
  65. * @return array<int, string>
  66. */
  67. public function requirements(): array
  68. {
  69. return [OrderAware::class];
  70. }
  71. /**
  72. * @deprecated tag:v6.5.0 Will be removed, implement handleFlow instead
  73. */
  74. public function handle(FlowEvent $event): void
  75. {
  76. Feature::triggerDeprecationOrThrow(
  77. 'v6.5.0.0',
  78. Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0')
  79. );
  80. $baseEvent = $event->getEvent();
  81. if (!$baseEvent instanceof OrderAware) {
  82. return;
  83. }
  84. $this->update($baseEvent->getContext(), $event->getConfig(), $baseEvent->getOrderId());
  85. }
  86. public function handleFlow(StorableFlow $flow): void
  87. {
  88. if (!$flow->hasStore(OrderAware::ORDER_ID)) {
  89. return;
  90. }
  91. $this->update($flow->getContext(), $flow->getConfig(), $flow->getStore(OrderAware::ORDER_ID));
  92. }
  93. /**
  94. * @param array<string, mixed> $config
  95. */
  96. private function update(Context $context, array $config, string $orderId): void
  97. {
  98. if (empty($config)) {
  99. return;
  100. }
  101. if ($config[self::FORCE_TRANSITION] ?? false) {
  102. $context->addState(self::FORCE_TRANSITION);
  103. }
  104. $this->connection->beginTransaction();
  105. try {
  106. $transitions = array_filter([
  107. self::ORDER => $config[self::ORDER] ?? null,
  108. self::ORDER_DELIVERY => $config[self::ORDER_DELIVERY] ?? null,
  109. self::ORDER_TRANSACTION => $config[self::ORDER_TRANSACTION] ?? null,
  110. ]);
  111. foreach ($transitions as $machine => $toPlace) {
  112. $this->transitState((string) $machine, $orderId, (string) $toPlace, $context);
  113. }
  114. $this->connection->commit();
  115. } catch (ShopwareHttpException $e) {
  116. $this->connection->rollBack();
  117. $this->logger->error($e->getMessage());
  118. } finally {
  119. $context->removeState(self::FORCE_TRANSITION);
  120. }
  121. }
  122. /**
  123. * @throws IllegalTransitionException
  124. * @throws StateMachineNotFoundException
  125. */
  126. private function transitState(string $machine, string $orderId, string $toPlace, Context $context): void
  127. {
  128. if (!$toPlace) {
  129. return;
  130. }
  131. $data = new ParameterBag();
  132. $machineId = $machine === self::ORDER ? $orderId : $this->getMachineId($machine, $orderId);
  133. if (!$machineId) {
  134. throw new StateMachineNotFoundException($machine);
  135. }
  136. $actionName = $this->getAvailableActionName($machine, $machineId, $toPlace);
  137. if (!$actionName) {
  138. $actionName = $toPlace;
  139. }
  140. switch ($machine) {
  141. case self::ORDER:
  142. $this->orderService->orderStateTransition($orderId, $actionName, $data, $context);
  143. return;
  144. case self::ORDER_DELIVERY:
  145. $this->orderService->orderDeliveryStateTransition($machineId, $actionName, $data, $context);
  146. return;
  147. case self::ORDER_TRANSACTION:
  148. $this->orderService->orderTransactionStateTransition($machineId, $actionName, $data, $context);
  149. return;
  150. default:
  151. throw new StateMachineNotFoundException($machine);
  152. }
  153. }
  154. private function getMachineId(string $machine, string $orderId): ?string
  155. {
  156. return $this->connection->fetchOne(
  157. 'SELECT LOWER(HEX(id)) FROM ' . $machine . ' WHERE order_id = :id AND version_id = :version ORDER BY created_at DESC',
  158. [
  159. 'id' => Uuid::fromHexToBytes($orderId),
  160. 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION),
  161. ]
  162. ) ?: null;
  163. }
  164. private function getAvailableActionName(string $machine, string $machineId, string $toPlace): ?string
  165. {
  166. $actionName = $this->connection->fetchOne(
  167. 'SELECT action_name FROM state_machine_transition WHERE from_state_id = :fromStateId AND to_state_id = :toPlaceId',
  168. [
  169. 'fromStateId' => $this->getFromPlaceId($machine, $machineId),
  170. 'toPlaceId' => $this->getToPlaceId($toPlace, $machine),
  171. ]
  172. );
  173. return $actionName ?: null;
  174. }
  175. private function getToPlaceId(string $toPlace, string $machine): ?string
  176. {
  177. $id = $this->connection->fetchOne(
  178. 'SELECT id FROM state_machine_state WHERE technical_name = :toPlace AND state_machine_id = :stateMachineId',
  179. [
  180. 'toPlace' => $toPlace,
  181. 'stateMachineId' => $this->getStateMachineId($machine),
  182. ]
  183. );
  184. return $id ?: null;
  185. }
  186. private function getFromPlaceId(string $machine, string $machineId): ?string
  187. {
  188. $escaped = EntityDefinitionQueryHelper::escape($machine);
  189. $id = $this->connection->fetchOne(
  190. 'SELECT state_id FROM ' . $escaped . 'WHERE id = :id',
  191. [
  192. 'id' => Uuid::fromHexToBytes($machineId),
  193. ]
  194. );
  195. return $id ?: null;
  196. }
  197. private function getStateMachineId(string $machine): ?string
  198. {
  199. $id = $this->connection->fetchOne(
  200. 'SELECT id FROM state_machine WHERE technical_name = :technicalName',
  201. [
  202. 'technicalName' => $machine . '.state',
  203. ]
  204. );
  205. return $id ?: null;
  206. }
  207. }