The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── Attribute
    ├── AsMessage.php
    └── AsMessageHandler.php
├── CHANGELOG.md
├── Command
    ├── AbstractFailedMessagesCommand.php
    ├── ConsumeMessagesCommand.php
    ├── DebugCommand.php
    ├── FailedMessagesRemoveCommand.php
    ├── FailedMessagesRetryCommand.php
    ├── FailedMessagesShowCommand.php
    ├── SetupTransportsCommand.php
    ├── StatsCommand.php
    └── StopWorkersCommand.php
├── DataCollector
    └── MessengerDataCollector.php
├── DependencyInjection
    └── MessengerPass.php
├── Envelope.php
├── Event
    ├── AbstractWorkerMessageEvent.php
    ├── SendMessageToTransportsEvent.php
    ├── WorkerMessageFailedEvent.php
    ├── WorkerMessageHandledEvent.php
    ├── WorkerMessageReceivedEvent.php
    ├── WorkerMessageRetriedEvent.php
    ├── WorkerMessageSkipEvent.php
    ├── WorkerRateLimitedEvent.php
    ├── WorkerRunningEvent.php
    ├── WorkerStartedEvent.php
    └── WorkerStoppedEvent.php
├── EventListener
    ├── AddErrorDetailsStampListener.php
    ├── DispatchPcntlSignalListener.php
    ├── ResetMemoryUsageListener.php
    ├── ResetServicesListener.php
    ├── SendFailedMessageForRetryListener.php
    ├── SendFailedMessageToFailureTransportListener.php
    ├── StopWorkerOnCustomStopExceptionListener.php
    ├── StopWorkerOnFailureLimitListener.php
    ├── StopWorkerOnMemoryLimitListener.php
    ├── StopWorkerOnMessageLimitListener.php
    ├── StopWorkerOnRestartSignalListener.php
    └── StopWorkerOnTimeLimitListener.php
├── Exception
    ├── DelayedMessageHandlingException.php
    ├── EnvelopeAwareExceptionInterface.php
    ├── EnvelopeAwareExceptionTrait.php
    ├── ExceptionInterface.php
    ├── HandlerFailedException.php
    ├── InvalidArgumentException.php
    ├── LogicException.php
    ├── MessageDecodingFailedException.php
    ├── NoHandlerForMessageException.php
    ├── NoSenderForMessageException.php
    ├── RecoverableExceptionInterface.php
    ├── RecoverableMessageHandlingException.php
    ├── RejectRedeliveredMessageException.php
    ├── RuntimeException.php
    ├── StopWorkerException.php
    ├── StopWorkerExceptionInterface.php
    ├── TransportException.php
    ├── UnrecoverableExceptionInterface.php
    ├── UnrecoverableMessageHandlingException.php
    ├── ValidationFailedException.php
    ├── WrappedExceptionsInterface.php
    └── WrappedExceptionsTrait.php
├── HandleTrait.php
├── Handler
    ├── Acknowledger.php
    ├── BatchHandlerInterface.php
    ├── BatchHandlerTrait.php
    ├── HandlerDescriptor.php
    ├── HandlersLocator.php
    ├── HandlersLocatorInterface.php
    └── RedispatchMessageHandler.php
├── LICENSE
├── Message
    └── RedispatchMessage.php
├── MessageBus.php
├── MessageBusInterface.php
├── Middleware
    ├── ActivationMiddleware.php
    ├── AddBusNameStampMiddleware.php
    ├── DeduplicateMiddleware.php
    ├── DispatchAfterCurrentBusMiddleware.php
    ├── FailedMessageProcessingMiddleware.php
    ├── HandleMessageMiddleware.php
    ├── MiddlewareInterface.php
    ├── RejectRedeliveredMessageMiddleware.php
    ├── RouterContextMiddleware.php
    ├── SendMessageMiddleware.php
    ├── StackInterface.php
    ├── StackMiddleware.php
    ├── TraceableMiddleware.php
    └── ValidationMiddleware.php
├── README.md
├── Retry
    ├── MultiplierRetryStrategy.php
    └── RetryStrategyInterface.php
├── RoutableMessageBus.php
├── Stamp
    ├── AckStamp.php
    ├── BusNameStamp.php
    ├── ConsumedByWorkerStamp.php
    ├── DeduplicateStamp.php
    ├── DelayStamp.php
    ├── DispatchAfterCurrentBusStamp.php
    ├── ErrorDetailsStamp.php
    ├── FlushBatchHandlersStamp.php
    ├── HandledStamp.php
    ├── HandlerArgumentsStamp.php
    ├── MessageDecodingFailedStamp.php
    ├── NoAutoAckStamp.php
    ├── NonSendableStampInterface.php
    ├── ReceivedStamp.php
    ├── RedeliveryStamp.php
    ├── RouterContextStamp.php
    ├── SentForRetryStamp.php
    ├── SentStamp.php
    ├── SentToFailureTransportStamp.php
    ├── SerializedMessageStamp.php
    ├── SerializerStamp.php
    ├── StampInterface.php
    ├── TransportMessageIdStamp.php
    ├── TransportNamesStamp.php
    └── ValidationStamp.php
├── Test
    └── Middleware
    │   └── MiddlewareTestCase.php
├── TraceableMessageBus.php
├── Transport
    ├── CloseableTransportInterface.php
    ├── InMemory
    │   ├── InMemoryTransport.php
    │   └── InMemoryTransportFactory.php
    ├── Receiver
    │   ├── KeepaliveReceiverInterface.php
    │   ├── ListableReceiverInterface.php
    │   ├── MessageCountAwareInterface.php
    │   ├── QueueReceiverInterface.php
    │   ├── ReceiverInterface.php
    │   └── SingleMessageReceiver.php
    ├── Sender
    │   ├── SenderInterface.php
    │   ├── SendersLocator.php
    │   └── SendersLocatorInterface.php
    ├── Serialization
    │   ├── Normalizer
    │   │   └── FlattenExceptionNormalizer.php
    │   ├── PhpSerializer.php
    │   ├── Serializer.php
    │   └── SerializerInterface.php
    ├── SetupableTransportInterface.php
    ├── Sync
    │   ├── SyncTransport.php
    │   └── SyncTransportFactory.php
    ├── TransportFactory.php
    ├── TransportFactoryInterface.php
    └── TransportInterface.php
├── Worker.php
├── WorkerMetadata.php
└── composer.json


/Attribute/AsMessage.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Attribute;
13 | 
14 | /**
15 |  * Attribute for configuring message routing.
16 |  *
17 |  * @author Pierre Rineau pierre.rineau@processus.org>
18 |  */
19 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
20 | class AsMessage
21 | {
22 |     public function __construct(
23 |         /**
24 |          * Name of the transports to which the message should be routed.
25 |          */
26 |         public string|array|null $transport = null,
27 |     ) {
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/Attribute/AsMessageHandler.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Attribute;
13 | 
14 | /**
15 |  * Service tag to autoconfigure message handlers.
16 |  *
17 |  * @author Alireza Mirsepassi <alirezamirsepassi@gmail.com>
18 |  */
19 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20 | class AsMessageHandler
21 | {
22 |     public function __construct(
23 |         /**
24 |          * Name of the bus from which this handler can receive messages, by default all buses.
25 |          */
26 |         public ?string $bus = null,
27 | 
28 |         /**
29 |          * Name of the transport from which this handler can receive messages, by default all transports.
30 |          */
31 |         public ?string $fromTransport = null,
32 | 
33 |         /**
34 |          * Type of messages (FQCN) that can be processed by the handler, only needed if can't be guessed by type-hint.
35 |          */
36 |         public ?string $handles = null,
37 | 
38 |         /**
39 |          * Name of the method that will process the message, only if the target is a class.
40 |          */
41 |         public ?string $method = null,
42 | 
43 |         /**
44 |          * Priority of this handler when multiple handlers can process the same message.
45 |          */
46 |         public int $priority = 0,
47 |     ) {
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/Command/DebugCommand.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Command;
 13 | 
 14 | use Symfony\Component\Console\Attribute\AsCommand;
 15 | use Symfony\Component\Console\Command\Command;
 16 | use Symfony\Component\Console\Completion\CompletionInput;
 17 | use Symfony\Component\Console\Completion\CompletionSuggestions;
 18 | use Symfony\Component\Console\Exception\RuntimeException;
 19 | use Symfony\Component\Console\Input\InputArgument;
 20 | use Symfony\Component\Console\Input\InputInterface;
 21 | use Symfony\Component\Console\Output\OutputInterface;
 22 | use Symfony\Component\Console\Style\SymfonyStyle;
 23 | 
 24 | /**
 25 |  * A console command to debug Messenger information.
 26 |  *
 27 |  * @author Roland Franssen <franssen.roland@gmail.com>
 28 |  */
 29 | #[AsCommand(name: 'debug:messenger', description: 'List messages you can dispatch using the message buses')]
 30 | class DebugCommand extends Command
 31 | {
 32 |     public function __construct(
 33 |         private array $mapping,
 34 |     ) {
 35 |         parent::__construct();
 36 |     }
 37 | 
 38 |     protected function configure(): void
 39 |     {
 40 |         $this
 41 |             ->addArgument('bus', InputArgument::OPTIONAL, \sprintf('The bus id (one of "%s")', implode('", "', array_keys($this->mapping))))
 42 |             ->setHelp(<<<'EOF'
 43 | The <info>%command.name%</info> command displays all messages that can be
 44 | dispatched using the message buses:
 45 | 
 46 |   <info>php %command.full_name%</info>
 47 | 
 48 | Or for a specific bus only:
 49 | 
 50 |   <info>php %command.full_name% command_bus</info>
 51 | 
 52 | EOF
 53 |             )
 54 |         ;
 55 |     }
 56 | 
 57 |     protected function execute(InputInterface $input, OutputInterface $output): int
 58 |     {
 59 |         $io = new SymfonyStyle($input, $output);
 60 |         $io->title('Messenger');
 61 | 
 62 |         $mapping = $this->mapping;
 63 |         if ($bus = $input->getArgument('bus')) {
 64 |             if (!isset($mapping[$bus])) {
 65 |                 throw new RuntimeException(\sprintf('Bus "%s" does not exist. Known buses are "%s".', $bus, implode('", "', array_keys($this->mapping))));
 66 |             }
 67 |             $mapping = [$bus => $mapping[$bus]];
 68 |         }
 69 | 
 70 |         foreach ($mapping as $bus => $handlersByMessage) {
 71 |             $io->section($bus);
 72 | 
 73 |             $tableRows = [];
 74 |             foreach ($handlersByMessage as $message => $handlers) {
 75 |                 if ($description = self::getClassDescription($message)) {
 76 |                     $tableRows[] = [\sprintf('<comment>%s</>', $description)];
 77 |                 }
 78 | 
 79 |                 $tableRows[] = [\sprintf('<fg=cyan>%s</fg=cyan>', $message)];
 80 |                 foreach ($handlers as $handler) {
 81 |                     $tableRows[] = [
 82 |                         \sprintf('    handled by <info>%s</>', $handler[0]).$this->formatConditions($handler[1]),
 83 |                     ];
 84 |                     if ($handlerDescription = self::getClassDescription($handler[0])) {
 85 |                         $tableRows[] = [\sprintf('               <comment>%s</>', $handlerDescription)];
 86 |                     }
 87 |                 }
 88 |                 $tableRows[] = [''];
 89 |             }
 90 | 
 91 |             if ($tableRows) {
 92 |                 $io->text('The following messages can be dispatched:');
 93 |                 $io->newLine();
 94 |                 $io->table([], $tableRows);
 95 |             } else {
 96 |                 $io->warning(\sprintf('No handled message found in bus "%s".', $bus));
 97 |             }
 98 |         }
 99 | 
100 |         return 0;
101 |     }
102 | 
103 |     private function formatConditions(array $options): string
104 |     {
105 |         if (!$options) {
106 |             return '';
107 |         }
108 | 
109 |         $optionsMapping = [];
110 |         foreach ($options as $key => $value) {
111 |             $optionsMapping[] = $key.'='.$value;
112 |         }
113 | 
114 |         return ' (when '.implode(', ', $optionsMapping).')';
115 |     }
116 | 
117 |     private static function getClassDescription(string $class): string
118 |     {
119 |         try {
120 |             $r = new \ReflectionClass($class);
121 | 
122 |             if ($docComment = $r->getDocComment()) {
123 |                 $docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0];
124 | 
125 |                 return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment));
126 |             }
127 |         } catch (\ReflectionException) {
128 |         }
129 | 
130 |         return '';
131 |     }
132 | 
133 |     public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
134 |     {
135 |         if ($input->mustSuggestArgumentValuesFor('bus')) {
136 |             $suggestions->suggestValues(array_keys($this->mapping));
137 |         }
138 |     }
139 | }
140 | 


--------------------------------------------------------------------------------
/Command/SetupTransportsCommand.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Command;
13 | 
14 | use Psr\Container\ContainerInterface;
15 | use Symfony\Component\Console\Attribute\AsCommand;
16 | use Symfony\Component\Console\Command\Command;
17 | use Symfony\Component\Console\Completion\CompletionInput;
18 | use Symfony\Component\Console\Completion\CompletionSuggestions;
19 | use Symfony\Component\Console\Input\InputArgument;
20 | use Symfony\Component\Console\Input\InputInterface;
21 | use Symfony\Component\Console\Output\OutputInterface;
22 | use Symfony\Component\Console\Style\SymfonyStyle;
23 | use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
24 | 
25 | /**
26 |  * @author Vincent Touzet <vincent.touzet@gmail.com>
27 |  */
28 | #[AsCommand(name: 'messenger:setup-transports', description: 'Prepare the required infrastructure for the transport')]
29 | class SetupTransportsCommand extends Command
30 | {
31 |     public function __construct(
32 |         private ContainerInterface $transportLocator,
33 |         private array $transportNames = [],
34 |     ) {
35 |         parent::__construct();
36 |     }
37 | 
38 |     protected function configure(): void
39 |     {
40 |         $this
41 |             ->addArgument('transport', InputArgument::OPTIONAL, 'Name of the transport to setup', null)
42 |             ->setHelp(<<<EOF
43 | The <info>%command.name%</info> command setups the transports:
44 | 
45 |     <info>php %command.full_name%</info>
46 | 
47 | Or a specific transport only:
48 | 
49 |     <info>php %command.full_name% <transport></info>
50 | EOF
51 |             )
52 |         ;
53 |     }
54 | 
55 |     protected function execute(InputInterface $input, OutputInterface $output): int
56 |     {
57 |         $io = new SymfonyStyle($input, $output);
58 | 
59 |         $transportNames = $this->transportNames;
60 |         // do we want to set up only one transport?
61 |         if ($transport = $input->getArgument('transport')) {
62 |             if (!$this->transportLocator->has($transport)) {
63 |                 throw new \RuntimeException(\sprintf('The "%s" transport does not exist.', $transport));
64 |             }
65 |             $transportNames = [$transport];
66 |         }
67 | 
68 |         foreach ($transportNames as $id => $transportName) {
69 |             $transport = $this->transportLocator->get($transportName);
70 |             if (!$transport instanceof SetupableTransportInterface) {
71 |                 $io->note(\sprintf('The "%s" transport does not support setup.', $transportName));
72 |                 continue;
73 |             }
74 | 
75 |             try {
76 |                 $transport->setup();
77 |                 $io->success(\sprintf('The "%s" transport was set up successfully.', $transportName));
78 |             } catch (\Exception $e) {
79 |                 throw new \RuntimeException(\sprintf('An error occurred while setting up the "%s" transport: ', $transportName).$e->getMessage(), 0, $e);
80 |             }
81 |         }
82 | 
83 |         return 0;
84 |     }
85 | 
86 |     public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
87 |     {
88 |         if ($input->mustSuggestArgumentValuesFor('transport')) {
89 |             $suggestions->suggestValues($this->transportNames);
90 |         }
91 |     }
92 | }
93 | 


--------------------------------------------------------------------------------
/Command/StatsCommand.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Command;
 13 | 
 14 | use Psr\Container\ContainerInterface;
 15 | use Symfony\Component\Console\Attribute\AsCommand;
 16 | use Symfony\Component\Console\Command\Command;
 17 | use Symfony\Component\Console\Completion\CompletionInput;
 18 | use Symfony\Component\Console\Completion\CompletionSuggestions;
 19 | use Symfony\Component\Console\Input\InputArgument;
 20 | use Symfony\Component\Console\Input\InputInterface;
 21 | use Symfony\Component\Console\Input\InputOption;
 22 | use Symfony\Component\Console\Output\ConsoleOutputInterface;
 23 | use Symfony\Component\Console\Output\OutputInterface;
 24 | use Symfony\Component\Console\Style\SymfonyStyle;
 25 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
 26 | use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
 27 | 
 28 | /**
 29 |  * @author Kévin Thérage <therage.kevin@gmail.com>
 30 |  */
 31 | #[AsCommand(name: 'messenger:stats', description: 'Show the message count for one or more transports')]
 32 | class StatsCommand extends Command
 33 | {
 34 |     public function __construct(
 35 |         private ContainerInterface $transportLocator,
 36 |         private array $transportNames = [],
 37 |     ) {
 38 |         parent::__construct();
 39 |     }
 40 | 
 41 |     protected function configure(): void
 42 |     {
 43 |         $this
 44 |             ->addArgument('transport_names', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'List of transports\' names')
 45 |             ->addOption('format', '', InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt')
 46 |             ->setHelp(<<<EOF
 47 | The <info>%command.name%</info> command counts the messages for all the transports:
 48 | 
 49 |     <info>php %command.full_name%</info>
 50 | 
 51 | Or specific transports only:
 52 | 
 53 |     <info>php %command.full_name% <transportNames></info>
 54 | 
 55 | The <info>--format</info> option specifies the format of the command output:
 56 | 
 57 |   <info>php %command.full_name% --format=json</info>
 58 | EOF
 59 |             )
 60 |         ;
 61 |     }
 62 | 
 63 |     protected function execute(InputInterface $input, OutputInterface $output): int
 64 |     {
 65 |         $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
 66 | 
 67 |         $format = $input->getOption('format');
 68 |         if ('text' === $format) {
 69 |             trigger_deprecation('symfony/messenger', '7.2', 'The "text" format is deprecated, use "txt" instead.');
 70 | 
 71 |             $format = 'txt';
 72 |         }
 73 |         if (!\in_array($format, $this->getAvailableFormatOptions(), true)) {
 74 |             throw new InvalidArgumentException('Invalid output format.');
 75 |         }
 76 | 
 77 |         $transportNames = $this->transportNames;
 78 |         if ($input->getArgument('transport_names')) {
 79 |             $transportNames = $input->getArgument('transport_names');
 80 |         }
 81 | 
 82 |         $outputTable = [];
 83 |         $uncountableTransports = [];
 84 |         foreach ($transportNames as $transportName) {
 85 |             if (!$this->transportLocator->has($transportName)) {
 86 |                 if ($this->formatSupportsWarnings($format)) {
 87 |                     $io->warning(\sprintf('The "%s" transport does not exist.', $transportName));
 88 |                 }
 89 | 
 90 |                 continue;
 91 |             }
 92 |             $transport = $this->transportLocator->get($transportName);
 93 |             if (!$transport instanceof MessageCountAwareInterface) {
 94 |                 $uncountableTransports[] = $transportName;
 95 | 
 96 |                 continue;
 97 |             }
 98 |             $outputTable[] = [$transportName, $transport->getMessageCount()];
 99 |         }
100 | 
101 |         match ($format) {
102 |             'txt' => $this->outputText($io, $outputTable, $uncountableTransports),
103 |             'json' => $this->outputJson($io, $outputTable, $uncountableTransports),
104 |         };
105 | 
106 |         return 0;
107 |     }
108 | 
109 |     private function outputText(SymfonyStyle $io, array $outputTable, array $uncountableTransports): void
110 |     {
111 |         $io->table(['Transport', 'Count'], $outputTable);
112 | 
113 |         if ($uncountableTransports) {
114 |             $io->note(\sprintf('Unable to get message count for the following transports: "%s".', implode('", "', $uncountableTransports)));
115 |         }
116 |     }
117 | 
118 |     private function outputJson(SymfonyStyle $io, array $outputTable, array $uncountableTransports): void
119 |     {
120 |         $output = ['transports' => []];
121 |         foreach ($outputTable as [$transportName, $count]) {
122 |             $output['transports'][$transportName] = ['count' => $count];
123 |         }
124 | 
125 |         if ($uncountableTransports) {
126 |             $output['uncountable_transports'] = $uncountableTransports;
127 |         }
128 | 
129 |         $io->writeln(json_encode($output, \JSON_PRETTY_PRINT));
130 |     }
131 | 
132 |     private function formatSupportsWarnings(string $format): bool
133 |     {
134 |         return match ($format) {
135 |             'txt' => true,
136 |             'json' => false,
137 |         };
138 |     }
139 | 
140 |     public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
141 |     {
142 |         if ($input->mustSuggestOptionValuesFor('format')) {
143 |             $suggestions->suggestValues($this->getAvailableFormatOptions());
144 |         }
145 |     }
146 | 
147 |     /** @return string[] */
148 |     private function getAvailableFormatOptions(): array
149 |     {
150 |         return ['txt', 'json'];
151 |     }
152 | }
153 | 


--------------------------------------------------------------------------------
/Command/StopWorkersCommand.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Command;
13 | 
14 | use Psr\Cache\CacheItemPoolInterface;
15 | use Symfony\Component\Console\Attribute\AsCommand;
16 | use Symfony\Component\Console\Command\Command;
17 | use Symfony\Component\Console\Input\InputInterface;
18 | use Symfony\Component\Console\Output\ConsoleOutputInterface;
19 | use Symfony\Component\Console\Output\OutputInterface;
20 | use Symfony\Component\Console\Style\SymfonyStyle;
21 | use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener;
22 | 
23 | /**
24 |  * @author Ryan Weaver <ryan@symfonycasts.com>
25 |  */
26 | #[AsCommand(name: 'messenger:stop-workers', description: 'Stop workers after their current message')]
27 | class StopWorkersCommand extends Command
28 | {
29 |     public function __construct(
30 |         private CacheItemPoolInterface $restartSignalCachePool,
31 |     ) {
32 |         parent::__construct();
33 |     }
34 | 
35 |     protected function configure(): void
36 |     {
37 |         $this
38 |             ->setDefinition([])
39 |             ->setHelp(<<<'EOF'
40 | The <info>%command.name%</info> command sends a signal to stop any <info>messenger:consume</info> processes that are running.
41 | 
42 |     <info>php %command.full_name%</info>
43 | 
44 | Each worker command will finish the message they are currently processing
45 | and then exit. Worker commands are *not* automatically restarted: that
46 | should be handled by a process control system.
47 | EOF
48 |             )
49 |         ;
50 |     }
51 | 
52 |     protected function execute(InputInterface $input, OutputInterface $output): int
53 |     {
54 |         $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
55 | 
56 |         $cacheItem = $this->restartSignalCachePool->getItem(StopWorkerOnRestartSignalListener::RESTART_REQUESTED_TIMESTAMP_KEY);
57 |         $cacheItem->set(microtime(true));
58 |         $this->restartSignalCachePool->save($cacheItem);
59 | 
60 |         $io->success('Signal successfully sent to stop any running workers.');
61 | 
62 |         return 0;
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/DataCollector/MessengerDataCollector.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\DataCollector;
 13 | 
 14 | use Symfony\Component\HttpFoundation\Request;
 15 | use Symfony\Component\HttpFoundation\Response;
 16 | use Symfony\Component\HttpKernel\DataCollector\DataCollector;
 17 | use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
 18 | use Symfony\Component\Messenger\TraceableMessageBus;
 19 | use Symfony\Component\VarDumper\Caster\ClassStub;
 20 | 
 21 | /**
 22 |  * @author Samuel Roze <samuel.roze@gmail.com>
 23 |  *
 24 |  * @final
 25 |  */
 26 | class MessengerDataCollector extends DataCollector implements LateDataCollectorInterface
 27 | {
 28 |     private array $traceableBuses = [];
 29 | 
 30 |     public function registerBus(string $name, TraceableMessageBus $bus): void
 31 |     {
 32 |         $this->traceableBuses[$name] = $bus;
 33 |     }
 34 | 
 35 |     public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
 36 |     {
 37 |         // Noop. Everything is collected live by the traceable buses & cloned as late as possible.
 38 |     }
 39 | 
 40 |     public function lateCollect(): void
 41 |     {
 42 |         $this->data = ['messages' => [], 'buses' => array_keys($this->traceableBuses)];
 43 | 
 44 |         $messages = [];
 45 |         foreach ($this->traceableBuses as $busName => $bus) {
 46 |             foreach ($bus->getDispatchedMessages() as $message) {
 47 |                 $debugRepresentation = $this->cloneVar($this->collectMessage($busName, $message));
 48 |                 $messages[] = [$debugRepresentation, $message['callTime']];
 49 |             }
 50 |         }
 51 | 
 52 |         // Order by call time
 53 |         usort($messages, fn ($a, $b) => $a[1] <=> $b[1]);
 54 | 
 55 |         // Keep the messages clones only
 56 |         $this->data['messages'] = array_column($messages, 0);
 57 |     }
 58 | 
 59 |     public function getName(): string
 60 |     {
 61 |         return 'messenger';
 62 |     }
 63 | 
 64 |     public function reset(): void
 65 |     {
 66 |         $this->data = [];
 67 |         foreach ($this->traceableBuses as $traceableBus) {
 68 |             $traceableBus->reset();
 69 |         }
 70 |     }
 71 | 
 72 |     protected function getCasters(): array
 73 |     {
 74 |         $casters = parent::getCasters();
 75 | 
 76 |         // Unset the default caster truncating collectors data.
 77 |         unset($casters['*']);
 78 | 
 79 |         return $casters;
 80 |     }
 81 | 
 82 |     private function collectMessage(string $busName, array $tracedMessage): array
 83 |     {
 84 |         $message = $tracedMessage['message'];
 85 | 
 86 |         $debugRepresentation = [
 87 |             'bus' => $busName,
 88 |             'stamps' => $tracedMessage['stamps'] ?? null,
 89 |             'stamps_after_dispatch' => $tracedMessage['stamps_after_dispatch'] ?? null,
 90 |             'message' => [
 91 |                 'type' => new ClassStub($message::class),
 92 |                 'value' => $message,
 93 |             ],
 94 |             'caller' => $tracedMessage['caller'],
 95 |         ];
 96 | 
 97 |         if (isset($tracedMessage['exception'])) {
 98 |             $exception = $tracedMessage['exception'];
 99 | 
100 |             $debugRepresentation['exception'] = [
101 |                 'type' => $exception::class,
102 |                 'value' => $exception,
103 |             ];
104 |         }
105 | 
106 |         return $debugRepresentation;
107 |     }
108 | 
109 |     public function getExceptionsCount(?string $bus = null): int
110 |     {
111 |         $count = 0;
112 |         foreach ($this->getMessages($bus) as $message) {
113 |             $count += (int) isset($message['exception']);
114 |         }
115 | 
116 |         return $count;
117 |     }
118 | 
119 |     public function getMessages(?string $bus = null): array
120 |     {
121 |         if (null === $bus) {
122 |             return $this->data['messages'];
123 |         }
124 | 
125 |         return array_filter($this->data['messages'], fn ($message) => $bus === $message['bus']);
126 |     }
127 | 
128 |     public function getBuses(): array
129 |     {
130 |         return $this->data['buses'];
131 |     }
132 | }
133 | 


--------------------------------------------------------------------------------
/Envelope.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger;
 13 | 
 14 | use Symfony\Component\Messenger\Stamp\StampInterface;
 15 | 
 16 | /**
 17 |  * A message wrapped in an envelope with stamps (configurations, markers, ...).
 18 |  *
 19 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 20 |  */
 21 | final class Envelope
 22 | {
 23 |     /**
 24 |      * @var array<class-string<StampInterface>, list<StampInterface>>
 25 |      */
 26 |     private array $stamps = [];
 27 | 
 28 |     /**
 29 |      * @param object|Envelope  $message
 30 |      * @param StampInterface[] $stamps
 31 |      */
 32 |     public function __construct(
 33 |         private object $message,
 34 |         array $stamps = [],
 35 |     ) {
 36 |         foreach ($stamps as $stamp) {
 37 |             $this->stamps[$stamp::class][] = $stamp;
 38 |         }
 39 |     }
 40 | 
 41 |     /**
 42 |      * Makes sure the message is in an Envelope and adds the given stamps.
 43 |      *
 44 |      * @param StampInterface[] $stamps
 45 |      */
 46 |     public static function wrap(object $message, array $stamps = []): self
 47 |     {
 48 |         $envelope = $message instanceof self ? $message : new self($message);
 49 | 
 50 |         return $envelope->with(...$stamps);
 51 |     }
 52 | 
 53 |     /**
 54 |      * Adds one or more stamps.
 55 |      */
 56 |     public function with(StampInterface ...$stamps): static
 57 |     {
 58 |         $cloned = clone $this;
 59 | 
 60 |         foreach ($stamps as $stamp) {
 61 |             $cloned->stamps[$stamp::class][] = $stamp;
 62 |         }
 63 | 
 64 |         return $cloned;
 65 |     }
 66 | 
 67 |     /**
 68 |      * Removes all stamps of the given class.
 69 |      */
 70 |     public function withoutAll(string $stampFqcn): static
 71 |     {
 72 |         $cloned = clone $this;
 73 | 
 74 |         unset($cloned->stamps[$stampFqcn]);
 75 | 
 76 |         return $cloned;
 77 |     }
 78 | 
 79 |     /**
 80 |      * Removes all stamps that implement the given type.
 81 |      */
 82 |     public function withoutStampsOfType(string $type): self
 83 |     {
 84 |         $cloned = clone $this;
 85 | 
 86 |         foreach ($cloned->stamps as $class => $stamps) {
 87 |             if ($class === $type || is_subclass_of($class, $type)) {
 88 |                 unset($cloned->stamps[$class]);
 89 |             }
 90 |         }
 91 | 
 92 |         return $cloned;
 93 |     }
 94 | 
 95 |     /**
 96 |      * @template TStamp of StampInterface
 97 |      *
 98 |      * @param class-string<TStamp> $stampFqcn
 99 |      *
100 |      * @return TStamp|null
101 |      */
102 |     public function last(string $stampFqcn): ?StampInterface
103 |     {
104 |         return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;
105 |     }
106 | 
107 |     /**
108 |      * @template TStamp of StampInterface
109 |      *
110 |      * @param class-string<TStamp>|null $stampFqcn
111 |      *
112 |      * @return StampInterface[]|StampInterface[][] The stamps for the specified FQCN, or all stamps by their class name
113 |      *
114 |      * @psalm-return ($stampFqcn is null ? array<class-string<StampInterface>, list<StampInterface>> : list<TStamp>)
115 |      */
116 |     public function all(?string $stampFqcn = null): array
117 |     {
118 |         if (null !== $stampFqcn) {
119 |             return $this->stamps[$stampFqcn] ?? [];
120 |         }
121 | 
122 |         return $this->stamps;
123 |     }
124 | 
125 |     public function getMessage(): object
126 |     {
127 |         return $this->message;
128 |     }
129 | }
130 | 


--------------------------------------------------------------------------------
/Event/AbstractWorkerMessageEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Stamp\StampInterface;
16 | 
17 | abstract class AbstractWorkerMessageEvent
18 | {
19 |     public function __construct(
20 |         private Envelope $envelope,
21 |         private string $receiverName,
22 |     ) {
23 |     }
24 | 
25 |     public function getEnvelope(): Envelope
26 |     {
27 |         return $this->envelope;
28 |     }
29 | 
30 |     /**
31 |      * Returns a unique identifier for transport receiver this message was received from.
32 |      */
33 |     public function getReceiverName(): string
34 |     {
35 |         return $this->receiverName;
36 |     }
37 | 
38 |     public function addStamps(StampInterface ...$stamps): void
39 |     {
40 |         $this->envelope = $this->envelope->with(...$stamps);
41 |     }
42 | }
43 | 


--------------------------------------------------------------------------------
/Event/SendMessageToTransportsEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
16 | 
17 | /**
18 |  * Event is dispatched before a message is sent to the transport.
19 |  *
20 |  * The event is *only* dispatched if the message will actually
21 |  * be sent to at least one transport. If the message is sent
22 |  * to multiple transports, the message is dispatched only once.
23 |  * This message is only dispatched the first time a message
24 |  * is sent to a transport, not also if it is retried.
25 |  *
26 |  * @author Ryan Weaver <ryan@symfonycasts.com>
27 |  */
28 | final class SendMessageToTransportsEvent
29 | {
30 |     /**
31 |      * @param array<string, SenderInterface> $senders
32 |      */
33 |     public function __construct(
34 |         private Envelope $envelope,
35 |         private array $senders,
36 |     ) {
37 |     }
38 | 
39 |     public function getEnvelope(): Envelope
40 |     {
41 |         return $this->envelope;
42 |     }
43 | 
44 |     public function setEnvelope(Envelope $envelope): void
45 |     {
46 |         $this->envelope = $envelope;
47 |     }
48 | 
49 |     /**
50 |      * @return array<string, SenderInterface>
51 |      */
52 |     public function getSenders(): array
53 |     {
54 |         return $this->senders;
55 |     }
56 | }
57 | 


--------------------------------------------------------------------------------
/Event/WorkerMessageFailedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Dispatched when a message was received from a transport and handling failed.
18 |  *
19 |  * The event name is the class name.
20 |  */
21 | final class WorkerMessageFailedEvent extends AbstractWorkerMessageEvent
22 | {
23 |     private \Throwable $throwable;
24 |     private bool $willRetry = false;
25 | 
26 |     public function __construct(Envelope $envelope, string $receiverName, \Throwable $error)
27 |     {
28 |         $this->throwable = $error;
29 | 
30 |         parent::__construct($envelope, $receiverName);
31 |     }
32 | 
33 |     public function getThrowable(): \Throwable
34 |     {
35 |         return $this->throwable;
36 |     }
37 | 
38 |     public function willRetry(): bool
39 |     {
40 |         return $this->willRetry;
41 |     }
42 | 
43 |     public function setForRetry(): void
44 |     {
45 |         $this->willRetry = true;
46 |     }
47 | }
48 | 


--------------------------------------------------------------------------------
/Event/WorkerMessageHandledEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | /**
15 |  * Dispatched after a message was received from a transport and successfully handled.
16 |  *
17 |  * The event name is the class name.
18 |  */
19 | final class WorkerMessageHandledEvent extends AbstractWorkerMessageEvent
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Event/WorkerMessageReceivedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | /**
15 |  * Dispatched when a message was received from a transport but before sent to the bus.
16 |  *
17 |  * The event name is the class name.
18 |  */
19 | final class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent
20 | {
21 |     private bool $shouldHandle = true;
22 | 
23 |     public function shouldHandle(?bool $shouldHandle = null): bool
24 |     {
25 |         if (null !== $shouldHandle) {
26 |             $this->shouldHandle = $shouldHandle;
27 |         }
28 | 
29 |         return $this->shouldHandle;
30 |     }
31 | }
32 | 


--------------------------------------------------------------------------------
/Event/WorkerMessageRetriedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | /**
15 |  * Dispatched after a message has been sent for retry.
16 |  *
17 |  * The event name is the class name.
18 |  */
19 | final class WorkerMessageRetriedEvent extends AbstractWorkerMessageEvent
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Event/WorkerMessageSkipEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | /**
15 |  * Dispatched when a message was skip.
16 |  *
17 |  * The event name is the class name.
18 |  */
19 | final class WorkerMessageSkipEvent extends AbstractWorkerMessageEvent
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Event/WorkerRateLimitedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\RateLimiter\LimiterInterface;
15 | 
16 | /**
17 |  * Dispatched after the worker has been blocked due to a configured rate limiter.
18 |  * Can be used to reset the rate limiter.
19 |  *
20 |  * @author Bob van de Vijver
21 |  */
22 | final class WorkerRateLimitedEvent
23 | {
24 |     public function __construct(private LimiterInterface $limiter, private string $transportName)
25 |     {
26 |     }
27 | 
28 |     public function getLimiter(): LimiterInterface
29 |     {
30 |         return $this->limiter;
31 |     }
32 | 
33 |     public function getTransportName(): string
34 |     {
35 |         return $this->transportName;
36 |     }
37 | }
38 | 


--------------------------------------------------------------------------------
/Event/WorkerRunningEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Worker;
15 | 
16 | /**
17 |  * Dispatched after the worker processed a message or didn't receive a message at all.
18 |  *
19 |  * @author Tobias Schultze <http://tobion.de>
20 |  */
21 | final class WorkerRunningEvent
22 | {
23 |     public function __construct(
24 |         private Worker $worker,
25 |         private bool $isWorkerIdle,
26 |     ) {
27 |     }
28 | 
29 |     public function getWorker(): Worker
30 |     {
31 |         return $this->worker;
32 |     }
33 | 
34 |     /**
35 |      * Returns true when no message has been received by the worker.
36 |      */
37 |     public function isWorkerIdle(): bool
38 |     {
39 |         return $this->isWorkerIdle;
40 |     }
41 | }
42 | 


--------------------------------------------------------------------------------
/Event/WorkerStartedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Worker;
15 | 
16 | /**
17 |  * Dispatched when a worker has been started.
18 |  *
19 |  * @author Tobias Schultze <http://tobion.de>
20 |  */
21 | final class WorkerStartedEvent
22 | {
23 |     public function __construct(
24 |         private Worker $worker,
25 |     ) {
26 |     }
27 | 
28 |     public function getWorker(): Worker
29 |     {
30 |         return $this->worker;
31 |     }
32 | }
33 | 


--------------------------------------------------------------------------------
/Event/WorkerStoppedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Event;
13 | 
14 | use Symfony\Component\Messenger\Worker;
15 | 
16 | /**
17 |  * Dispatched when a worker has been stopped.
18 |  *
19 |  * @author Robin Chalas <robin.chalas@gmail.com>
20 |  */
21 | final class WorkerStoppedEvent
22 | {
23 |     public function __construct(
24 |         private Worker $worker,
25 |     ) {
26 |     }
27 | 
28 |     public function getWorker(): Worker
29 |     {
30 |         return $this->worker;
31 |     }
32 | }
33 | 


--------------------------------------------------------------------------------
/EventListener/AddErrorDetailsStampListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 | use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
16 | use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
17 | 
18 | final class AddErrorDetailsStampListener implements EventSubscriberInterface
19 | {
20 |     public function onMessageFailed(WorkerMessageFailedEvent $event): void
21 |     {
22 |         $stamp = ErrorDetailsStamp::create($event->getThrowable());
23 |         $previousStamp = $event->getEnvelope()->last(ErrorDetailsStamp::class);
24 | 
25 |         // Do not append duplicate information
26 |         if (null === $previousStamp || !$previousStamp->equals($stamp)) {
27 |             $event->addStamps($stamp);
28 |         }
29 |     }
30 | 
31 |     public static function getSubscribedEvents(): array
32 |     {
33 |         return [
34 |             // must have higher priority than SendFailedMessageForRetryListener
35 |             WorkerMessageFailedEvent::class => ['onMessageFailed', 200],
36 |         ];
37 |     }
38 | }
39 | 


--------------------------------------------------------------------------------
/EventListener/DispatchPcntlSignalListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
16 | 
17 | /**
18 |  * @author Tobias Schultze <http://tobion.de>
19 |  */
20 | class DispatchPcntlSignalListener implements EventSubscriberInterface
21 | {
22 |     public function onWorkerRunning(): void
23 |     {
24 |         if (!\function_exists('pcntl_signal_dispatch')) {
25 |             return;
26 |         }
27 | 
28 |         pcntl_signal_dispatch();
29 |     }
30 | 
31 |     public static function getSubscribedEvents(): array
32 |     {
33 |         if (!\function_exists('pcntl_signal_dispatch')) {
34 |             return [];
35 |         }
36 | 
37 |         return [
38 |             WorkerRunningEvent::class => ['onWorkerRunning', 100],
39 |         ];
40 |     }
41 | }
42 | 


--------------------------------------------------------------------------------
/EventListener/ResetMemoryUsageListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 | use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | 
18 | /**
19 |  * @author Tim Düsterhus <tim@tideways-gmbh.com>
20 |  */
21 | final class ResetMemoryUsageListener implements EventSubscriberInterface
22 | {
23 |     private bool $collect = false;
24 | 
25 |     public function resetBefore(WorkerMessageReceivedEvent $event): void
26 |     {
27 |         // Reset the peak memory usage for accurate measurement of the
28 |         // memory usage on a per-message basis.
29 |         memory_reset_peak_usage();
30 |         $this->collect = true;
31 |     }
32 | 
33 |     public function collectAfter(WorkerRunningEvent $event): void
34 |     {
35 |         if ($event->isWorkerIdle() && $this->collect) {
36 |             gc_collect_cycles();
37 |             $this->collect = false;
38 |         }
39 |     }
40 | 
41 |     public static function getSubscribedEvents(): array
42 |     {
43 |         return [
44 |             WorkerMessageReceivedEvent::class => ['resetBefore', -1024],
45 |             WorkerRunningEvent::class => ['collectAfter', -1024],
46 |         ];
47 |     }
48 | }
49 | 


--------------------------------------------------------------------------------
/EventListener/ResetServicesListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 | use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
18 | 
19 | /**
20 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
21 |  */
22 | class ResetServicesListener implements EventSubscriberInterface
23 | {
24 |     public function __construct(
25 |         private ServicesResetter $servicesResetter,
26 |     ) {
27 |     }
28 | 
29 |     public function resetServices(WorkerRunningEvent $event): void
30 |     {
31 |         if (!$event->isWorkerIdle()) {
32 |             $this->servicesResetter->reset();
33 |         }
34 |     }
35 | 
36 |     public function resetServicesAtStop(WorkerStoppedEvent $event): void
37 |     {
38 |         $this->servicesResetter->reset();
39 |     }
40 | 
41 |     public static function getSubscribedEvents(): array
42 |     {
43 |         return [
44 |             WorkerRunningEvent::class => ['resetServices', -1024],
45 |             WorkerStoppedEvent::class => ['resetServicesAtStop', -1024],
46 |         ];
47 |     }
48 | }
49 | 


--------------------------------------------------------------------------------
/EventListener/SendFailedMessageToFailureTransportListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Container\ContainerInterface;
15 | use Psr\Log\LoggerInterface;
16 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17 | use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
18 | use Symfony\Component\Messenger\Event\WorkerMessageSkipEvent;
19 | use Symfony\Component\Messenger\Stamp\DelayStamp;
20 | use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
21 | use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
22 | 
23 | /**
24 |  * Sends a rejected message to a "failure transport".
25 |  *
26 |  * @author Ryan Weaver <ryan@symfonycasts.com>
27 |  */
28 | class SendFailedMessageToFailureTransportListener implements EventSubscriberInterface
29 | {
30 |     public function __construct(
31 |         private ContainerInterface $failureSenders,
32 |         private ?LoggerInterface $logger = null,
33 |     ) {
34 |     }
35 | 
36 |     public function onMessageFailed(WorkerMessageFailedEvent $event): void
37 |     {
38 |         if ($event->willRetry()) {
39 |             return;
40 |         }
41 | 
42 |         if (!$this->failureSenders->has($event->getReceiverName())) {
43 |             return;
44 |         }
45 | 
46 |         $failureSender = $this->failureSenders->get($event->getReceiverName());
47 | 
48 |         $envelope = $event->getEnvelope();
49 | 
50 |         // avoid re-sending to the failed sender
51 |         if (null !== $envelope->last(SentToFailureTransportStamp::class)) {
52 |             return;
53 |         }
54 | 
55 |         $envelope = $envelope->with(
56 |             new SentToFailureTransportStamp($event->getReceiverName()),
57 |             new DelayStamp(0),
58 |             new RedeliveryStamp(0)
59 |         );
60 | 
61 |         $this->logger?->info('Rejected message {class} will be sent to the failure transport {transport}.', [
62 |             'class' => $envelope->getMessage()::class,
63 |             'transport' => $failureSender::class,
64 |         ]);
65 | 
66 |         $failureSender->send($envelope);
67 |     }
68 | 
69 |     public function onMessageSkip(WorkerMessageSkipEvent $event): void
70 |     {
71 |         if (!$this->failureSenders->has($event->getReceiverName())) {
72 |             return;
73 |         }
74 | 
75 |         $failureSender = $this->failureSenders->get($event->getReceiverName());
76 |         $envelope = $event->getEnvelope()->with(
77 |             new SentToFailureTransportStamp($event->getReceiverName()),
78 |             new DelayStamp(0),
79 |         );
80 | 
81 |         $failureSender->send($envelope);
82 |     }
83 | 
84 |     public static function getSubscribedEvents(): array
85 |     {
86 |         return [
87 |             WorkerMessageFailedEvent::class => ['onMessageFailed', -100],
88 |             WorkerMessageSkipEvent::class => ['onMessageSkip', -100],
89 |         ];
90 |     }
91 | }
92 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnCustomStopExceptionListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 | use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | use Symfony\Component\Messenger\Exception\HandlerFailedException;
18 | use Symfony\Component\Messenger\Exception\StopWorkerExceptionInterface;
19 | 
20 | /**
21 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
22 |  */
23 | class StopWorkerOnCustomStopExceptionListener implements EventSubscriberInterface
24 | {
25 |     private bool $stop = false;
26 | 
27 |     public function onMessageFailed(WorkerMessageFailedEvent $event): void
28 |     {
29 |         $th = $event->getThrowable();
30 |         if ($th instanceof StopWorkerExceptionInterface) {
31 |             $this->stop = true;
32 |         }
33 |         if ($th instanceof HandlerFailedException) {
34 |             foreach ($th->getWrappedExceptions() as $e) {
35 |                 if ($e instanceof StopWorkerExceptionInterface) {
36 |                     $this->stop = true;
37 |                     break;
38 |                 }
39 |             }
40 |         }
41 |     }
42 | 
43 |     public function onWorkerRunning(WorkerRunningEvent $event): void
44 |     {
45 |         if ($this->stop) {
46 |             $event->getWorker()->stop();
47 |         }
48 |     }
49 | 
50 |     public static function getSubscribedEvents(): array
51 |     {
52 |         return [
53 |             WorkerMessageFailedEvent::class => 'onMessageFailed',
54 |             WorkerRunningEvent::class => 'onWorkerRunning',
55 |         ];
56 |     }
57 | }
58 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnFailureLimitListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 | use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
17 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
18 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
19 | 
20 | /**
21 |  * @author Michel Hunziker <info@michelhunziker.com>
22 |  */
23 | class StopWorkerOnFailureLimitListener implements EventSubscriberInterface
24 | {
25 |     private int $failedMessages = 0;
26 | 
27 |     public function __construct(
28 |         private int $maximumNumberOfFailures,
29 |         private ?LoggerInterface $logger = null,
30 |     ) {
31 |         if ($maximumNumberOfFailures <= 0) {
32 |             throw new InvalidArgumentException('Failure limit must be greater than zero.');
33 |         }
34 |     }
35 | 
36 |     public function onMessageFailed(WorkerMessageFailedEvent $event): void
37 |     {
38 |         ++$this->failedMessages;
39 |     }
40 | 
41 |     public function onWorkerRunning(WorkerRunningEvent $event): void
42 |     {
43 |         if (!$event->isWorkerIdle() && $this->failedMessages >= $this->maximumNumberOfFailures) {
44 |             $this->failedMessages = 0;
45 |             $event->getWorker()->stop();
46 | 
47 |             $this->logger?->info('Worker stopped due to limit of {count} failed message(s) is reached', ['count' => $this->maximumNumberOfFailures]);
48 |         }
49 |     }
50 | 
51 |     public static function getSubscribedEvents(): array
52 |     {
53 |         return [
54 |             WorkerMessageFailedEvent::class => 'onMessageFailed',
55 |             WorkerRunningEvent::class => 'onWorkerRunning',
56 |         ];
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnMemoryLimitListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | 
18 | /**
19 |  * @author Simon Delicata <simon.delicata@free.fr>
20 |  * @author Tobias Schultze <http://tobion.de>
21 |  */
22 | class StopWorkerOnMemoryLimitListener implements EventSubscriberInterface
23 | {
24 |     private \Closure $memoryResolver;
25 | 
26 |     public function __construct(
27 |         private int $memoryLimit,
28 |         private ?LoggerInterface $logger = null,
29 |         ?callable $memoryResolver = null,
30 |     ) {
31 |         $memoryResolver ??= static fn () => memory_get_usage(true);
32 |         $this->memoryResolver = $memoryResolver(...);
33 |     }
34 | 
35 |     public function onWorkerRunning(WorkerRunningEvent $event): void
36 |     {
37 |         $memoryResolver = $this->memoryResolver;
38 |         $usedMemory = $memoryResolver();
39 |         if ($usedMemory > $this->memoryLimit) {
40 |             $event->getWorker()->stop();
41 |             $this->logger?->info('Worker stopped due to memory limit of {limit} bytes exceeded ({memory} bytes used)', ['limit' => $this->memoryLimit, 'memory' => $usedMemory]);
42 |         }
43 |     }
44 | 
45 |     public static function getSubscribedEvents(): array
46 |     {
47 |         return [
48 |             WorkerRunningEvent::class => 'onWorkerRunning',
49 |         ];
50 |     }
51 | }
52 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnMessageLimitListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
18 | 
19 | /**
20 |  * @author Samuel Roze <samuel.roze@gmail.com>
21 |  * @author Tobias Schultze <http://tobion.de>
22 |  */
23 | class StopWorkerOnMessageLimitListener implements EventSubscriberInterface
24 | {
25 |     private int $receivedMessages = 0;
26 | 
27 |     public function __construct(
28 |         private int $maximumNumberOfMessages,
29 |         private ?LoggerInterface $logger = null,
30 |     ) {
31 |         if ($maximumNumberOfMessages <= 0) {
32 |             throw new InvalidArgumentException('Message limit must be greater than zero.');
33 |         }
34 |     }
35 | 
36 |     public function onWorkerRunning(WorkerRunningEvent $event): void
37 |     {
38 |         if (!$event->isWorkerIdle() && ++$this->receivedMessages >= $this->maximumNumberOfMessages) {
39 |             $this->receivedMessages = 0;
40 |             $event->getWorker()->stop();
41 | 
42 |             $this->logger?->info('Worker stopped due to maximum count of {count} messages processed', ['count' => $this->maximumNumberOfMessages]);
43 |         }
44 |     }
45 | 
46 |     public static function getSubscribedEvents(): array
47 |     {
48 |         return [
49 |             WorkerRunningEvent::class => 'onWorkerRunning',
50 |         ];
51 |     }
52 | }
53 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnRestartSignalListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Cache\CacheItemPoolInterface;
15 | use Psr\Log\LoggerInterface;
16 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
18 | use Symfony\Component\Messenger\Event\WorkerStartedEvent;
19 | 
20 | /**
21 |  * @author Ryan Weaver <ryan@symfonycasts.com>
22 |  */
23 | class StopWorkerOnRestartSignalListener implements EventSubscriberInterface
24 | {
25 |     public const RESTART_REQUESTED_TIMESTAMP_KEY = 'workers.restart_requested_timestamp';
26 | 
27 |     private float $workerStartedAt = 0;
28 | 
29 |     public function __construct(
30 |         private CacheItemPoolInterface $cachePool,
31 |         private ?LoggerInterface $logger = null,
32 |     ) {
33 |     }
34 | 
35 |     public function onWorkerStarted(): void
36 |     {
37 |         $this->workerStartedAt = microtime(true);
38 |     }
39 | 
40 |     public function onWorkerRunning(WorkerRunningEvent $event): void
41 |     {
42 |         if ($this->shouldRestart()) {
43 |             $event->getWorker()->stop();
44 |             $this->logger?->info('Worker stopped because a restart was requested.');
45 |         }
46 |     }
47 | 
48 |     public static function getSubscribedEvents(): array
49 |     {
50 |         return [
51 |             WorkerStartedEvent::class => 'onWorkerStarted',
52 |             WorkerRunningEvent::class => 'onWorkerRunning',
53 |         ];
54 |     }
55 | 
56 |     private function shouldRestart(): bool
57 |     {
58 |         $cacheItem = $this->cachePool->getItem(self::RESTART_REQUESTED_TIMESTAMP_KEY);
59 | 
60 |         if (!$cacheItem->isHit()) {
61 |             // no restart has ever been scheduled
62 |             return false;
63 |         }
64 | 
65 |         return $this->workerStartedAt < $cacheItem->get();
66 |     }
67 | }
68 | 


--------------------------------------------------------------------------------
/EventListener/StopWorkerOnTimeLimitListener.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\EventListener;
13 | 
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 | use Symfony\Component\Messenger\Event\WorkerRunningEvent;
17 | use Symfony\Component\Messenger\Event\WorkerStartedEvent;
18 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
19 | 
20 | /**
21 |  * @author Simon Delicata <simon.delicata@free.fr>
22 |  * @author Tobias Schultze <http://tobion.de>
23 |  */
24 | class StopWorkerOnTimeLimitListener implements EventSubscriberInterface
25 | {
26 |     private float $endTime = 0;
27 | 
28 |     public function __construct(
29 |         private int $timeLimitInSeconds,
30 |         private ?LoggerInterface $logger = null,
31 |     ) {
32 |         if ($timeLimitInSeconds <= 0) {
33 |             throw new InvalidArgumentException('Time limit must be greater than zero.');
34 |         }
35 |     }
36 | 
37 |     public function onWorkerStarted(): void
38 |     {
39 |         $startTime = microtime(true);
40 |         $this->endTime = $startTime + $this->timeLimitInSeconds;
41 |     }
42 | 
43 |     public function onWorkerRunning(WorkerRunningEvent $event): void
44 |     {
45 |         if ($this->endTime < microtime(true)) {
46 |             $event->getWorker()->stop();
47 |             $this->logger?->info('Worker stopped due to time limit of {timeLimit}s exceeded', ['timeLimit' => $this->timeLimitInSeconds]);
48 |         }
49 |     }
50 | 
51 |     public static function getSubscribedEvents(): array
52 |     {
53 |         return [
54 |             WorkerStartedEvent::class => 'onWorkerStarted',
55 |             WorkerRunningEvent::class => 'onWorkerRunning',
56 |         ];
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/Exception/DelayedMessageHandlingException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * When handling queued messages from {@link DispatchAfterCurrentBusMiddleware},
18 |  * some handlers caused an exception. This exception contains all those handler exceptions.
19 |  *
20 |  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
21 |  */
22 | class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
23 | {
24 |     use EnvelopeAwareExceptionTrait;
25 |     use WrappedExceptionsTrait;
26 | 
27 |     public function __construct(
28 |         private array $exceptions,
29 |         ?Envelope $envelope = null,
30 |     ) {
31 |         $this->envelope = $envelope;
32 | 
33 |         $exceptionMessages = implode(", \n", array_map(
34 |             fn (\Throwable $e) => $e::class.': '.$e->getMessage(),
35 |             $exceptions
36 |         ));
37 | 
38 |         if (1 === \count($exceptions)) {
39 |             $message = \sprintf("A delayed message handler threw an exception: \n\n%s", $exceptionMessages);
40 |         } else {
41 |             $message = \sprintf("Some delayed message handlers threw an exception: \n\n%s", $exceptionMessages);
42 |         }
43 | 
44 |         parent::__construct($message, 0, $exceptions[array_key_first($exceptions)]);
45 |     }
46 | }
47 | 


--------------------------------------------------------------------------------
/Exception/EnvelopeAwareExceptionInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | interface EnvelopeAwareExceptionInterface
17 | {
18 |     public function getEnvelope(): ?Envelope;
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/EnvelopeAwareExceptionTrait.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * @internal
18 |  */
19 | trait EnvelopeAwareExceptionTrait
20 | {
21 |     private ?Envelope $envelope = null;
22 | 
23 |     public function getEnvelope(): ?Envelope
24 |     {
25 |         return $this->envelope;
26 |     }
27 | }
28 | 


--------------------------------------------------------------------------------
/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * Base Messenger component's exception.
16 |  *
17 |  * @author Samuel Roze <samuel.roze@gmail.com>
18 |  */
19 | interface ExceptionInterface extends \Throwable
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Exception/HandlerFailedException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
17 | {
18 |     use WrappedExceptionsTrait;
19 | 
20 |     /**
21 |      * @param \Throwable[] $exceptions The name of the handler should be given as key
22 |      */
23 |     public function __construct(
24 |         private Envelope $envelope,
25 |         array $exceptions,
26 |     ) {
27 |         $firstFailure = current($exceptions);
28 | 
29 |         $message = \sprintf('Handling "%s" failed: ', $envelope->getMessage()::class);
30 | 
31 |         parent::__construct(
32 |             $message.(1 === \count($exceptions)
33 |                 ? $firstFailure->getMessage()
34 |                 : \sprintf('%d handlers failed. First failure is: %s', \count($exceptions), $firstFailure->getMessage())
35 |             ),
36 |             (int) $firstFailure->getCode(),
37 |             $firstFailure
38 |         );
39 | 
40 |         $this->exceptions = $exceptions;
41 |     }
42 | 
43 |     public function getEnvelope(): Envelope
44 |     {
45 |         return $this->envelope;
46 |     }
47 | }
48 | 


--------------------------------------------------------------------------------
/Exception/InvalidArgumentException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Yonel Ceruto <yonelceruto@gmail.com>
16 |  */
17 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/LogicException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Roland Franssen <franssen.roland@gmail.com>
16 |  */
17 | class LogicException extends \LogicException implements ExceptionInterface
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/MessageDecodingFailedException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * Thrown when a message cannot be decoded in a serializer.
16 |  */
17 | class MessageDecodingFailedException extends InvalidArgumentException
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/NoHandlerForMessageException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Samuel Roze <samuel.roze@gmail.com>
16 |  */
17 | class NoHandlerForMessageException extends LogicException
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/NoSenderForMessageException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Jérémy Reynaud <jeremy@reynaud.io>
16 |  */
17 | class NoSenderForMessageException extends LogicException
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/RecoverableExceptionInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * Marker interface for exceptions to indicate that handling a message should have worked.
16 |  *
17 |  * If something goes wrong while handling a message that's received from a transport
18 |  * and the message should be retried, a handler can throw such an exception.
19 |  *
20 |  * @author Jérémy Derussé <jeremy@derusse.com>
21 |  *
22 |  * @method int|null getRetryDelay() The time to wait in milliseconds
23 |  */
24 | interface RecoverableExceptionInterface extends \Throwable
25 | {
26 | }
27 | 


--------------------------------------------------------------------------------
/Exception/RecoverableMessageHandlingException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * A concrete implementation of RecoverableExceptionInterface that can be used directly.
16 |  *
17 |  * @author Frederic Bouchery <frederic@bouchery.fr>
18 |  */
19 | class RecoverableMessageHandlingException extends RuntimeException implements RecoverableExceptionInterface
20 | {
21 |     public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, private readonly ?int $retryDelay = null)
22 |     {
23 |         parent::__construct($message, $code, $previous);
24 |     }
25 | 
26 |     public function getRetryDelay(): ?int
27 |     {
28 |         return $this->retryDelay;
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/Exception/RejectRedeliveredMessageException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Tobias Schultze <http://tobion.de>
16 |  */
17 | class RejectRedeliveredMessageException extends RuntimeException
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Fabien Potencier <fabien@symfony.com>
16 |  */
17 | class RuntimeException extends \RuntimeException implements ExceptionInterface
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/StopWorkerException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
16 |  */
17 | class StopWorkerException extends RuntimeException implements StopWorkerExceptionInterface
18 | {
19 |     public function __construct(string $message = 'Worker should stop.', ?\Throwable $previous = null)
20 |     {
21 |         parent::__construct($message, 0, $previous);
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/Exception/StopWorkerExceptionInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
16 |  */
17 | interface StopWorkerExceptionInterface extends \Throwable
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/TransportException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Eric Masoero <em@studeal.fr>
16 |  */
17 | class TransportException extends RuntimeException
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Exception/UnrecoverableExceptionInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * Marker interface for exceptions to indicate that handling a message will continue to fail.
16 |  *
17 |  * If something goes wrong while handling a message that's received from a transport
18 |  * and the message should not be retried, a handler can throw such an exception.
19 |  *
20 |  * @author Tobias Schultze <http://tobion.de>
21 |  */
22 | interface UnrecoverableExceptionInterface extends \Throwable
23 | {
24 | }
25 | 


--------------------------------------------------------------------------------
/Exception/UnrecoverableMessageHandlingException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * A concrete implementation of UnrecoverableExceptionInterface that can be used directly.
16 |  *
17 |  * @author Frederic Bouchery <frederic@bouchery.fr>
18 |  */
19 | class UnrecoverableMessageHandlingException extends RuntimeException implements UnrecoverableExceptionInterface
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Exception/ValidationFailedException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Validator\ConstraintViolationListInterface;
16 | 
17 | /**
18 |  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
19 |  */
20 | class ValidationFailedException extends RuntimeException implements EnvelopeAwareExceptionInterface
21 | {
22 |     use EnvelopeAwareExceptionTrait;
23 | 
24 |     public function __construct(
25 |         private object $violatingMessage,
26 |         private ConstraintViolationListInterface $violations,
27 |         ?Envelope $envelope = null,
28 |     ) {
29 |         $this->envelope = $envelope;
30 | 
31 |         parent::__construct(\sprintf('Message of type "%s" failed validation.', $this->violatingMessage::class));
32 |     }
33 | 
34 |     public function getViolatingMessage(): object
35 |     {
36 |         return $this->violatingMessage;
37 |     }
38 | 
39 |     public function getViolations(): ConstraintViolationListInterface
40 |     {
41 |         return $this->violations;
42 |     }
43 | }
44 | 


--------------------------------------------------------------------------------
/Exception/WrappedExceptionsInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * Exception that holds multiple exceptions thrown by one or more handlers and/or messages.
16 |  *
17 |  * @author Jeroen <https://github.com/Jeroeny>
18 |  */
19 | interface WrappedExceptionsInterface extends \Throwable
20 | {
21 |     /**
22 |      * @template TException of \Throwable
23 |      *
24 |      * @param class-string<TException>|null $class
25 |      *
26 |      * @return \Throwable[]
27 |      *
28 |      * @psalm-return ($class is null ? \Throwable[] : TException[])
29 |      */
30 |     public function getWrappedExceptions(?string $class = null, bool $recursive = false): array;
31 | }
32 | 


--------------------------------------------------------------------------------
/Exception/WrappedExceptionsTrait.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Exception;
13 | 
14 | /**
15 |  * @author Jeroen <https://github.com/Jeroeny>
16 |  *
17 |  * @internal
18 |  */
19 | trait WrappedExceptionsTrait
20 | {
21 |     private array $exceptions;
22 | 
23 |     /**
24 |      * @template TException of \Throwable
25 |      *
26 |      * @param class-string<TException>|null $class
27 |      *
28 |      * @return \Throwable[]
29 |      *
30 |      * @psalm-return ($class is null ? \Throwable[] : TException[])
31 |      */
32 |     public function getWrappedExceptions(?string $class = null, bool $recursive = false): array
33 |     {
34 |         return $this->getWrappedExceptionsRecursively($class, $recursive, $this->exceptions);
35 |     }
36 | 
37 |     /**
38 |      * @param class-string<\Throwable>|null $class
39 |      * @param iterable<\Throwable>          $exceptions
40 |      *
41 |      * @return \Throwable[]
42 |      */
43 |     private function getWrappedExceptionsRecursively(?string $class, bool $recursive, iterable $exceptions): array
44 |     {
45 |         $unwrapped = [];
46 |         foreach ($exceptions as $key => $exception) {
47 |             if ($recursive && $exception instanceof WrappedExceptionsInterface) {
48 |                 $unwrapped[] = $this->getWrappedExceptionsRecursively($class, $recursive, $exception->getWrappedExceptions());
49 | 
50 |                 continue;
51 |             }
52 | 
53 |             if ($class && !is_a($exception, $class)) {
54 |                 continue;
55 |             }
56 | 
57 |             $unwrapped[] = [$key => $exception];
58 |         }
59 | 
60 |         return array_merge(...$unwrapped);
61 |     }
62 | }
63 | 


--------------------------------------------------------------------------------
/HandleTrait.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger;
13 | 
14 | use Symfony\Component\Messenger\Exception\LogicException;
15 | use Symfony\Component\Messenger\Stamp\HandledStamp;
16 | use Symfony\Component\Messenger\Stamp\StampInterface;
17 | 
18 | /**
19 |  * Leverages a message bus to expect a single, synchronous message handling and return its result.
20 |  *
21 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
22 |  */
23 | trait HandleTrait
24 | {
25 |     private MessageBusInterface $messageBus;
26 | 
27 |     /**
28 |      * Dispatches the given message, expecting to be handled by a single handler
29 |      * and returns the result from the handler returned value.
30 |      * This behavior is useful for both synchronous command & query buses,
31 |      * the last one usually returning the handler result.
32 |      *
33 |      * @param object|Envelope  $message The message or the message pre-wrapped in an envelope
34 |      * @param StampInterface[] $stamps  Stamps to be set on the Envelope which are used to control middleware behavior
35 |      */
36 |     private function handle(object $message, array $stamps = []): mixed
37 |     {
38 |         if (!isset($this->messageBus)) {
39 |             throw new LogicException(\sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, but that property has not been initialized yet.', MessageBusInterface::class, static::class));
40 |         }
41 | 
42 |         $envelope = $this->messageBus->dispatch($message, $stamps);
43 |         /** @var HandledStamp[] $handledStamps */
44 |         $handledStamps = $envelope->all(HandledStamp::class);
45 | 
46 |         if (!$handledStamps) {
47 |             throw new LogicException(\sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', get_debug_type($envelope->getMessage()), static::class, __FUNCTION__));
48 |         }
49 | 
50 |         if (\count($handledStamps) > 1) {
51 |             $handlers = implode(', ', array_map(fn (HandledStamp $stamp): string => \sprintf('"%s"', $stamp->getHandlerName()), $handledStamps));
52 | 
53 |             throw new LogicException(\sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: %s.', get_debug_type($envelope->getMessage()), static::class, __FUNCTION__, \count($handledStamps), $handlers));
54 |         }
55 | 
56 |         return $handledStamps[0]->getResult();
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/Handler/Acknowledger.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | use Symfony\Component\Messenger\Exception\LogicException;
15 | 
16 | /**
17 |  * @author Nicolas Grekas <p@tchwork.com>
18 |  */
19 | class Acknowledger
20 | {
21 |     private ?\Closure $ack;
22 |     private ?\Throwable $error = null;
23 |     private mixed $result = null;
24 | 
25 |     /**
26 |      * @param \Closure(\Throwable|null, mixed):void|null $ack
27 |      */
28 |     public function __construct(
29 |         private string $handlerClass,
30 |         ?\Closure $ack = null,
31 |     ) {
32 |         $this->ack = $ack ?? static function () {};
33 |     }
34 | 
35 |     /**
36 |      * @param mixed $result
37 |      */
38 |     public function ack($result = null): void
39 |     {
40 |         $this->doAck(null, $result);
41 |     }
42 | 
43 |     public function nack(\Throwable $error): void
44 |     {
45 |         $this->doAck($error);
46 |     }
47 | 
48 |     public function getError(): ?\Throwable
49 |     {
50 |         return $this->error;
51 |     }
52 | 
53 |     public function getResult(): mixed
54 |     {
55 |         return $this->result;
56 |     }
57 | 
58 |     public function isAcknowledged(): bool
59 |     {
60 |         return null === $this->ack;
61 |     }
62 | 
63 |     public function __destruct()
64 |     {
65 |         if (null !== $this->ack) {
66 |             throw new LogicException(\sprintf('The acknowledger was not called by the "%s" batch handler.', $this->handlerClass));
67 |         }
68 |     }
69 | 
70 |     private function doAck(?\Throwable $e = null, mixed $result = null): void
71 |     {
72 |         if (!$ack = $this->ack) {
73 |             throw new LogicException(\sprintf('The acknowledger cannot be called twice by the "%s" batch handler.', $this->handlerClass));
74 |         }
75 |         $this->ack = null;
76 |         $this->error = $e;
77 |         $this->result = $result;
78 |         $ack($e, $result);
79 |     }
80 | }
81 | 


--------------------------------------------------------------------------------
/Handler/BatchHandlerInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | /**
15 |  * @author Nicolas Grekas <p@tchwork.com>
16 |  */
17 | interface BatchHandlerInterface
18 | {
19 |     /**
20 |      * @param Acknowledger|null $ack The function to call to ack/nack the $message.
21 |      *                               The message should be handled synchronously when null.
22 |      *
23 |      * @return mixed The number of pending messages in the batch if $ack is not null,
24 |      *               the result from handling the message otherwise
25 |      */
26 |     // public function __invoke(object $message, ?Acknowledger $ack = null): mixed;
27 | 
28 |     /**
29 |      * Flushes any pending buffers.
30 |      *
31 |      * @param bool $force Whether flushing is required; it can be skipped if not
32 |      */
33 |     public function flush(bool $force): void;
34 | }
35 | 


--------------------------------------------------------------------------------
/Handler/BatchHandlerTrait.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | /**
15 |  * @author Nicolas Grekas <p@tchwork.com>
16 |  */
17 | trait BatchHandlerTrait
18 | {
19 |     private array $jobs = [];
20 | 
21 |     public function flush(bool $force): void
22 |     {
23 |         if ($jobs = $this->jobs) {
24 |             $this->jobs = [];
25 |             $this->process($jobs);
26 |         }
27 |     }
28 | 
29 |     /**
30 |      * @param Acknowledger|null $ack The function to call to ack/nack the $message.
31 |      *                               The message should be handled synchronously when null.
32 |      *
33 |      * @return mixed The number of pending messages in the batch if $ack is not null,
34 |      *               the result from handling the message otherwise
35 |      */
36 |     private function handle(object $message, ?Acknowledger $ack): mixed
37 |     {
38 |         if (null === $ack) {
39 |             $ack = new Acknowledger(get_debug_type($this));
40 |             $this->jobs[] = [$message, $ack];
41 |             $this->flush(true);
42 | 
43 |             return $ack->getResult();
44 |         }
45 | 
46 |         $this->jobs[] = [$message, $ack];
47 |         if (!$this->shouldFlush()) {
48 |             return \count($this->jobs);
49 |         }
50 | 
51 |         $this->flush(true);
52 | 
53 |         return 0;
54 |     }
55 | 
56 |     private function shouldFlush(): bool
57 |     {
58 |         return $this->getBatchSize() <= \count($this->jobs);
59 |     }
60 | 
61 |     /**
62 |      * Completes the jobs in the list.
63 |      *
64 |      * @param list<array{0: object, 1: Acknowledger}> $jobs A list of pairs of messages and their corresponding acknowledgers
65 |      */
66 |     abstract private function process(array $jobs): void;
67 | 
68 |     private function getBatchSize(): int
69 |     {
70 |         return 10;
71 |     }
72 | }
73 | 


--------------------------------------------------------------------------------
/Handler/HandlerDescriptor.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | /**
15 |  * Describes a handler and the possible associated options, such as `from_transport`, `bus`, etc.
16 |  *
17 |  * @author Samuel Roze <samuel.roze@gmail.com>
18 |  */
19 | final class HandlerDescriptor
20 | {
21 |     private \Closure $handler;
22 |     private string $name;
23 |     private ?BatchHandlerInterface $batchHandler = null;
24 | 
25 |     public function __construct(
26 |         callable $handler,
27 |         private array $options = [],
28 |     ) {
29 |         $handler = $handler(...);
30 | 
31 |         $this->handler = $handler;
32 | 
33 |         $r = new \ReflectionFunction($handler);
34 | 
35 |         if ($r->isAnonymous()) {
36 |             $this->name = 'Closure';
37 |         } elseif (!$handler = $r->getClosureThis()) {
38 |             $class = $r->getClosureCalledClass();
39 | 
40 |             $this->name = ($class ? $class->name.'::' : '').$r->name;
41 |         } else {
42 |             if ($handler instanceof BatchHandlerInterface) {
43 |                 $this->batchHandler = $handler;
44 |             }
45 | 
46 |             $this->name = $handler::class.'::'.$r->name;
47 |         }
48 |     }
49 | 
50 |     public function getHandler(): \Closure
51 |     {
52 |         return $this->handler;
53 |     }
54 | 
55 |     public function getName(): string
56 |     {
57 |         $name = $this->name;
58 |         $alias = $this->options['alias'] ?? null;
59 | 
60 |         if (null !== $alias) {
61 |             $name .= '@'.$alias;
62 |         }
63 | 
64 |         return $name;
65 |     }
66 | 
67 |     public function getBatchHandler(): ?BatchHandlerInterface
68 |     {
69 |         return $this->batchHandler;
70 |     }
71 | 
72 |     public function getOption(string $option): mixed
73 |     {
74 |         return $this->options[$option] ?? null;
75 |     }
76 | 
77 |     public function getOptions(): array
78 |     {
79 |         return $this->options;
80 |     }
81 | }
82 | 


--------------------------------------------------------------------------------
/Handler/HandlersLocator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Stamp\ReceivedStamp;
16 | 
17 | /**
18 |  * Maps a message to a list of handlers.
19 |  *
20 |  * @author Nicolas Grekas <p@tchwork.com>
21 |  * @author Samuel Roze <samuel.roze@gmail.com>
22 |  */
23 | class HandlersLocator implements HandlersLocatorInterface
24 | {
25 |     /**
26 |      * @param HandlerDescriptor[][]|callable[][] $handlers
27 |      */
28 |     public function __construct(
29 |         private array $handlers,
30 |     ) {
31 |     }
32 | 
33 |     public function getHandlers(Envelope $envelope): iterable
34 |     {
35 |         $seen = [];
36 | 
37 |         foreach (self::listTypes($envelope) as $type) {
38 |             foreach ($this->handlers[$type] ?? [] as $handlerDescriptor) {
39 |                 if (\is_callable($handlerDescriptor)) {
40 |                     $handlerDescriptor = new HandlerDescriptor($handlerDescriptor);
41 |                 }
42 | 
43 |                 if (!$this->shouldHandle($envelope, $handlerDescriptor)) {
44 |                     continue;
45 |                 }
46 | 
47 |                 $name = $handlerDescriptor->getName();
48 |                 if (\in_array($name, $seen, true)) {
49 |                     continue;
50 |                 }
51 | 
52 |                 $seen[] = $name;
53 | 
54 |                 yield $handlerDescriptor;
55 |             }
56 |         }
57 |     }
58 | 
59 |     /**
60 |      * @internal
61 |      */
62 |     public static function listTypes(Envelope $envelope): array
63 |     {
64 |         $class = $envelope->getMessage()::class;
65 | 
66 |         return [$class => $class]
67 |             + class_parents($class)
68 |             + class_implements($class)
69 |             + self::listWildcards($class)
70 |             + ['*' => '*'];
71 |     }
72 | 
73 |     private static function listWildcards(string $type): array
74 |     {
75 |         $type .= '\*';
76 |         $wildcards = [];
77 |         while ($i = strrpos($type, '\\', -3)) {
78 |             $type = substr_replace($type, '\*', $i);
79 |             $wildcards[$type] = $type;
80 |         }
81 | 
82 |         return $wildcards;
83 |     }
84 | 
85 |     private function shouldHandle(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool
86 |     {
87 |         if (null === $received = $envelope->last(ReceivedStamp::class)) {
88 |             return true;
89 |         }
90 | 
91 |         if (null === $expectedTransport = $handlerDescriptor->getOption('from_transport')) {
92 |             return true;
93 |         }
94 | 
95 |         return $received->getTransportName() === $expectedTransport;
96 |     }
97 | }
98 | 


--------------------------------------------------------------------------------
/Handler/HandlersLocatorInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Maps a message to a list of handlers.
18 |  *
19 |  * @author Nicolas Grekas <p@tchwork.com>
20 |  */
21 | interface HandlersLocatorInterface
22 | {
23 |     /**
24 |      * Returns the handlers for the given message name.
25 |      *
26 |      * @return iterable<int, HandlerDescriptor>
27 |      */
28 |     public function getHandlers(Envelope $envelope): iterable;
29 | }
30 | 


--------------------------------------------------------------------------------
/Handler/RedispatchMessageHandler.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Handler;
13 | 
14 | use Symfony\Component\Messenger\Message\RedispatchMessage;
15 | use Symfony\Component\Messenger\MessageBusInterface;
16 | use Symfony\Component\Messenger\Stamp\HandledStamp;
17 | use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
18 | 
19 | final class RedispatchMessageHandler
20 | {
21 |     public function __construct(
22 |         private MessageBusInterface $bus,
23 |     ) {
24 |     }
25 | 
26 |     public function __invoke(RedispatchMessage $message): mixed
27 |     {
28 |         $envelope = $this->bus->dispatch($message->envelope, [new TransportNamesStamp($message->transportNames)]);
29 | 
30 |         return $envelope->last(HandledStamp::class)?->getResult();
31 |     }
32 | }
33 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | Copyright (c) 2018-present Fabien Potencier
 2 | 
 3 | Permission is hereby granted, free of charge, to any person obtaining a copy
 4 | of this software and associated documentation files (the "Software"), to deal
 5 | in the Software without restriction, including without limitation the rights
 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 7 | copies of the Software, and to permit persons to whom the Software is furnished
 8 | to do so, subject to the following conditions:
 9 | 
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 | 
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 | 


--------------------------------------------------------------------------------
/Message/RedispatchMessage.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Message;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | final class RedispatchMessage implements \Stringable
17 | {
18 |     /**
19 |      * @param object|Envelope $envelope       The message or the message pre-wrapped in an envelope
20 |      * @param string[]|string $transportNames Transport names to be used for the message
21 |      */
22 |     public function __construct(
23 |         public readonly object $envelope,
24 |         public readonly array|string $transportNames = [],
25 |     ) {
26 |     }
27 | 
28 |     public function __toString(): string
29 |     {
30 |         $message = $this->envelope instanceof Envelope ? $this->envelope->getMessage() : $this->envelope;
31 | 
32 |         return \sprintf('%s via %s', $message instanceof \Stringable ? (string) $message : $message::class, implode(', ', (array) $this->transportNames));
33 |     }
34 | }
35 | 


--------------------------------------------------------------------------------
/MessageBus.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger;
13 | 
14 | use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
15 | use Symfony\Component\Messenger\Middleware\StackMiddleware;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  * @author Matthias Noback <matthiasnoback@gmail.com>
20 |  * @author Nicolas Grekas <p@tchwork.com>
21 |  */
22 | class MessageBus implements MessageBusInterface
23 | {
24 |     private \IteratorAggregate $middlewareAggregate;
25 | 
26 |     /**
27 |      * @param iterable<mixed, MiddlewareInterface> $middlewareHandlers
28 |      */
29 |     public function __construct(iterable $middlewareHandlers = [])
30 |     {
31 |         if ($middlewareHandlers instanceof \IteratorAggregate) {
32 |             $this->middlewareAggregate = $middlewareHandlers;
33 |         } elseif (\is_array($middlewareHandlers)) {
34 |             $this->middlewareAggregate = new \ArrayObject($middlewareHandlers);
35 |         } else {
36 |             // $this->middlewareAggregate should be an instance of IteratorAggregate.
37 |             // When $middlewareHandlers is an Iterator, we wrap it to ensure it is lazy-loaded and can be rewound.
38 |             $this->middlewareAggregate = new class($middlewareHandlers) implements \IteratorAggregate {
39 |                 private \ArrayObject $cachedIterator;
40 | 
41 |                 public function __construct(
42 |                     private \Traversable $middlewareHandlers,
43 |                 ) {
44 |                 }
45 | 
46 |                 public function getIterator(): \Traversable
47 |                 {
48 |                     return $this->cachedIterator ??= new \ArrayObject(iterator_to_array($this->middlewareHandlers, false));
49 |                 }
50 |             };
51 |         }
52 |     }
53 | 
54 |     public function dispatch(object $message, array $stamps = []): Envelope
55 |     {
56 |         $envelope = Envelope::wrap($message, $stamps);
57 |         $middlewareIterator = $this->middlewareAggregate->getIterator();
58 | 
59 |         while ($middlewareIterator instanceof \IteratorAggregate) {
60 |             $middlewareIterator = $middlewareIterator->getIterator();
61 |         }
62 |         $middlewareIterator->rewind();
63 | 
64 |         if (!$middlewareIterator->valid()) {
65 |             return $envelope;
66 |         }
67 |         $stack = new StackMiddleware($middlewareIterator);
68 | 
69 |         return $middlewareIterator->current()->handle($envelope, $stack);
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/MessageBusInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger;
13 | 
14 | use Symfony\Component\Messenger\Exception\ExceptionInterface;
15 | use Symfony\Component\Messenger\Stamp\StampInterface;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  */
20 | interface MessageBusInterface
21 | {
22 |     /**
23 |      * Dispatches the given message.
24 |      *
25 |      * @param object|Envelope  $message The message or the message pre-wrapped in an envelope
26 |      * @param StampInterface[] $stamps  Stamps set on the Envelope which are used to control middleware behavior
27 |      *
28 |      * @throws ExceptionInterface
29 |      */
30 |     public function dispatch(object $message, array $stamps = []): Envelope;
31 | }
32 | 


--------------------------------------------------------------------------------
/Middleware/ActivationMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Execute the inner middleware according to an activation strategy.
18 |  *
19 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
20 |  */
21 | class ActivationMiddleware implements MiddlewareInterface
22 | {
23 |     private \Closure|bool $activated;
24 | 
25 |     public function __construct(
26 |         private MiddlewareInterface $inner,
27 |         bool|callable $activated,
28 |     ) {
29 |         $this->activated = \is_bool($activated) ? $activated : $activated(...);
30 |     }
31 | 
32 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
33 |     {
34 |         if (\is_callable($this->activated) ? ($this->activated)($envelope) : $this->activated) {
35 |             return $this->inner->handle($envelope, $stack);
36 |         }
37 | 
38 |         return $stack->next()->handle($envelope, $stack);
39 |     }
40 | }
41 | 


--------------------------------------------------------------------------------
/Middleware/AddBusNameStampMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Stamp\BusNameStamp;
16 | 
17 | /**
18 |  * Adds the BusNameStamp to the bus.
19 |  *
20 |  * @author Ryan Weaver <ryan@symfonycasts.com>
21 |  */
22 | class AddBusNameStampMiddleware implements MiddlewareInterface
23 | {
24 |     public function __construct(
25 |         private string $busName,
26 |     ) {
27 |     }
28 | 
29 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
30 |     {
31 |         if (null === $envelope->last(BusNameStamp::class)) {
32 |             $envelope = $envelope->with(new BusNameStamp($this->busName));
33 |         }
34 | 
35 |         return $stack->next()->handle($envelope, $stack);
36 |     }
37 | }
38 | 


--------------------------------------------------------------------------------
/Middleware/DeduplicateMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Lock\LockFactory;
15 | use Symfony\Component\Messenger\Envelope;
16 | use Symfony\Component\Messenger\Stamp\DeduplicateStamp;
17 | use Symfony\Component\Messenger\Stamp\ReceivedStamp;
18 | 
19 | final class DeduplicateMiddleware implements MiddlewareInterface
20 | {
21 |     public function __construct(private LockFactory $lockFactory)
22 |     {
23 |     }
24 | 
25 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
26 |     {
27 |         if (!$stamp = $envelope->last(DeduplicateStamp::class)) {
28 |             return $stack->next()->handle($envelope, $stack);
29 |         }
30 | 
31 |         if (!$envelope->last(ReceivedStamp::class)) {
32 |             $lock = $this->lockFactory->createLockFromKey($stamp->getKey(), $stamp->getTtl(), false);
33 | 
34 |             if (!$lock->acquire()) {
35 |                 return $envelope;
36 |             }
37 |         } elseif ($stamp->onlyDeduplicateInQueue()) {
38 |             $this->lockFactory->createLockFromKey($stamp->getKey())->release();
39 |         }
40 | 
41 |         try {
42 |             $envelope = $stack->next()->handle($envelope, $stack);
43 |         } finally {
44 |             if ($envelope->last(ReceivedStamp::class) && !$stamp->onlyDeduplicateInQueue()) {
45 |                 $this->lockFactory->createLockFromKey($stamp->getKey())->release();
46 |             }
47 |         }
48 | 
49 |         return $envelope;
50 |     }
51 | }
52 | 


--------------------------------------------------------------------------------
/Middleware/DispatchAfterCurrentBusMiddleware.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Middleware;
 13 | 
 14 | use Symfony\Component\Messenger\Envelope;
 15 | use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
 16 | use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
 17 | 
 18 | /**
 19 |  * Allow to configure messages to be handled after the current bus is finished.
 20 |  *
 21 |  * I.e, messages dispatched from a handler with a DispatchAfterCurrentBus stamp
 22 |  * will actually be handled once the current message being dispatched is fully
 23 |  * handled.
 24 |  *
 25 |  * For instance, using this middleware before the DoctrineTransactionMiddleware
 26 |  * means sub-dispatched messages with a DispatchAfterCurrentBus stamp would be
 27 |  * handled after the Doctrine transaction has been committed.
 28 |  *
 29 |  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 30 |  */
 31 | class DispatchAfterCurrentBusMiddleware implements MiddlewareInterface
 32 | {
 33 |     /**
 34 |      * @var QueuedEnvelope[] A queue of messages and next middleware
 35 |      */
 36 |     private array $queue = [];
 37 | 
 38 |     /**
 39 |      * @var bool this property is used to signal if we are inside a the first/root call to
 40 |      *           MessageBusInterface::dispatch() or if dispatch has been called inside a message handler
 41 |      */
 42 |     private bool $isRootDispatchCallRunning = false;
 43 | 
 44 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
 45 |     {
 46 |         if (null !== $envelope->last(DispatchAfterCurrentBusStamp::class)) {
 47 |             if ($this->isRootDispatchCallRunning) {
 48 |                 $this->queue[] = new QueuedEnvelope($envelope, $stack);
 49 | 
 50 |                 return $envelope;
 51 |             }
 52 | 
 53 |             $envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class);
 54 |         }
 55 | 
 56 |         if ($this->isRootDispatchCallRunning) {
 57 |             /*
 58 |              * A call to MessageBusInterface::dispatch() was made from inside the main bus handling,
 59 |              * but the message does not have the stamp. So, process it like normal.
 60 |              */
 61 |             return $stack->next()->handle($envelope, $stack);
 62 |         }
 63 | 
 64 |         // First time we get here, mark as inside a "root dispatch" call:
 65 |         $this->isRootDispatchCallRunning = true;
 66 |         try {
 67 |             // Execute the whole middleware stack & message handling for main dispatch:
 68 |             $returnedEnvelope = $stack->next()->handle($envelope, $stack);
 69 |         } catch (\Throwable $exception) {
 70 |             /*
 71 |              * Whenever an exception occurs while handling a message that has
 72 |              * queued other messages, we drop the queued ones.
 73 |              * This is intentional since the queued commands were likely dependent
 74 |              * on the preceding command.
 75 |              */
 76 |             $this->queue = [];
 77 |             $this->isRootDispatchCallRunning = false;
 78 | 
 79 |             throw $exception;
 80 |         }
 81 | 
 82 |         // "Root dispatch" call is finished, dispatch stored messages.
 83 |         $exceptions = [];
 84 |         while (null !== $queueItem = array_shift($this->queue)) {
 85 |             // Save how many messages are left in queue before handling the message
 86 |             $queueLengthBefore = \count($this->queue);
 87 |             try {
 88 |                 // Execute the stored messages
 89 |                 $queueItem->getStack()->next()->handle($queueItem->getEnvelope(), $queueItem->getStack());
 90 |             } catch (\Exception $exception) {
 91 |                 // Gather all exceptions
 92 |                 $exceptions[] = $exception;
 93 |                 // Restore queue to previous state
 94 |                 $this->queue = \array_slice($this->queue, 0, $queueLengthBefore);
 95 |             }
 96 |         }
 97 | 
 98 |         $this->isRootDispatchCallRunning = false;
 99 |         if (\count($exceptions) > 0) {
100 |             throw new DelayedMessageHandlingException($exceptions, $returnedEnvelope);
101 |         }
102 | 
103 |         return $returnedEnvelope;
104 |     }
105 | }
106 | 
107 | /**
108 |  * @internal
109 |  */
110 | final class QueuedEnvelope
111 | {
112 |     private Envelope $envelope;
113 | 
114 |     public function __construct(
115 |         Envelope $envelope,
116 |         private StackInterface $stack,
117 |     ) {
118 |         $this->envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class);
119 |     }
120 | 
121 |     public function getEnvelope(): Envelope
122 |     {
123 |         return $this->envelope;
124 |     }
125 | 
126 |     public function getStack(): StackInterface
127 |     {
128 |         return $this->stack;
129 |     }
130 | }
131 | 


--------------------------------------------------------------------------------
/Middleware/FailedMessageProcessingMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Stamp\ReceivedStamp;
16 | use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
17 | 
18 | /**
19 |  * @author Ryan Weaver <ryan@symfonycasts.com>
20 |  */
21 | class FailedMessageProcessingMiddleware implements MiddlewareInterface
22 | {
23 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
24 |     {
25 |         // look for "received" messages decorated with the SentToFailureTransportStamp
26 |         /** @var SentToFailureTransportStamp|null $sentToFailureStamp */
27 |         $sentToFailureStamp = $envelope->last(SentToFailureTransportStamp::class);
28 |         if (null !== $sentToFailureStamp && null !== $envelope->last(ReceivedStamp::class)) {
29 |             // mark the message as "received" from the original transport
30 |             // this guarantees the same behavior as when originally received
31 |             $envelope = $envelope->with(new ReceivedStamp($sentToFailureStamp->getOriginalReceiverName()));
32 |         }
33 | 
34 |         return $stack->next()->handle($envelope, $stack);
35 |     }
36 | }
37 | 


--------------------------------------------------------------------------------
/Middleware/HandleMessageMiddleware.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Middleware;
 13 | 
 14 | use Psr\Log\LoggerAwareTrait;
 15 | use Symfony\Component\Messenger\Envelope;
 16 | use Symfony\Component\Messenger\Exception\HandlerFailedException;
 17 | use Symfony\Component\Messenger\Exception\LogicException;
 18 | use Symfony\Component\Messenger\Exception\NoHandlerForMessageException;
 19 | use Symfony\Component\Messenger\Handler\Acknowledger;
 20 | use Symfony\Component\Messenger\Handler\HandlerDescriptor;
 21 | use Symfony\Component\Messenger\Handler\HandlersLocatorInterface;
 22 | use Symfony\Component\Messenger\Stamp\AckStamp;
 23 | use Symfony\Component\Messenger\Stamp\FlushBatchHandlersStamp;
 24 | use Symfony\Component\Messenger\Stamp\HandledStamp;
 25 | use Symfony\Component\Messenger\Stamp\HandlerArgumentsStamp;
 26 | use Symfony\Component\Messenger\Stamp\NoAutoAckStamp;
 27 | 
 28 | /**
 29 |  * @author Samuel Roze <samuel.roze@gmail.com>
 30 |  */
 31 | class HandleMessageMiddleware implements MiddlewareInterface
 32 | {
 33 |     use LoggerAwareTrait;
 34 | 
 35 |     public function __construct(
 36 |         private HandlersLocatorInterface $handlersLocator,
 37 |         private bool $allowNoHandlers = false,
 38 |     ) {
 39 |     }
 40 | 
 41 |     /**
 42 |      * @throws NoHandlerForMessageException When no handler is found and $allowNoHandlers is false
 43 |      */
 44 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
 45 |     {
 46 |         $handler = null;
 47 |         $message = $envelope->getMessage();
 48 | 
 49 |         $context = [
 50 |             'class' => $message::class,
 51 |         ];
 52 | 
 53 |         $exceptions = [];
 54 |         $alreadyHandled = false;
 55 |         foreach ($this->handlersLocator->getHandlers($envelope) as $handlerDescriptor) {
 56 |             if ($this->messageHasAlreadyBeenHandled($envelope, $handlerDescriptor)) {
 57 |                 $alreadyHandled = true;
 58 |                 continue;
 59 |             }
 60 | 
 61 |             try {
 62 |                 $handler = $handlerDescriptor->getHandler();
 63 |                 $batchHandler = $handlerDescriptor->getBatchHandler();
 64 | 
 65 |                 /** @var AckStamp $ackStamp */
 66 |                 if ($batchHandler && $ackStamp = $envelope->last(AckStamp::class)) {
 67 |                     $ack = new Acknowledger(get_debug_type($batchHandler), static function (?\Throwable $e = null, $result = null) use ($envelope, $ackStamp, $handlerDescriptor) {
 68 |                         if (null !== $e) {
 69 |                             $e = new HandlerFailedException($envelope, [$handlerDescriptor->getName() => $e]);
 70 |                         } else {
 71 |                             $envelope = $envelope->with(HandledStamp::fromDescriptor($handlerDescriptor, $result));
 72 |                         }
 73 | 
 74 |                         $ackStamp->ack($envelope, $e);
 75 |                     });
 76 | 
 77 |                     $result = $this->callHandler($handler, $message, $ack, $envelope->last(HandlerArgumentsStamp::class));
 78 | 
 79 |                     if (!\is_int($result) || 0 > $result) {
 80 |                         throw new LogicException(\sprintf('A handler implementing BatchHandlerInterface must return the size of the current batch as a positive integer, "%s" returned from "%s".', \is_int($result) ? $result : get_debug_type($result), get_debug_type($batchHandler)));
 81 |                     }
 82 | 
 83 |                     if (!$ack->isAcknowledged()) {
 84 |                         $envelope = $envelope->with(new NoAutoAckStamp($handlerDescriptor));
 85 |                     } elseif ($ack->getError()) {
 86 |                         throw $ack->getError();
 87 |                     } else {
 88 |                         $result = $ack->getResult();
 89 |                     }
 90 |                 } else {
 91 |                     $result = $this->callHandler($handler, $message, null, $envelope->last(HandlerArgumentsStamp::class));
 92 |                 }
 93 | 
 94 |                 $handledStamp = HandledStamp::fromDescriptor($handlerDescriptor, $result);
 95 |                 $envelope = $envelope->with($handledStamp);
 96 |                 $this->logger?->info('Message {class} handled by {handler}', $context + ['handler' => $handledStamp->getHandlerName()]);
 97 |             } catch (\Throwable $e) {
 98 |                 $exceptions[$handlerDescriptor->getName()] = $e;
 99 |             }
100 |         }
101 | 
102 |         /** @var FlushBatchHandlersStamp $flushStamp */
103 |         if ($flushStamp = $envelope->last(FlushBatchHandlersStamp::class)) {
104 |             /** @var NoAutoAckStamp $stamp */
105 |             foreach ($envelope->all(NoAutoAckStamp::class) as $stamp) {
106 |                 try {
107 |                     $handler = $stamp->getHandlerDescriptor()->getBatchHandler();
108 |                     $handler->flush($flushStamp->force());
109 |                 } catch (\Throwable $e) {
110 |                     $exceptions[$stamp->getHandlerDescriptor()->getName()] = $e;
111 |                 }
112 |             }
113 |         }
114 | 
115 |         if (null === $handler && !$alreadyHandled) {
116 |             if (!$this->allowNoHandlers) {
117 |                 throw new NoHandlerForMessageException(\sprintf('No handler for message "%s".', $context['class']));
118 |             }
119 | 
120 |             $this->logger?->info('No handler for message {class}', $context);
121 |         }
122 | 
123 |         if (\count($exceptions)) {
124 |             throw new HandlerFailedException($envelope, $exceptions);
125 |         }
126 | 
127 |         return $stack->next()->handle($envelope, $stack);
128 |     }
129 | 
130 |     private function messageHasAlreadyBeenHandled(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool
131 |     {
132 |         /** @var HandledStamp $stamp */
133 |         foreach ($envelope->all(HandledStamp::class) as $stamp) {
134 |             if ($stamp->getHandlerName() === $handlerDescriptor->getName()) {
135 |                 return true;
136 |             }
137 |         }
138 | 
139 |         return false;
140 |     }
141 | 
142 |     private function callHandler(\Closure $handler, object $message, ?Acknowledger $ack, ?HandlerArgumentsStamp $handlerArgumentsStamp): mixed
143 |     {
144 |         $arguments = [$message];
145 |         if (null !== $ack) {
146 |             $arguments[] = $ack;
147 |         }
148 |         if (null !== $handlerArgumentsStamp) {
149 |             $arguments = [...$arguments, ...$handlerArgumentsStamp->getAdditionalArguments()];
150 |         }
151 | 
152 |         return $handler(...$arguments);
153 |     }
154 | }
155 | 


--------------------------------------------------------------------------------
/Middleware/MiddlewareInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\ExceptionInterface;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  */
20 | interface MiddlewareInterface
21 | {
22 |     /**
23 |      * @throws ExceptionInterface
24 |      */
25 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope;
26 | }
27 | 


--------------------------------------------------------------------------------
/Middleware/RejectRedeliveredMessageMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceivedStamp;
15 | use Symfony\Component\Messenger\Envelope;
16 | use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
17 | 
18 | /**
19 |  * Middleware that throws a RejectRedeliveredMessageException when a message is detected that has been redelivered by AMQP.
20 |  *
21 |  * The middleware runs before the HandleMessageMiddleware and prevents redelivered messages from being handled directly.
22 |  * The thrown exception is caught by the worker and will trigger the retry logic according to the retry strategy.
23 |  *
24 |  * AMQP redelivers messages when they do not get acknowledged or rejected. This can happen when the connection times out
25 |  * or an exception is thrown before acknowledging or rejecting. When such errors happen again while handling the
26 |  * redelivered message, the message would get redelivered again and again. The purpose of this middleware is to prevent
27 |  * infinite redelivery loops and to unblock the queue by republishing the redelivered messages as retries with a retry
28 |  * limit and potential delay.
29 |  *
30 |  * @author Tobias Schultze <http://tobion.de>
31 |  */
32 | class RejectRedeliveredMessageMiddleware implements MiddlewareInterface
33 | {
34 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
35 |     {
36 |         $amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
37 |         if ($amqpReceivedStamp instanceof AmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
38 |             throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
39 |         }
40 | 
41 |         return $stack->next()->handle($envelope, $stack);
42 |     }
43 | }
44 | 


--------------------------------------------------------------------------------
/Middleware/RouterContextMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;
16 | use Symfony\Component\Messenger\Stamp\RouterContextStamp;
17 | use Symfony\Component\Routing\RequestContextAwareInterface;
18 | 
19 | /**
20 |  * Restore the Router context when processing the message.
21 |  *
22 |  * @author Jérémy Derussé <jeremy@derusse.com>
23 |  */
24 | class RouterContextMiddleware implements MiddlewareInterface
25 | {
26 |     public function __construct(
27 |         private RequestContextAwareInterface $router,
28 |     ) {
29 |     }
30 | 
31 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
32 |     {
33 |         $context = $this->router->getContext();
34 | 
35 |         if (!$envelope->last(ConsumedByWorkerStamp::class) || !$contextStamp = $envelope->last(RouterContextStamp::class)) {
36 |             $envelope = $envelope->with(new RouterContextStamp(
37 |                 $context->getBaseUrl(),
38 |                 $context->getMethod(),
39 |                 $context->getHost(),
40 |                 $context->getScheme(),
41 |                 $context->getHttpPort(),
42 |                 $context->getHttpsPort(),
43 |                 $context->getPathInfo(),
44 |                 $context->getQueryString()
45 |             ));
46 | 
47 |             return $stack->next()->handle($envelope, $stack);
48 |         }
49 | 
50 |         $currentBaseUrl = $context->getBaseUrl();
51 |         $currentMethod = $context->getMethod();
52 |         $currentHost = $context->getHost();
53 |         $currentScheme = $context->getScheme();
54 |         $currentHttpPort = $context->getHttpPort();
55 |         $currentHttpsPort = $context->getHttpsPort();
56 |         $currentPathInfo = $context->getPathInfo();
57 |         $currentQueryString = $context->getQueryString();
58 | 
59 |         /* @var RouterContextStamp $contextStamp */
60 |         $context
61 |             ->setBaseUrl($contextStamp->getBaseUrl())
62 |             ->setMethod($contextStamp->getMethod())
63 |             ->setHost($contextStamp->getHost())
64 |             ->setScheme($contextStamp->getScheme())
65 |             ->setHttpPort($contextStamp->getHttpPort())
66 |             ->setHttpsPort($contextStamp->getHttpsPort())
67 |             ->setPathInfo($contextStamp->getPathInfo())
68 |             ->setQueryString($contextStamp->getQueryString())
69 |         ;
70 | 
71 |         try {
72 |             return $stack->next()->handle($envelope, $stack);
73 |         } finally {
74 |             $context
75 |                 ->setBaseUrl($currentBaseUrl)
76 |                 ->setMethod($currentMethod)
77 |                 ->setHost($currentHost)
78 |                 ->setScheme($currentScheme)
79 |                 ->setHttpPort($currentHttpPort)
80 |                 ->setHttpsPort($currentHttpsPort)
81 |                 ->setPathInfo($currentPathInfo)
82 |                 ->setQueryString($currentQueryString)
83 |             ;
84 |         }
85 |     }
86 | }
87 | 


--------------------------------------------------------------------------------
/Middleware/SendMessageMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Psr\EventDispatcher\EventDispatcherInterface;
15 | use Psr\Log\LoggerAwareTrait;
16 | use Symfony\Component\Messenger\Envelope;
17 | use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
18 | use Symfony\Component\Messenger\Exception\NoSenderForMessageException;
19 | use Symfony\Component\Messenger\Stamp\ReceivedStamp;
20 | use Symfony\Component\Messenger\Stamp\SentStamp;
21 | use Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface;
22 | 
23 | /**
24 |  * @author Samuel Roze <samuel.roze@gmail.com>
25 |  * @author Tobias Schultze <http://tobion.de>
26 |  */
27 | class SendMessageMiddleware implements MiddlewareInterface
28 | {
29 |     use LoggerAwareTrait;
30 | 
31 |     public function __construct(
32 |         private SendersLocatorInterface $sendersLocator,
33 |         private ?EventDispatcherInterface $eventDispatcher = null,
34 |         private bool $allowNoSenders = true,
35 |     ) {
36 |     }
37 | 
38 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
39 |     {
40 |         $context = [
41 |             'class' => $envelope->getMessage()::class,
42 |         ];
43 | 
44 |         $sender = null;
45 | 
46 |         if ($envelope->all(ReceivedStamp::class)) {
47 |             // it's a received message, do not send it back
48 |             $this->logger?->info('Received message {class}', $context);
49 |         } else {
50 |             $shouldDispatchEvent = true;
51 |             $senders = $this->sendersLocator->getSenders($envelope);
52 |             $senders = \is_array($senders) ? $senders : iterator_to_array($senders);
53 |             foreach ($senders as $alias => $sender) {
54 |                 if (null !== $this->eventDispatcher && $shouldDispatchEvent) {
55 |                     $event = new SendMessageToTransportsEvent($envelope, $senders);
56 |                     $this->eventDispatcher->dispatch($event);
57 |                     $envelope = $event->getEnvelope();
58 |                     $shouldDispatchEvent = false;
59 |                 }
60 | 
61 |                 $this->logger?->info('Sending message {class} with {alias} sender using {sender}', $context + ['alias' => $alias, 'sender' => $sender::class]);
62 |                 $envelope = $sender->send($envelope->with(new SentStamp($sender::class, \is_string($alias) ? $alias : null)));
63 |             }
64 | 
65 |             if (!$this->allowNoSenders && !$sender) {
66 |                 throw new NoSenderForMessageException(\sprintf('No sender for message "%s".', $context['class']));
67 |             }
68 |         }
69 | 
70 |         if (null === $sender) {
71 |             return $stack->next()->handle($envelope, $stack);
72 |         }
73 | 
74 |         // message should only be sent and not be handled by the next middleware
75 |         return $envelope;
76 |     }
77 | }
78 | 


--------------------------------------------------------------------------------
/Middleware/StackInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | /**
15 |  * @author Nicolas Grekas <p@tchwork.com>
16 |  *
17 |  * Implementations must be cloneable, and each clone must unstack the stack independently.
18 |  */
19 | interface StackInterface
20 | {
21 |     /**
22 |      * Returns the next middleware to process a message.
23 |      */
24 |     public function next(): MiddlewareInterface;
25 | }
26 | 


--------------------------------------------------------------------------------
/Middleware/StackMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * @author Nicolas Grekas <p@tchwork.com>
18 |  */
19 | class StackMiddleware implements MiddlewareInterface, StackInterface
20 | {
21 |     private MiddlewareStack $stack;
22 |     private int $offset = 0;
23 | 
24 |     /**
25 |      * @param iterable<mixed, MiddlewareInterface>|MiddlewareInterface|null $middlewareIterator
26 |      */
27 |     public function __construct(iterable|MiddlewareInterface|null $middlewareIterator = null)
28 |     {
29 |         $this->stack = new MiddlewareStack();
30 | 
31 |         if (null === $middlewareIterator) {
32 |             return;
33 |         }
34 | 
35 |         if ($middlewareIterator instanceof \Iterator) {
36 |             $this->stack->iterator = $middlewareIterator;
37 |         } elseif ($middlewareIterator instanceof MiddlewareInterface) {
38 |             $this->stack->stack[] = $middlewareIterator;
39 |         } else {
40 |             $this->stack->iterator = (function () use ($middlewareIterator) {
41 |                 yield from $middlewareIterator;
42 |             })();
43 |         }
44 |     }
45 | 
46 |     public function next(): MiddlewareInterface
47 |     {
48 |         if (null === $next = $this->stack->next($this->offset)) {
49 |             return $this;
50 |         }
51 | 
52 |         ++$this->offset;
53 | 
54 |         return $next;
55 |     }
56 | 
57 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
58 |     {
59 |         return $envelope;
60 |     }
61 | }
62 | 
63 | /**
64 |  * @internal
65 |  */
66 | class MiddlewareStack
67 | {
68 |     /** @var \Iterator<mixed, MiddlewareInterface>|null */
69 |     public ?\Iterator $iterator = null;
70 |     public array $stack = [];
71 | 
72 |     public function next(int $offset): ?MiddlewareInterface
73 |     {
74 |         if (isset($this->stack[$offset])) {
75 |             return $this->stack[$offset];
76 |         }
77 | 
78 |         if (null === $this->iterator) {
79 |             return null;
80 |         }
81 | 
82 |         $this->iterator->next();
83 | 
84 |         if (!$this->iterator->valid()) {
85 |             return $this->iterator = null;
86 |         }
87 | 
88 |         return $this->stack[] = $this->iterator->current();
89 |     }
90 | }
91 | 


--------------------------------------------------------------------------------
/Middleware/TraceableMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Stopwatch\Stopwatch;
16 | 
17 | /**
18 |  * Collects some data about a middleware.
19 |  *
20 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
21 |  */
22 | class TraceableMiddleware implements MiddlewareInterface
23 | {
24 |     public function __construct(
25 |         private Stopwatch $stopwatch,
26 |         private string $busName,
27 |         private string $eventCategory = 'messenger.middleware',
28 |     ) {
29 |     }
30 | 
31 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
32 |     {
33 |         $stack = new TraceableStack($stack, $this->stopwatch, $this->busName, $this->eventCategory);
34 | 
35 |         try {
36 |             return $stack->next()->handle($envelope, $stack);
37 |         } finally {
38 |             $stack->stop();
39 |         }
40 |     }
41 | }
42 | 
43 | /**
44 |  * @internal
45 |  */
46 | class TraceableStack implements StackInterface
47 | {
48 |     private ?string $currentEvent = null;
49 | 
50 |     public function __construct(
51 |         private StackInterface $stack,
52 |         private Stopwatch $stopwatch,
53 |         private string $busName,
54 |         private string $eventCategory,
55 |     ) {
56 |     }
57 | 
58 |     public function next(): MiddlewareInterface
59 |     {
60 |         if (null !== $this->currentEvent && $this->stopwatch->isStarted($this->currentEvent)) {
61 |             $this->stopwatch->stop($this->currentEvent);
62 |         }
63 | 
64 |         if ($this->stack === $nextMiddleware = $this->stack->next()) {
65 |             $this->currentEvent = 'Tail';
66 |         } else {
67 |             $this->currentEvent = \sprintf('"%s"', get_debug_type($nextMiddleware));
68 |         }
69 |         $this->currentEvent .= \sprintf(' on "%s"', $this->busName);
70 | 
71 |         $this->stopwatch->start($this->currentEvent, $this->eventCategory);
72 | 
73 |         return $nextMiddleware;
74 |     }
75 | 
76 |     public function stop(): void
77 |     {
78 |         if (null !== $this->currentEvent && $this->stopwatch->isStarted($this->currentEvent)) {
79 |             $this->stopwatch->stop($this->currentEvent);
80 |         }
81 |         $this->currentEvent = null;
82 |     }
83 | 
84 |     public function __clone()
85 |     {
86 |         $this->stack = clone $this->stack;
87 |     }
88 | }
89 | 


--------------------------------------------------------------------------------
/Middleware/ValidationMiddleware.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Middleware;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\ValidationFailedException;
16 | use Symfony\Component\Messenger\Stamp\ValidationStamp;
17 | use Symfony\Component\Validator\Validator\ValidatorInterface;
18 | 
19 | /**
20 |  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
21 |  */
22 | class ValidationMiddleware implements MiddlewareInterface
23 | {
24 |     public function __construct(
25 |         private ValidatorInterface $validator,
26 |     ) {
27 |     }
28 | 
29 |     public function handle(Envelope $envelope, StackInterface $stack): Envelope
30 |     {
31 |         $message = $envelope->getMessage();
32 |         $groups = null;
33 |         /** @var ValidationStamp|null $validationStamp */
34 |         if ($validationStamp = $envelope->last(ValidationStamp::class)) {
35 |             $groups = $validationStamp->getGroups();
36 |         }
37 | 
38 |         $violations = $this->validator->validate($message, null, $groups);
39 |         if (\count($violations)) {
40 |             throw new ValidationFailedException($message, $violations, $envelope);
41 |         }
42 | 
43 |         return $stack->next()->handle($envelope, $stack);
44 |     }
45 | }
46 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | Messenger Component
 2 | ===================
 3 | 
 4 | The Messenger component helps applications send and receive messages to/from
 5 | other applications or via message queues.
 6 | 
 7 | Sponsor
 8 | -------
 9 | 
10 | Help Symfony by [sponsoring][1] its development!
11 | 
12 | Resources
13 | ---------
14 | 
15 |  * [Documentation](https://symfony.com/doc/current/components/messenger.html)
16 |  * [Contributing](https://symfony.com/doc/current/contributing/index.html)
17 |  * [Report issues](https://github.com/symfony/symfony/issues) and
18 |    [send Pull Requests](https://github.com/symfony/symfony/pulls)
19 |    in the [main Symfony repository](https://github.com/symfony/symfony)
20 | 
21 | [1]: https://symfony.com/sponsor
22 | 


--------------------------------------------------------------------------------
/Retry/MultiplierRetryStrategy.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Retry;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
16 | use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
17 | 
18 | /**
19 |  * A retry strategy with a constant or exponential retry delay.
20 |  *
21 |  * For example, if $delayMilliseconds=10000 & $multiplier=1 (default),
22 |  * each retry will wait exactly 10 seconds.
23 |  *
24 |  * But if $delayMilliseconds=10000 & $multiplier=2:
25 |  *      * Retry 1: 10 second delay
26 |  *      * Retry 2: 20 second delay (10000 * 2 = 20000)
27 |  *      * Retry 3: 40 second delay (20000 * 2 = 40000)
28 |  *
29 |  * @author Ryan Weaver <ryan@symfonycasts.com>
30 |  *
31 |  * @final
32 |  */
33 | class MultiplierRetryStrategy implements RetryStrategyInterface
34 | {
35 |     /**
36 |      * @param int   $maxRetries           The maximum number of times to retry
37 |      * @param int   $delayMilliseconds    Amount of time to delay (or the initial value when multiplier is used)
38 |      * @param float $multiplier           Multiplier to apply to the delay each time a retry occurs
39 |      * @param int   $maxDelayMilliseconds Maximum delay to allow (0 means no maximum)
40 |      * @param float $jitter               Randomness to apply to the delay (between 0 and 1)
41 |      */
42 |     public function __construct(
43 |         private int $maxRetries = 3,
44 |         private int $delayMilliseconds = 1000,
45 |         private float $multiplier = 1,
46 |         private int $maxDelayMilliseconds = 0,
47 |         private float $jitter = 0.1,
48 |     ) {
49 |         if ($delayMilliseconds < 0) {
50 |             throw new InvalidArgumentException(\sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMilliseconds));
51 |         }
52 | 
53 |         if ($multiplier < 1) {
54 |             throw new InvalidArgumentException(\sprintf('Multiplier must be greater than zero: "%s" given.', $multiplier));
55 |         }
56 | 
57 |         if ($maxDelayMilliseconds < 0) {
58 |             throw new InvalidArgumentException(\sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMilliseconds));
59 |         }
60 | 
61 |         if ($jitter < 0 || $jitter > 1) {
62 |             throw new InvalidArgumentException(\sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter));
63 |         }
64 |     }
65 | 
66 |     /**
67 |      * @param \Throwable|null $throwable The cause of the failed handling
68 |      */
69 |     public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool
70 |     {
71 |         $retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
72 | 
73 |         return $retries < $this->maxRetries;
74 |     }
75 | 
76 |     /**
77 |      * @param \Throwable|null $throwable The cause of the failed handling
78 |      */
79 |     public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int
80 |     {
81 |         $retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
82 | 
83 |         $delay = $this->delayMilliseconds * $this->multiplier ** $retries;
84 | 
85 |         if ($this->jitter > 0) {
86 |             $randomness = (int) min(\PHP_INT_MAX, $delay * $this->jitter);
87 |             $delay += random_int(-$randomness, +$randomness);
88 |         }
89 | 
90 |         if ($delay > $this->maxDelayMilliseconds && 0 !== $this->maxDelayMilliseconds) {
91 |             return $this->maxDelayMilliseconds;
92 |         }
93 | 
94 |         return (int) min(\PHP_INT_MAX, ceil($delay));
95 |     }
96 | }
97 | 


--------------------------------------------------------------------------------
/Retry/RetryStrategyInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Retry;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * @author Fabien Potencier <fabien@symfony.com>
18 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
19 |  * @author Ryan Weaver <ryan@symfonycasts.com>
20 |  */
21 | interface RetryStrategyInterface
22 | {
23 |     /**
24 |      * @param \Throwable|null $throwable The cause of the failed handling
25 |      */
26 |     public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool;
27 | 
28 |     /**
29 |      * @param \Throwable|null $throwable The cause of the failed handling
30 |      *
31 |      * @return int The time to delay/wait in milliseconds
32 |      */
33 |     public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int;
34 | }
35 | 


--------------------------------------------------------------------------------
/RoutableMessageBus.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger;
13 | 
14 | use Psr\Container\ContainerInterface;
15 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
16 | use Symfony\Component\Messenger\Stamp\BusNameStamp;
17 | 
18 | /**
19 |  * Bus of buses that is routable using a BusNameStamp.
20 |  *
21 |  * This is useful when passed to Worker: messages received
22 |  * from the transport can be sent to the correct bus.
23 |  *
24 |  * @author Ryan Weaver <ryan@symfonycasts.com>
25 |  */
26 | class RoutableMessageBus implements MessageBusInterface
27 | {
28 |     public function __construct(
29 |         private ContainerInterface $busLocator,
30 |         private ?MessageBusInterface $fallbackBus = null,
31 |     ) {
32 |     }
33 | 
34 |     public function dispatch(object $envelope, array $stamps = []): Envelope
35 |     {
36 |         if (!$envelope instanceof Envelope) {
37 |             throw new InvalidArgumentException('Messages passed to RoutableMessageBus::dispatch() must be inside an Envelope.');
38 |         }
39 | 
40 |         /** @var BusNameStamp|null $busNameStamp */
41 |         $busNameStamp = $envelope->last(BusNameStamp::class);
42 | 
43 |         if (null === $busNameStamp) {
44 |             if (null === $this->fallbackBus) {
45 |                 throw new InvalidArgumentException('Envelope is missing a BusNameStamp and no fallback message bus is configured on RoutableMessageBus.');
46 |             }
47 | 
48 |             return $this->fallbackBus->dispatch($envelope, $stamps);
49 |         }
50 | 
51 |         return $this->getMessageBus($busNameStamp->getBusName())->dispatch($envelope, $stamps);
52 |     }
53 | 
54 |     /**
55 |      * @internal
56 |      */
57 |     public function getMessageBus(string $busName): MessageBusInterface
58 |     {
59 |         if (!$this->busLocator->has($busName)) {
60 |             throw new InvalidArgumentException(\sprintf('Bus named "%s" does not exist.', $busName));
61 |         }
62 | 
63 |         return $this->busLocator->get($busName);
64 |     }
65 | }
66 | 


--------------------------------------------------------------------------------
/Stamp/AckStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Marker stamp for messages that can be ack/nack'ed.
18 |  */
19 | final class AckStamp implements NonSendableStampInterface
20 | {
21 |     /**
22 |      * @param \Closure(Envelope, \Throwable|null) $ack
23 |      */
24 |     public function __construct(
25 |         private readonly \Closure $ack,
26 |     ) {
27 |     }
28 | 
29 |     public function ack(Envelope $envelope, ?\Throwable $e = null): void
30 |     {
31 |         ($this->ack)($envelope, $e);
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------
/Stamp/BusNameStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Stamp used to identify which bus it was passed to.
16 |  *
17 |  * @author Ryan Weaver <ryan@symfonycasts.com>
18 |  */
19 | final class BusNameStamp implements StampInterface
20 | {
21 |     public function __construct(
22 |         private string $busName,
23 |     ) {
24 |     }
25 | 
26 |     public function getBusName(): string
27 |     {
28 |         return $this->busName;
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/Stamp/ConsumedByWorkerStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * A marker that this message was consumed by a worker process.
16 |  */
17 | class ConsumedByWorkerStamp implements NonSendableStampInterface
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Stamp/DeduplicateStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Lock\Key;
15 | use Symfony\Component\Messenger\Exception\LogicException;
16 | 
17 | final class DeduplicateStamp implements StampInterface
18 | {
19 |     private Key $key;
20 | 
21 |     public function __construct(
22 |         string $key,
23 |         private ?float $ttl = 300.0,
24 |         private bool $onlyDeduplicateInQueue = false,
25 |     ) {
26 |         if (!class_exists(Key::class)) {
27 |             throw new LogicException(\sprintf('You cannot use the "%s" as the Lock component is not installed. Try running "composer require symfony/lock".', self::class));
28 |         }
29 | 
30 |         $this->key = new Key($key);
31 |     }
32 | 
33 |     public function onlyDeduplicateInQueue(): bool
34 |     {
35 |         return $this->onlyDeduplicateInQueue;
36 |     }
37 | 
38 |     public function getKey(): Key
39 |     {
40 |         return $this->key;
41 |     }
42 | 
43 |     public function getTtl(): ?float
44 |     {
45 |         return $this->ttl;
46 |     }
47 | }
48 | 


--------------------------------------------------------------------------------
/Stamp/DelayStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Apply this stamp to delay delivery of your message on a transport.
16 |  */
17 | final class DelayStamp implements StampInterface
18 | {
19 |     /**
20 |      * @param int $delay The delay in milliseconds
21 |      */
22 |     public function __construct(
23 |         private int $delay,
24 |     ) {
25 |     }
26 | 
27 |     public function getDelay(): int
28 |     {
29 |         return $this->delay;
30 |     }
31 | 
32 |     public static function delayFor(\DateInterval $interval): self
33 |     {
34 |         $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
35 |         $end = $now->add($interval);
36 | 
37 |         return new self(($end->getTimestamp() - $now->getTimestamp()) * 1000);
38 |     }
39 | 
40 |     public static function delayUntil(\DateTimeInterface $dateTime): self
41 |     {
42 |         return new self(($dateTime->getTimestamp() - time()) * 1000);
43 |     }
44 | }
45 | 


--------------------------------------------------------------------------------
/Stamp/DispatchAfterCurrentBusStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Marker item to tell this message should be handled in after the current bus has finished.
16 |  *
17 |  * @see \Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware
18 |  *
19 |  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
20 |  */
21 | final class DispatchAfterCurrentBusStamp implements NonSendableStampInterface
22 | {
23 | }
24 | 


--------------------------------------------------------------------------------
/Stamp/ErrorDetailsStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\ErrorHandler\Exception\FlattenException;
15 | use Symfony\Component\Messenger\Exception\HandlerFailedException;
16 | 
17 | /**
18 |  * Stamp applied when a messages fails due to an exception in the handler.
19 |  */
20 | final class ErrorDetailsStamp implements StampInterface
21 | {
22 |     public function __construct(
23 |         private string $exceptionClass,
24 |         private int|string $exceptionCode,
25 |         private string $exceptionMessage,
26 |         private ?FlattenException $flattenException = null,
27 |     ) {
28 |     }
29 | 
30 |     public static function create(\Throwable $throwable): self
31 |     {
32 |         if ($throwable instanceof HandlerFailedException) {
33 |             $throwable = $throwable->getPrevious();
34 |         }
35 | 
36 |         $flattenException = null;
37 |         if (class_exists(FlattenException::class)) {
38 |             $flattenException = FlattenException::createFromThrowable($throwable);
39 |         }
40 | 
41 |         return new self($throwable::class, $throwable->getCode(), $throwable->getMessage(), $flattenException);
42 |     }
43 | 
44 |     public function getExceptionClass(): string
45 |     {
46 |         return $this->exceptionClass;
47 |     }
48 | 
49 |     public function getExceptionCode(): int|string
50 |     {
51 |         return $this->exceptionCode;
52 |     }
53 | 
54 |     public function getExceptionMessage(): string
55 |     {
56 |         return $this->exceptionMessage;
57 |     }
58 | 
59 |     public function getFlattenException(): ?FlattenException
60 |     {
61 |         return $this->flattenException;
62 |     }
63 | 
64 |     public function equals(?self $that): bool
65 |     {
66 |         if (null === $that) {
67 |             return false;
68 |         }
69 | 
70 |         if ($this->flattenException && $that->flattenException) {
71 |             return $this->flattenException->getClass() === $that->flattenException->getClass()
72 |                 && $this->flattenException->getCode() === $that->flattenException->getCode()
73 |                 && $this->flattenException->getMessage() === $that->flattenException->getMessage();
74 |         }
75 | 
76 |         return $this->exceptionClass === $that->exceptionClass
77 |             && $this->exceptionCode === $that->exceptionCode
78 |             && $this->exceptionMessage === $that->exceptionMessage;
79 |     }
80 | }
81 | 


--------------------------------------------------------------------------------
/Stamp/FlushBatchHandlersStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Marker telling that any batch handlers bound to the envelope should be flushed.
16 |  */
17 | final class FlushBatchHandlersStamp implements NonSendableStampInterface
18 | {
19 |     public function __construct(
20 |         private bool $force,
21 |     ) {
22 |     }
23 | 
24 |     public function force(): bool
25 |     {
26 |         return $this->force;
27 |     }
28 | }
29 | 


--------------------------------------------------------------------------------
/Stamp/HandledStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Messenger\Handler\HandlerDescriptor;
15 | 
16 | /**
17 |  * Stamp identifying a message handled by the `HandleMessageMiddleware` middleware
18 |  * and storing the handler returned value.
19 |  *
20 |  * This is used by synchronous command buses expecting a return value and the retry logic
21 |  * to only execute handlers that didn't succeed.
22 |  *
23 |  * @see \Symfony\Component\Messenger\Middleware\HandleMessageMiddleware
24 |  * @see \Symfony\Component\Messenger\HandleTrait
25 |  *
26 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
27 |  */
28 | final class HandledStamp implements StampInterface
29 | {
30 |     public function __construct(
31 |         private mixed $result,
32 |         private string $handlerName,
33 |     ) {
34 |     }
35 | 
36 |     public static function fromDescriptor(HandlerDescriptor $handler, mixed $result): self
37 |     {
38 |         return new self($result, $handler->getName());
39 |     }
40 | 
41 |     public function getResult(): mixed
42 |     {
43 |         return $this->result;
44 |     }
45 | 
46 |     public function getHandlerName(): string
47 |     {
48 |         return $this->handlerName;
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/Stamp/HandlerArgumentsStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * @author Jáchym Toušek <enumag@gmail.com>
16 |  */
17 | final class HandlerArgumentsStamp implements NonSendableStampInterface
18 | {
19 |     public function __construct(
20 |         private array $additionalArguments,
21 |     ) {
22 |     }
23 | 
24 |     public function getAdditionalArguments(): array
25 |     {
26 |         return $this->additionalArguments;
27 |     }
28 | }
29 | 


--------------------------------------------------------------------------------
/Stamp/MessageDecodingFailedStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * @author Grégoire Pineau <lyrixx@lyrixx.info>
16 |  */
17 | class MessageDecodingFailedStamp implements StampInterface
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/Stamp/NoAutoAckStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Messenger\Handler\HandlerDescriptor;
15 | 
16 | /**
17 |  * Marker telling that ack should not be done automatically for this message.
18 |  */
19 | final class NoAutoAckStamp implements NonSendableStampInterface
20 | {
21 |     public function __construct(
22 |         private HandlerDescriptor $handlerDescriptor,
23 |     ) {
24 |     }
25 | 
26 |     public function getHandlerDescriptor(): HandlerDescriptor
27 |     {
28 |         return $this->handlerDescriptor;
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/Stamp/NonSendableStampInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * A stamp that should not be included with the Envelope if sent to a transport.
16 |  *
17 |  * @author Ryan Weaver <ryan@symfonycasts.com>
18 |  */
19 | interface NonSendableStampInterface extends StampInterface
20 | {
21 | }
22 | 


--------------------------------------------------------------------------------
/Stamp/ReceivedStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
15 | 
16 | /**
17 |  * Marker stamp for a received message.
18 |  *
19 |  * This is mainly used by the `SendMessageMiddleware` middleware to identify
20 |  * a message should not be sent if it was just received.
21 |  *
22 |  * @see SendMessageMiddleware
23 |  *
24 |  * @author Samuel Roze <samuel.roze@gmail.com>
25 |  */
26 | final class ReceivedStamp implements NonSendableStampInterface
27 | {
28 |     public function __construct(
29 |         private string $transportName,
30 |     ) {
31 |     }
32 | 
33 |     public function getTransportName(): string
34 |     {
35 |         return $this->transportName;
36 |     }
37 | }
38 | 


--------------------------------------------------------------------------------
/Stamp/RedeliveryStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Stamp applied when a messages needs to be redelivered.
18 |  */
19 | final class RedeliveryStamp implements StampInterface
20 | {
21 |     private \DateTimeInterface $redeliveredAt;
22 | 
23 |     public function __construct(
24 |         private int $retryCount,
25 |         ?\DateTimeInterface $redeliveredAt = null,
26 |     ) {
27 |         $this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
28 |     }
29 | 
30 |     public static function getRetryCountFromEnvelope(Envelope $envelope): int
31 |     {
32 |         /** @var self|null $retryMessageStamp */
33 |         $retryMessageStamp = $envelope->last(self::class);
34 | 
35 |         return $retryMessageStamp ? $retryMessageStamp->getRetryCount() : 0;
36 |     }
37 | 
38 |     public function getRetryCount(): int
39 |     {
40 |         return $this->retryCount;
41 |     }
42 | 
43 |     public function getRedeliveredAt(): \DateTimeInterface
44 |     {
45 |         return $this->redeliveredAt;
46 |     }
47 | }
48 | 


--------------------------------------------------------------------------------
/Stamp/RouterContextStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * @author Jérémy Derussé <jeremy@derusse.com>
16 |  */
17 | class RouterContextStamp implements StampInterface
18 | {
19 |     public function __construct(
20 |         private string $baseUrl,
21 |         private string $method,
22 |         private string $host,
23 |         private string $scheme,
24 |         private int $httpPort,
25 |         private int $httpsPort,
26 |         private string $pathInfo,
27 |         private string $queryString,
28 |     ) {
29 |     }
30 | 
31 |     public function getBaseUrl(): string
32 |     {
33 |         return $this->baseUrl;
34 |     }
35 | 
36 |     public function getMethod(): string
37 |     {
38 |         return $this->method;
39 |     }
40 | 
41 |     public function getHost(): string
42 |     {
43 |         return $this->host;
44 |     }
45 | 
46 |     public function getScheme(): string
47 |     {
48 |         return $this->scheme;
49 |     }
50 | 
51 |     public function getHttpPort(): int
52 |     {
53 |         return $this->httpPort;
54 |     }
55 | 
56 |     public function getHttpsPort(): int
57 |     {
58 |         return $this->httpsPort;
59 |     }
60 | 
61 |     public function getPathInfo(): string
62 |     {
63 |         return $this->pathInfo;
64 |     }
65 | 
66 |     public function getQueryString(): string
67 |     {
68 |         return $this->queryString;
69 |     }
70 | }
71 | 


--------------------------------------------------------------------------------
/Stamp/SentForRetryStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Stamp indicating whether a failed message has been sent for retry.
16 |  */
17 | final class SentForRetryStamp implements NonSendableStampInterface
18 | {
19 |     public function __construct(
20 |         public readonly bool $isSent,
21 |     ) {
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/Stamp/SentStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Marker stamp identifying a message sent by the `SendMessageMiddleware`.
16 |  *
17 |  * @see \Symfony\Component\Messenger\Middleware\SendMessageMiddleware
18 |  *
19 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
20 |  */
21 | final class SentStamp implements NonSendableStampInterface
22 | {
23 |     public function __construct(
24 |         private string $senderClass,
25 |         private ?string $senderAlias = null,
26 |     ) {
27 |     }
28 | 
29 |     public function getSenderClass(): string
30 |     {
31 |         return $this->senderClass;
32 |     }
33 | 
34 |     public function getSenderAlias(): ?string
35 |     {
36 |         return $this->senderAlias;
37 |     }
38 | }
39 | 


--------------------------------------------------------------------------------
/Stamp/SentToFailureTransportStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Stamp applied when a message is sent to the failure transport.
16 |  *
17 |  * @author Ryan Weaver <ryan@symfonycasts.com>
18 |  */
19 | final class SentToFailureTransportStamp implements StampInterface
20 | {
21 |     public function __construct(
22 |         private string $originalReceiverName,
23 |     ) {
24 |     }
25 | 
26 |     public function getOriginalReceiverName(): string
27 |     {
28 |         return $this->originalReceiverName;
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/Stamp/SerializedMessageStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | final class SerializedMessageStamp implements NonSendableStampInterface
15 | {
16 |     public function __construct(private string $serializedMessage)
17 |     {
18 |     }
19 | 
20 |     public function getSerializedMessage(): string
21 |     {
22 |         return $this->serializedMessage;
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/Stamp/SerializerStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
16 |  */
17 | final class SerializerStamp implements StampInterface
18 | {
19 |     public function __construct(
20 |         private array $context,
21 |     ) {
22 |     }
23 | 
24 |     public function getContext(): array
25 |     {
26 |         return $this->context;
27 |     }
28 | }
29 | 


--------------------------------------------------------------------------------
/Stamp/StampInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * An envelope stamp related to a message.
16 |  *
17 |  * Stamps must be serializable value objects for transport.
18 |  *
19 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
20 |  */
21 | interface StampInterface
22 | {
23 | }
24 | 


--------------------------------------------------------------------------------
/Stamp/TransportMessageIdStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Added by a sender or receiver to indicate the id of this message in that transport.
16 |  *
17 |  * @author Ryan Weaver <ryan@symfonycasts.com>
18 |  */
19 | final class TransportMessageIdStamp implements StampInterface
20 | {
21 |     /**
22 |      * @param mixed $id some "identifier" of the message in a transport
23 |      */
24 |     public function __construct(
25 |         private mixed $id,
26 |     ) {
27 |     }
28 | 
29 |     public function getId(): mixed
30 |     {
31 |         return $this->id;
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------
/Stamp/TransportNamesStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | /**
15 |  * Stamp used to override the transport names specified in the Messenger routing configuration file.
16 |  */
17 | final class TransportNamesStamp implements StampInterface
18 | {
19 |     private array $transportNames;
20 | 
21 |     /**
22 |      * @param string[]|string $transportNames Transport names to be used for the message
23 |      */
24 |     public function __construct(array|string $transportNames)
25 |     {
26 |         $this->transportNames = (array) $transportNames;
27 |     }
28 | 
29 |     public function getTransportNames(): array
30 |     {
31 |         return $this->transportNames;
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------
/Stamp/ValidationStamp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Stamp;
13 | 
14 | use Symfony\Component\Validator\Constraints\GroupSequence;
15 | 
16 | /**
17 |  * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
18 |  */
19 | final class ValidationStamp implements StampInterface
20 | {
21 |     /**
22 |      * @param string[]|GroupSequence $groups
23 |      */
24 |     public function __construct(
25 |         private array|GroupSequence $groups,
26 |     ) {
27 |     }
28 | 
29 |     /** @return string[]|GroupSequence */
30 |     public function getGroups(): array|GroupSequence
31 |     {
32 |         return $this->groups;
33 |     }
34 | }
35 | 


--------------------------------------------------------------------------------
/Test/Middleware/MiddlewareTestCase.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Test\Middleware;
13 | 
14 | use PHPUnit\Framework\TestCase;
15 | use Symfony\Component\Messenger\Envelope;
16 | use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
17 | use Symfony\Component\Messenger\Middleware\StackInterface;
18 | use Symfony\Component\Messenger\Middleware\StackMiddleware;
19 | 
20 | /**
21 |  * @author Nicolas Grekas <p@tchwork.com>
22 |  */
23 | abstract class MiddlewareTestCase extends TestCase
24 | {
25 |     protected function getStackMock(bool $nextIsCalled = true)
26 |     {
27 |         if (!$nextIsCalled) {
28 |             $stack = $this->createMock(StackInterface::class);
29 |             $stack
30 |                 ->expects($this->never())
31 |                 ->method('next')
32 |             ;
33 | 
34 |             return $stack;
35 |         }
36 | 
37 |         $nextMiddleware = $this->createMock(MiddlewareInterface::class);
38 |         $nextMiddleware
39 |             ->expects($this->once())
40 |             ->method('handle')
41 |             ->willReturnCallback(fn (Envelope $envelope, StackInterface $stack): Envelope => $envelope)
42 |         ;
43 | 
44 |         return new StackMiddleware($nextMiddleware);
45 |     }
46 | 
47 |     protected function getThrowingStackMock(?\Throwable $throwable = null)
48 |     {
49 |         $nextMiddleware = $this->createMock(MiddlewareInterface::class);
50 |         $nextMiddleware
51 |             ->expects($this->once())
52 |             ->method('handle')
53 |             ->willThrowException($throwable ?? new \RuntimeException('Thrown from next middleware.'))
54 |         ;
55 | 
56 |         return new StackMiddleware($nextMiddleware);
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/TraceableMessageBus.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger;
 13 | 
 14 | /**
 15 |  * @author Samuel Roze <samuel.roze@gmail.com>
 16 |  */
 17 | class TraceableMessageBus implements MessageBusInterface
 18 | {
 19 |     private array $dispatchedMessages = [];
 20 | 
 21 |     public function __construct(
 22 |         private MessageBusInterface $decoratedBus,
 23 |         protected readonly ?\Closure $disabled = null,
 24 |     ) {
 25 |     }
 26 | 
 27 |     public function dispatch(object $message, array $stamps = []): Envelope
 28 |     {
 29 |         if ($this->disabled?->__invoke()) {
 30 |             return $this->decoratedBus->dispatch($message, $stamps);
 31 |         }
 32 | 
 33 |         $envelope = Envelope::wrap($message, $stamps);
 34 |         $context = [
 35 |             'stamps' => array_merge([], ...array_values($envelope->all())),
 36 |             'message' => $envelope->getMessage(),
 37 |             'caller' => $this->getCaller(),
 38 |             'callTime' => microtime(true),
 39 |         ];
 40 | 
 41 |         try {
 42 |             return $envelope = $this->decoratedBus->dispatch($message, $stamps);
 43 |         } catch (\Throwable $e) {
 44 |             $context['exception'] = $e;
 45 | 
 46 |             throw $e;
 47 |         } finally {
 48 |             $this->dispatchedMessages[] = $context + ['stamps_after_dispatch' => array_merge([], ...array_values($envelope->all()))];
 49 |         }
 50 |     }
 51 | 
 52 |     public function getDispatchedMessages(): array
 53 |     {
 54 |         return $this->dispatchedMessages;
 55 |     }
 56 | 
 57 |     public function reset(): void
 58 |     {
 59 |         $this->dispatchedMessages = [];
 60 |     }
 61 | 
 62 |     private function getCaller(): array
 63 |     {
 64 |         $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 8);
 65 | 
 66 |         $file = $trace[1]['file'] ?? null;
 67 |         $line = $trace[1]['line'] ?? null;
 68 | 
 69 |         $handleTraitFile = (new \ReflectionClass(HandleTrait::class))->getFileName();
 70 |         $found = false;
 71 |         for ($i = 1; $i < 8; ++$i) {
 72 |             if (isset($trace[$i]['file'], $trace[$i + 1]['file'], $trace[$i + 1]['line']) && $trace[$i]['file'] === $handleTraitFile) {
 73 |                 $file = $trace[$i + 1]['file'];
 74 |                 $line = $trace[$i + 1]['line'];
 75 |                 $found = true;
 76 | 
 77 |                 break;
 78 |             }
 79 |         }
 80 | 
 81 |         for ($i = 2; $i < 8 && !$found; ++$i) {
 82 |             if (isset($trace[$i]['class'], $trace[$i]['function'])
 83 |                 && 'dispatch' === $trace[$i]['function']
 84 |                 && is_a($trace[$i]['class'], MessageBusInterface::class, true)
 85 |             ) {
 86 |                 $file = $trace[$i]['file'];
 87 |                 $line = $trace[$i]['line'];
 88 | 
 89 |                 while (++$i < 8) {
 90 |                     if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) {
 91 |                         $file = $trace[$i]['file'];
 92 |                         $line = $trace[$i]['line'];
 93 | 
 94 |                         break;
 95 |                     }
 96 |                 }
 97 |                 break;
 98 |             }
 99 |         }
100 | 
101 |         $name = str_replace('\\', '/', (string) $file);
102 | 
103 |         return [
104 |             'name' => substr($name, strrpos($name, '/') + 1),
105 |             'file' => $file,
106 |             'line' => $line,
107 |         ];
108 |     }
109 | }
110 | 


--------------------------------------------------------------------------------
/Transport/CloseableTransportInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport;
13 | 
14 | interface CloseableTransportInterface
15 | {
16 |     public function close(): void;
17 | }
18 | 


--------------------------------------------------------------------------------
/Transport/InMemory/InMemoryTransport.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Transport\InMemory;
 13 | 
 14 | use Psr\Clock\ClockInterface;
 15 | use Symfony\Component\Messenger\Envelope;
 16 | use Symfony\Component\Messenger\Exception\LogicException;
 17 | use Symfony\Component\Messenger\Stamp\DelayStamp;
 18 | use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
 19 | use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
 20 | use Symfony\Component\Messenger\Transport\TransportInterface;
 21 | use Symfony\Contracts\Service\ResetInterface;
 22 | 
 23 | /**
 24 |  * Transport that stays in memory. Useful for testing purpose.
 25 |  *
 26 |  * @author Gary PEGEOT <garypegeot@gmail.com>
 27 |  */
 28 | class InMemoryTransport implements TransportInterface, ResetInterface
 29 | {
 30 |     /**
 31 |      * @var Envelope[]
 32 |      */
 33 |     private array $sent = [];
 34 | 
 35 |     /**
 36 |      * @var Envelope[]
 37 |      */
 38 |     private array $acknowledged = [];
 39 | 
 40 |     /**
 41 |      * @var Envelope[]
 42 |      */
 43 |     private array $rejected = [];
 44 | 
 45 |     /**
 46 |      * @var Envelope[]
 47 |      */
 48 |     private array $queue = [];
 49 | 
 50 |     private int $nextId = 1;
 51 |     private array $availableAt = [];
 52 | 
 53 |     public function __construct(
 54 |         private ?SerializerInterface $serializer = null,
 55 |         private ?ClockInterface $clock = null,
 56 |     ) {
 57 |     }
 58 | 
 59 |     public function get(): iterable
 60 |     {
 61 |         $envelopes = [];
 62 |         $now = $this->clock?->now() ?? new \DateTimeImmutable();
 63 |         foreach ($this->decode($this->queue) as $id => $envelope) {
 64 |             if (!isset($this->availableAt[$id]) || $now > $this->availableAt[$id]) {
 65 |                 $envelopes[] = $envelope;
 66 |             }
 67 |         }
 68 | 
 69 |         return $envelopes;
 70 |     }
 71 | 
 72 |     public function ack(Envelope $envelope): void
 73 |     {
 74 |         $this->acknowledged[] = $this->encode($envelope);
 75 | 
 76 |         if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) {
 77 |             throw new LogicException('No TransportMessageIdStamp found on the Envelope.');
 78 |         }
 79 | 
 80 |         unset($this->queue[$id = $transportMessageIdStamp->getId()], $this->availableAt[$id]);
 81 |     }
 82 | 
 83 |     public function reject(Envelope $envelope): void
 84 |     {
 85 |         $this->rejected[] = $this->encode($envelope);
 86 | 
 87 |         if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) {
 88 |             throw new LogicException('No TransportMessageIdStamp found on the Envelope.');
 89 |         }
 90 | 
 91 |         unset($this->queue[$id = $transportMessageIdStamp->getId()], $this->availableAt[$id]);
 92 |     }
 93 | 
 94 |     public function send(Envelope $envelope): Envelope
 95 |     {
 96 |         $id = $this->nextId++;
 97 |         $envelope = $envelope->with(new TransportMessageIdStamp($id));
 98 |         $encodedEnvelope = $this->encode($envelope);
 99 |         $this->sent[] = $encodedEnvelope;
100 |         $this->queue[$id] = $encodedEnvelope;
101 | 
102 |         /** @var DelayStamp|null $delayStamp */
103 |         if ($delayStamp = $envelope->last(DelayStamp::class)) {
104 |             $now = $this->clock?->now() ?? new \DateTimeImmutable();
105 |             $this->availableAt[$id] = $now->modify(\sprintf('+%d seconds', $delayStamp->getDelay() / 1000));
106 |         }
107 | 
108 |         return $envelope;
109 |     }
110 | 
111 |     public function reset(): void
112 |     {
113 |         $this->sent = $this->queue = $this->rejected = $this->acknowledged = [];
114 |     }
115 | 
116 |     /**
117 |      * @return Envelope[]
118 |      */
119 |     public function getAcknowledged(): array
120 |     {
121 |         return $this->decode($this->acknowledged);
122 |     }
123 | 
124 |     /**
125 |      * @return Envelope[]
126 |      */
127 |     public function getRejected(): array
128 |     {
129 |         return $this->decode($this->rejected);
130 |     }
131 | 
132 |     /**
133 |      * @return Envelope[]
134 |      */
135 |     public function getSent(): array
136 |     {
137 |         return $this->decode($this->sent);
138 |     }
139 | 
140 |     private function encode(Envelope $envelope): Envelope|array
141 |     {
142 |         if (null === $this->serializer) {
143 |             return $envelope;
144 |         }
145 | 
146 |         return $this->serializer->encode($envelope);
147 |     }
148 | 
149 |     /**
150 |      * @param array<mixed> $messagesEncoded
151 |      *
152 |      * @return Envelope[]
153 |      */
154 |     private function decode(array $messagesEncoded): array
155 |     {
156 |         if (null === $this->serializer) {
157 |             return $messagesEncoded;
158 |         }
159 | 
160 |         return array_map($this->serializer->decode(...), $messagesEncoded);
161 |     }
162 | }
163 | 


--------------------------------------------------------------------------------
/Transport/InMemory/InMemoryTransportFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\InMemory;
13 | 
14 | use Psr\Clock\ClockInterface;
15 | use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
16 | use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
17 | use Symfony\Component\Messenger\Transport\TransportInterface;
18 | use Symfony\Contracts\Service\ResetInterface;
19 | 
20 | /**
21 |  * @author Gary PEGEOT <garypegeot@gmail.com>
22 |  *
23 |  * @implements TransportFactoryInterface<InMemoryTransport>
24 |  */
25 | class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterface
26 | {
27 |     /**
28 |      * @var InMemoryTransport[]
29 |      */
30 |     private array $createdTransports = [];
31 | 
32 |     public function __construct(
33 |         private readonly ?ClockInterface $clock = null,
34 |     ) {
35 |     }
36 | 
37 |     public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
38 |     {
39 |         ['serialize' => $serialize] = $this->parseDsn($dsn);
40 | 
41 |         return $this->createdTransports[] = new InMemoryTransport($serialize ? $serializer : null, $this->clock);
42 |     }
43 | 
44 |     public function supports(string $dsn, array $options): bool
45 |     {
46 |         return str_starts_with($dsn, 'in-memory://');
47 |     }
48 | 
49 |     public function reset(): void
50 |     {
51 |         foreach ($this->createdTransports as $transport) {
52 |             $transport->reset();
53 |         }
54 |     }
55 | 
56 |     private function parseDsn(string $dsn): array
57 |     {
58 |         $query = [];
59 |         if ($queryAsString = strstr($dsn, '?')) {
60 |             parse_str(ltrim($queryAsString, '?'), $query);
61 |         }
62 | 
63 |         return [
64 |             'serialize' => filter_var($query['serialize'] ?? false, \FILTER_VALIDATE_BOOL),
65 |         ];
66 |     }
67 | }
68 | 


--------------------------------------------------------------------------------
/Transport/Receiver/KeepaliveReceiverInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\TransportException;
16 | 
17 | interface KeepaliveReceiverInterface extends ReceiverInterface
18 | {
19 |     /**
20 |      * Informs the transport that the message is still being processed to avoid a timeout on the transport's side.
21 |      *
22 |      * @param int|null $seconds The minimum duration the message should be kept alive
23 |      *
24 |      * @throws TransportException If there is an issue communicating with the transport
25 |      */
26 |     public function keepalive(Envelope $envelope, ?int $seconds = null): void;
27 | }
28 | 


--------------------------------------------------------------------------------
/Transport/Receiver/ListableReceiverInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Used when a receiver has the ability to list messages and find specific messages.
18 |  * A receiver that implements this should add the TransportMessageIdStamp
19 |  * to the Envelopes that it returns.
20 |  *
21 |  * @author Ryan Weaver <ryan@symfonycasts.com>
22 |  */
23 | interface ListableReceiverInterface extends ReceiverInterface
24 | {
25 |     /**
26 |      * Returns all the messages (up to the limit) in this receiver.
27 |      *
28 |      * Messages should be given the same stamps as when using ReceiverInterface::get().
29 |      *
30 |      * @return Envelope[]|iterable
31 |      */
32 |     public function all(?int $limit = null): iterable;
33 | 
34 |     /**
35 |      * Returns the Envelope by id or none.
36 |      *
37 |      * Message should be given the same stamps as when using ReceiverInterface::get().
38 |      */
39 |     public function find(mixed $id): ?Envelope;
40 | }
41 | 


--------------------------------------------------------------------------------
/Transport/Receiver/MessageCountAwareInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | /**
15 |  * @author Samuel Roze <samuel.roze@gmail.com>
16 |  * @author Ryan Weaver <ryan@symfonycasts.com>
17 |  */
18 | interface MessageCountAwareInterface
19 | {
20 |     /**
21 |      * Returns the number of messages waiting to be handled.
22 |      *
23 |      * In some systems, this may be an approximate number.
24 |      */
25 |     public function getMessageCount(): int;
26 | }
27 | 


--------------------------------------------------------------------------------
/Transport/Receiver/QueueReceiverInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Some transports may have multiple queues. This interface is used to read from only some queues.
18 |  *
19 |  * @author David Buchmann <mail@davidbu.ch>
20 |  */
21 | interface QueueReceiverInterface extends ReceiverInterface
22 | {
23 |     /**
24 |      * Get messages from the specified queue names instead of consuming from all queues.
25 |      *
26 |      * @param string[] $queueNames
27 |      *
28 |      * @return Envelope[]
29 |      */
30 |     public function getFromQueues(array $queueNames): iterable;
31 | }
32 | 


--------------------------------------------------------------------------------
/Transport/Receiver/ReceiverInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\TransportException;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  * @author Ryan Weaver <ryan@symfonycasts.com>
20 |  */
21 | interface ReceiverInterface
22 | {
23 |     /**
24 |      * Receives some messages.
25 |      *
26 |      * While this method could return an unlimited number of messages,
27 |      * the intention is that it returns only one, or a "small number"
28 |      * of messages each time. This gives the user more flexibility:
29 |      * they can finish processing the one (or "small number") of messages
30 |      * from this receiver and move on to check other receivers for messages.
31 |      * If this method returns too many messages, it could cause a
32 |      * blocking effect where handling the messages received from one
33 |      * call to get() takes a long time, blocking other receivers from
34 |      * being called.
35 |      *
36 |      * If applicable, the Envelope should contain a TransportMessageIdStamp.
37 |      *
38 |      * If a received message cannot be decoded, the message should not
39 |      * be retried again (e.g. if there's a queue, it should be removed)
40 |      * and a MessageDecodingFailedException should be thrown.
41 |      *
42 |      * @return iterable<Envelope>
43 |      *
44 |      * @throws TransportException If there is an issue communicating with the transport
45 |      */
46 |     public function get(): iterable;
47 | 
48 |     /**
49 |      * Acknowledges that the passed message was handled.
50 |      *
51 |      * @throws TransportException If there is an issue communicating with the transport
52 |      */
53 |     public function ack(Envelope $envelope): void;
54 | 
55 |     /**
56 |      * Called when handling the message failed and it should not be retried.
57 |      *
58 |      * @throws TransportException If there is an issue communicating with the transport
59 |      */
60 |     public function reject(Envelope $envelope): void;
61 | }
62 | 


--------------------------------------------------------------------------------
/Transport/Receiver/SingleMessageReceiver.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Receiver;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Receiver that decorates another, but receives only 1 specific message.
18 |  *
19 |  * @author Ryan Weaver <ryan@symfonycasts.com>
20 |  *
21 |  * @internal
22 |  */
23 | class SingleMessageReceiver implements ReceiverInterface
24 | {
25 |     private bool $hasReceived = false;
26 | 
27 |     public function __construct(
28 |         private ReceiverInterface $receiver,
29 |         private Envelope $envelope,
30 |     ) {
31 |     }
32 | 
33 |     public function get(): iterable
34 |     {
35 |         if ($this->hasReceived) {
36 |             return [];
37 |         }
38 | 
39 |         $this->hasReceived = true;
40 | 
41 |         return [$this->envelope];
42 |     }
43 | 
44 |     public function ack(Envelope $envelope): void
45 |     {
46 |         $this->receiver->ack($envelope);
47 |     }
48 | 
49 |     public function reject(Envelope $envelope): void
50 |     {
51 |         $this->receiver->reject($envelope);
52 |     }
53 | }
54 | 


--------------------------------------------------------------------------------
/Transport/Sender/SenderInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Sender;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\ExceptionInterface;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  */
20 | interface SenderInterface
21 | {
22 |     /**
23 |      * Sends the given envelope.
24 |      *
25 |      * The sender can read different stamps for transport configuration,
26 |      * like delivery delay.
27 |      *
28 |      * If applicable, the returned Envelope should contain a TransportMessageIdStamp.
29 |      *
30 |      * @throws ExceptionInterface
31 |      */
32 |     public function send(Envelope $envelope): Envelope;
33 | }
34 | 


--------------------------------------------------------------------------------
/Transport/Sender/SendersLocator.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Transport\Sender;
 13 | 
 14 | use Psr\Container\ContainerInterface;
 15 | use Symfony\Component\Messenger\Attribute\AsMessage;
 16 | use Symfony\Component\Messenger\Envelope;
 17 | use Symfony\Component\Messenger\Exception\RuntimeException;
 18 | use Symfony\Component\Messenger\Handler\HandlersLocator;
 19 | use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
 20 | 
 21 | /**
 22 |  * Maps a message to a list of senders.
 23 |  *
 24 |  * @author Fabien Potencier <fabien@symfony.com>
 25 |  */
 26 | class SendersLocator implements SendersLocatorInterface
 27 | {
 28 |     /**
 29 |      * @param array<string, list<string>> $sendersMap     An array, keyed by "type", set to an array of sender aliases
 30 |      * @param ContainerInterface          $sendersLocator Locator of senders, keyed by sender alias
 31 |      */
 32 |     public function __construct(
 33 |         private array $sendersMap,
 34 |         private ContainerInterface $sendersLocator,
 35 |     ) {
 36 |     }
 37 | 
 38 |     public function getSenders(Envelope $envelope): iterable
 39 |     {
 40 |         if ($envelope->all(TransportNamesStamp::class)) {
 41 |             foreach ($envelope->last(TransportNamesStamp::class)->getTransportNames() as $senderAlias) {
 42 |                 yield from $this->getSenderFromAlias($senderAlias);
 43 |             }
 44 | 
 45 |             return;
 46 |         }
 47 | 
 48 |         $seen = [];
 49 |         $found = false;
 50 | 
 51 |         foreach (HandlersLocator::listTypes($envelope) as $type) {
 52 |             if (str_ends_with($type, '*') && $seen) {
 53 |                 // the '*' acts as a fallback, if other senders already matched
 54 |                 // with previous types, skip the senders bound to the fallback
 55 |                 continue;
 56 |             }
 57 | 
 58 |             foreach ($this->sendersMap[$type] ?? [] as $senderAlias) {
 59 |                 if (!\in_array($senderAlias, $seen, true)) {
 60 |                     $seen[] = $senderAlias;
 61 | 
 62 |                     yield from $this->getSenderFromAlias($senderAlias);
 63 |                     $found = true;
 64 |                 }
 65 |             }
 66 |         }
 67 | 
 68 |         // Let the configuration-driven map upper override message attributes,
 69 |         // this allows environment-specific configuration overriding hardcoded
 70 |         // transport name.
 71 |         if ($found) {
 72 |             return;
 73 |         }
 74 | 
 75 |         foreach ($this->getTransportNamesFromAttribute($envelope) as $senderAlias) {
 76 |             yield from $this->getSenderFromAlias($senderAlias);
 77 |         }
 78 |     }
 79 | 
 80 |     private function getTransportNamesFromAttribute(Envelope $envelope): array
 81 |     {
 82 |         $transports = [];
 83 |         $messageClass = $envelope->getMessage()::class;
 84 | 
 85 |         foreach ([$messageClass] + class_parents($messageClass) + class_implements($messageClass) as $class) {
 86 |             foreach ((new \ReflectionClass($class))->getAttributes(AsMessage::class, \ReflectionAttribute::IS_INSTANCEOF) as $refAttr) {
 87 |                 $asMessage = $refAttr->newInstance();
 88 | 
 89 |                 if ($asMessage->transport) {
 90 |                     $transports = array_merge($transports, (array) $asMessage->transport);
 91 |                 }
 92 |             }
 93 |         }
 94 | 
 95 |         return $transports;
 96 |     }
 97 | 
 98 |     private function getSenderFromAlias(string $senderAlias): iterable
 99 |     {
100 |         if (!$this->sendersLocator->has($senderAlias)) {
101 |             throw new RuntimeException(\sprintf('Invalid senders configuration: sender "%s" is not in the senders locator.', $senderAlias));
102 |         }
103 | 
104 |         yield $senderAlias => $this->sendersLocator->get($senderAlias);
105 |     }
106 | }
107 | 


--------------------------------------------------------------------------------
/Transport/Sender/SendersLocatorInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Sender;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | 
16 | /**
17 |  * Maps a message to a list of senders.
18 |  *
19 |  * @author Samuel Roze <samuel.roze@gmail.com>
20 |  * @author Tobias Schultze <http://tobion.de>
21 |  */
22 | interface SendersLocatorInterface
23 | {
24 |     /**
25 |      * Gets the senders for the given message name.
26 |      *
27 |      * @return iterable<string, SenderInterface> Indexed by sender alias if available
28 |      */
29 |     public function getSenders(Envelope $envelope): iterable;
30 | }
31 | 


--------------------------------------------------------------------------------
/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Serialization\Normalizer;
13 | 
14 | use Symfony\Component\ErrorHandler\Exception\FlattenException;
15 | use Symfony\Component\Messenger\Transport\Serialization\Serializer;
16 | use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
17 | use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
18 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
19 | 
20 | /**
21 |  * This normalizer is only used in Debug/Dev/Messenger contexts.
22 |  *
23 |  * @author Pascal Luna <skalpa@zetareticuli.org>
24 |  */
25 | final class FlattenExceptionNormalizer implements DenormalizerInterface, NormalizerInterface
26 | {
27 |     use NormalizerAwareTrait;
28 | 
29 |     public function normalize(mixed $data, ?string $format = null, array $context = []): array
30 |     {
31 |         return [
32 |             'message' => $data->getMessage(),
33 |             'code' => $data->getCode(),
34 |             'headers' => $data->getHeaders(),
35 |             'class' => $data->getClass(),
36 |             'file' => $data->getFile(),
37 |             'line' => $data->getLine(),
38 |             'previous' => null === $data->getPrevious() ? null : $this->normalize($data->getPrevious(), $format, $context),
39 |             'status' => $data->getStatusCode(),
40 |             'status_text' => $data->getStatusText(),
41 |             'trace' => $data->getTrace(),
42 |             'trace_as_string' => $data->getTraceAsString(),
43 |         ];
44 |     }
45 | 
46 |     public function getSupportedTypes(?string $format): array
47 |     {
48 |         return [
49 |             FlattenException::class => false,
50 |         ];
51 |     }
52 | 
53 |     public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
54 |     {
55 |         return $data instanceof FlattenException && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
56 |     }
57 | 
58 |     public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): FlattenException
59 |     {
60 |         $object = new FlattenException();
61 | 
62 |         $object->setMessage($data['message']);
63 |         $object->setCode($data['code']);
64 |         $object->setStatusCode($data['status'] ?? 500);
65 |         $object->setClass($data['class']);
66 |         $object->setFile($data['file']);
67 |         $object->setLine($data['line']);
68 |         $object->setStatusText($data['status_text']);
69 |         $object->setHeaders((array) $data['headers']);
70 | 
71 |         if (isset($data['previous'])) {
72 |             $object->setPrevious($this->denormalize($data['previous'], $type, $format, $context));
73 |         }
74 | 
75 |         $property = new \ReflectionProperty(FlattenException::class, 'trace');
76 |         $property->setValue($object, (array) $data['trace']);
77 | 
78 |         $property = new \ReflectionProperty(FlattenException::class, 'traceAsString');
79 |         $property->setValue($object, $data['trace_as_string']);
80 | 
81 |         return $object;
82 |     }
83 | 
84 |     public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
85 |     {
86 |         return FlattenException::class === $type && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
87 |     }
88 | }
89 | 


--------------------------------------------------------------------------------
/Transport/Serialization/PhpSerializer.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Transport\Serialization;
 13 | 
 14 | use Symfony\Component\Messenger\Envelope;
 15 | use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
 16 | use Symfony\Component\Messenger\Stamp\MessageDecodingFailedStamp;
 17 | use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
 18 | 
 19 | /**
 20 |  * @author Ryan Weaver<ryan@symfonycasts.com>
 21 |  */
 22 | class PhpSerializer implements SerializerInterface
 23 | {
 24 |     private bool $acceptPhpIncompleteClass = false;
 25 | 
 26 |     /**
 27 |      * @internal
 28 |      */
 29 |     public function acceptPhpIncompleteClass(): void
 30 |     {
 31 |         $this->acceptPhpIncompleteClass = true;
 32 |     }
 33 | 
 34 |     /**
 35 |      * @internal
 36 |      */
 37 |     public function rejectPhpIncompleteClass(): void
 38 |     {
 39 |         $this->acceptPhpIncompleteClass = false;
 40 |     }
 41 | 
 42 |     public function decode(array $encodedEnvelope): Envelope
 43 |     {
 44 |         if (empty($encodedEnvelope['body'])) {
 45 |             throw new MessageDecodingFailedException('Encoded envelope should have at least a "body", or maybe you should implement your own serializer.');
 46 |         }
 47 | 
 48 |         if (!str_ends_with($encodedEnvelope['body'], '}')) {
 49 |             $encodedEnvelope['body'] = base64_decode($encodedEnvelope['body']);
 50 |         }
 51 | 
 52 |         $serializeEnvelope = stripslashes($encodedEnvelope['body']);
 53 | 
 54 |         return $this->safelyUnserialize($serializeEnvelope);
 55 |     }
 56 | 
 57 |     public function encode(Envelope $envelope): array
 58 |     {
 59 |         $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
 60 | 
 61 |         $body = addslashes(serialize($envelope));
 62 | 
 63 |         if (!preg_match('//u', $body)) {
 64 |             $body = base64_encode($body);
 65 |         }
 66 | 
 67 |         return [
 68 |             'body' => $body,
 69 |         ];
 70 |     }
 71 | 
 72 |     private function safelyUnserialize(string $contents): Envelope
 73 |     {
 74 |         if ('' === $contents) {
 75 |             throw new MessageDecodingFailedException('Could not decode an empty message using PHP serialization.');
 76 |         }
 77 | 
 78 |         if ($this->acceptPhpIncompleteClass) {
 79 |             $prevUnserializeHandler = ini_set('unserialize_callback_func', null);
 80 |         } else {
 81 |             $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback');
 82 |         }
 83 |         $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler) {
 84 |             if (__FILE__ === $file && !\in_array($type, [\E_DEPRECATED, \E_USER_DEPRECATED], true)) {
 85 |                 throw new \ErrorException($msg, 0, $type, $file, $line);
 86 |             }
 87 | 
 88 |             return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
 89 |         });
 90 | 
 91 |         try {
 92 |             /** @var Envelope */
 93 |             $envelope = unserialize($contents);
 94 |         } catch (\Throwable $e) {
 95 |             if ($e instanceof MessageDecodingFailedException) {
 96 |                 throw $e;
 97 |             }
 98 | 
 99 |             throw new MessageDecodingFailedException('Could not decode Envelope: '.$e->getMessage(), 0, $e);
100 |         } finally {
101 |             restore_error_handler();
102 |             ini_set('unserialize_callback_func', $prevUnserializeHandler);
103 |         }
104 | 
105 |         if (!$envelope instanceof Envelope) {
106 |             throw new MessageDecodingFailedException('Could not decode message into an Envelope.');
107 |         }
108 | 
109 |         if ($envelope->getMessage() instanceof \__PHP_Incomplete_Class) {
110 |             $envelope = $envelope->with(new MessageDecodingFailedStamp());
111 |         }
112 | 
113 |         return $envelope;
114 |     }
115 | 
116 |     /**
117 |      * @internal
118 |      */
119 |     public static function handleUnserializeCallback(string $class): never
120 |     {
121 |         throw new MessageDecodingFailedException(\sprintf('Message class "%s" not found during decoding.', $class));
122 |     }
123 | }
124 | 


--------------------------------------------------------------------------------
/Transport/Serialization/SerializerInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Serialization;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
16 | 
17 | /**
18 |  * @author Samuel Roze <samuel.roze@gmail.com>
19 |  */
20 | interface SerializerInterface
21 | {
22 |     /**
23 |      * Decodes an envelope and its message from an encoded-form.
24 |      *
25 |      * The `$encodedEnvelope` parameter is a key-value array that
26 |      * describes the envelope and its content, that will be used by the different transports.
27 |      *
28 |      * The most common keys are:
29 |      * - `body` (string) - the message body
30 |      * - `headers` (string<string>) - a key/value pair of headers
31 |      *
32 |      * @throws MessageDecodingFailedException
33 |      */
34 |     public function decode(array $encodedEnvelope): Envelope;
35 | 
36 |     /**
37 |      * Encodes an envelope content (message & stamps) to a common format understandable by transports.
38 |      * The encoded array should only contain scalars and arrays.
39 |      *
40 |      * Stamps that implement NonSendableStampInterface should
41 |      * not be encoded.
42 |      *
43 |      * The most common keys of the encoded array are:
44 |      * - `body` (string) - the message body
45 |      * - `headers` (string<string>) - a key/value pair of headers
46 |      */
47 |     public function encode(Envelope $envelope): array;
48 | }
49 | 


--------------------------------------------------------------------------------
/Transport/SetupableTransportInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport;
13 | 
14 | /**
15 |  * @author Vincent Touzet <vincent.touzet@gmail.com>
16 |  */
17 | interface SetupableTransportInterface
18 | {
19 |     /**
20 |      * Setup the transport.
21 |      */
22 |     public function setup(): void;
23 | }
24 | 


--------------------------------------------------------------------------------
/Transport/Sync/SyncTransport.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Sync;
13 | 
14 | use Symfony\Component\Messenger\Envelope;
15 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
16 | use Symfony\Component\Messenger\MessageBusInterface;
17 | use Symfony\Component\Messenger\Stamp\ReceivedStamp;
18 | use Symfony\Component\Messenger\Stamp\SentStamp;
19 | use Symfony\Component\Messenger\Transport\TransportInterface;
20 | 
21 | /**
22 |  * Transport that immediately marks messages as received and dispatches for handling.
23 |  *
24 |  * @author Ryan Weaver <ryan@symfonycasts.com>
25 |  */
26 | class SyncTransport implements TransportInterface
27 | {
28 |     public function __construct(
29 |         private MessageBusInterface $messageBus,
30 |     ) {
31 |     }
32 | 
33 |     public function get(): iterable
34 |     {
35 |         throw new InvalidArgumentException('You cannot receive messages from the Messenger SyncTransport.');
36 |     }
37 | 
38 |     public function ack(Envelope $envelope): void
39 |     {
40 |         throw new InvalidArgumentException('You cannot call ack() on the Messenger SyncTransport.');
41 |     }
42 | 
43 |     public function reject(Envelope $envelope): void
44 |     {
45 |         throw new InvalidArgumentException('You cannot call reject() on the Messenger SyncTransport.');
46 |     }
47 | 
48 |     public function send(Envelope $envelope): Envelope
49 |     {
50 |         /** @var SentStamp|null $sentStamp */
51 |         $sentStamp = $envelope->last(SentStamp::class);
52 |         $alias = null === $sentStamp ? 'sync' : ($sentStamp->getSenderAlias() ?: $sentStamp->getSenderClass());
53 | 
54 |         $envelope = $envelope->with(new ReceivedStamp($alias));
55 | 
56 |         return $this->messageBus->dispatch($envelope);
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/Transport/Sync/SyncTransportFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport\Sync;
13 | 
14 | use Symfony\Component\Messenger\MessageBusInterface;
15 | use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
16 | use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
17 | use Symfony\Component\Messenger\Transport\TransportInterface;
18 | 
19 | /**
20 |  * @author Ryan Weaver <ryan@symfonycasts.com>
21 |  *
22 |  * @implements TransportFactoryInterface<SyncTransport>
23 |  */
24 | class SyncTransportFactory implements TransportFactoryInterface
25 | {
26 |     public function __construct(
27 |         private MessageBusInterface $messageBus,
28 |     ) {
29 |     }
30 | 
31 |     public function createTransport(#[\SensitiveParameter] string $dsn, array $options, SerializerInterface $serializer): TransportInterface
32 |     {
33 |         return new SyncTransport($this->messageBus);
34 |     }
35 | 
36 |     public function supports(#[\SensitiveParameter] string $dsn, array $options): bool
37 |     {
38 |         return str_starts_with($dsn, 'sync://');
39 |     }
40 | }
41 | 


--------------------------------------------------------------------------------
/Transport/TransportFactory.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | /*
  4 |  * This file is part of the Symfony package.
  5 |  *
  6 |  * (c) Fabien Potencier <fabien@symfony.com>
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | namespace Symfony\Component\Messenger\Transport;
 13 | 
 14 | use Symfony\Component\Messenger\Exception\InvalidArgumentException;
 15 | use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
 16 | 
 17 | /**
 18 |  * @author Samuel Roze <samuel.roze@gmail.com>
 19 |  *
 20 |  * @implements TransportFactoryInterface<TransportInterface>
 21 |  */
 22 | class TransportFactory implements TransportFactoryInterface
 23 | {
 24 |     /**
 25 |      * @param iterable<mixed, TransportFactoryInterface> $factories
 26 |      */
 27 |     public function __construct(
 28 |         private iterable $factories,
 29 |     ) {
 30 |     }
 31 | 
 32 |     public function createTransport(#[\SensitiveParameter] string $dsn, array $options, SerializerInterface $serializer): TransportInterface
 33 |     {
 34 |         foreach ($this->factories as $factory) {
 35 |             if ($factory->supports($dsn, $options)) {
 36 |                 return $factory->createTransport($dsn, $options, $serializer);
 37 |             }
 38 |         }
 39 | 
 40 |         // Help the user to select Symfony packages based on protocol.
 41 |         $packageSuggestion = '';
 42 |         if (str_starts_with($dsn, 'amqp://')) {
 43 |             $packageSuggestion = ' Run "composer require symfony/amqp-messenger" to install AMQP transport.';
 44 |         } elseif (str_starts_with($dsn, 'doctrine://')) {
 45 |             $packageSuggestion = ' Run "composer require symfony/doctrine-messenger" to install Doctrine transport.';
 46 |         } elseif (str_starts_with($dsn, 'redis://') || str_starts_with($dsn, 'rediss://')) {
 47 |             $packageSuggestion = ' Run "composer require symfony/redis-messenger" to install Redis transport.';
 48 |         } elseif (str_starts_with($dsn, 'valkey://') || str_starts_with($dsn, 'valkeys://')) {
 49 |             $packageSuggestion = ' Run "composer require symfony/redis-messenger" to install Valkey transport.';
 50 |         } elseif (str_starts_with($dsn, 'sqs://') || preg_match('#^https://sqs\.[\w\-]+\.amazonaws\.com/.+#', $dsn)) {
 51 |             $packageSuggestion = ' Run "composer require symfony/amazon-sqs-messenger" to install Amazon SQS transport.';
 52 |         } elseif (str_starts_with($dsn, 'beanstalkd://')) {
 53 |             $packageSuggestion = ' Run "composer require symfony/beanstalkd-messenger" to install Beanstalkd transport.';
 54 |         }
 55 | 
 56 |         if ($dsn = $this->santitizeDsn($dsn)) {
 57 |             throw new InvalidArgumentException(\sprintf('No transport supports Messenger DSN "%s".', $dsn).$packageSuggestion);
 58 |         }
 59 | 
 60 |         throw new InvalidArgumentException('No transport supports the given Messenger DSN.'.$packageSuggestion);
 61 |     }
 62 | 
 63 |     public function supports(#[\SensitiveParameter] string $dsn, array $options): bool
 64 |     {
 65 |         foreach ($this->factories as $factory) {
 66 |             if ($factory->supports($dsn, $options)) {
 67 |                 return true;
 68 |             }
 69 |         }
 70 | 
 71 |         return false;
 72 |     }
 73 | 
 74 |     private function santitizeDsn(string $dsn): string
 75 |     {
 76 |         $parts = parse_url($dsn);
 77 |         $dsn = '';
 78 | 
 79 |         if (isset($parts['scheme'])) {
 80 |             $dsn .= $parts['scheme'].'://';
 81 |         }
 82 | 
 83 |         if (isset($parts['user']) && !isset($parts['pass'])) {
 84 |             $dsn .= '******';
 85 |         } elseif (isset($parts['user'])) {
 86 |             $dsn .= $parts['user'];
 87 |         }
 88 | 
 89 |         if (isset($parts['pass'])) {
 90 |             $dsn .= ':******';
 91 |         }
 92 | 
 93 |         if (isset($parts['host'])) {
 94 |             if (isset($parts['user'])) {
 95 |                 $dsn .= '@';
 96 |             }
 97 |             $dsn .= $parts['host'];
 98 |         }
 99 | 
100 |         if (isset($parts['port'])) {
101 |             $dsn .= ':'.$parts['port'];
102 |         }
103 | 
104 |         if (isset($parts['path'])) {
105 |             $dsn .= $parts['path'];
106 |         }
107 | 
108 |         return $dsn;
109 |     }
110 | }
111 | 


--------------------------------------------------------------------------------
/Transport/TransportFactoryInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport;
13 | 
14 | use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
15 | 
16 | /**
17 |  * Creates a Messenger transport.
18 |  *
19 |  * @author Samuel Roze <samuel.roze@gmail.com>
20 |  *
21 |  * @template-covariant TTransport of TransportInterface
22 |  */
23 | interface TransportFactoryInterface
24 | {
25 |     /**
26 |      * @return TTransport
27 |      */
28 |     public function createTransport(#[\SensitiveParameter] string $dsn, array $options, SerializerInterface $serializer): TransportInterface;
29 | 
30 |     public function supports(#[\SensitiveParameter] string $dsn, array $options): bool;
31 | }
32 | 


--------------------------------------------------------------------------------
/Transport/TransportInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger\Transport;
13 | 
14 | use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
15 | use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
16 | 
17 | /**
18 |  * @author Nicolas Grekas <p@tchwork.com>
19 |  */
20 | interface TransportInterface extends ReceiverInterface, SenderInterface
21 | {
22 | }
23 | 


--------------------------------------------------------------------------------
/WorkerMetadata.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | /*
 4 |  * This file is part of the Symfony package.
 5 |  *
 6 |  * (c) Fabien Potencier <fabien@symfony.com>
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | namespace Symfony\Component\Messenger;
13 | 
14 | /**
15 |  * @author Oleg Krasavin <okwinza@gmail.com>
16 |  */
17 | final class WorkerMetadata
18 | {
19 |     public function __construct(
20 |         private array $metadata,
21 |     ) {
22 |     }
23 | 
24 |     public function set(array $newMetadata): void
25 |     {
26 |         $this->metadata = array_merge($this->metadata, $newMetadata);
27 |     }
28 | 
29 |     /**
30 |      * Returns the queue names the worker consumes from, if "--queues" option was used.
31 |      * Returns null otherwise.
32 |      */
33 |     public function getQueueNames(): ?array
34 |     {
35 |         return $this->metadata['queueNames'] ?? null;
36 |     }
37 | 
38 |     /**
39 |      * Returns an array of unique identifiers for transport receivers the worker consumes from.
40 |      */
41 |     public function getTransportNames(): array
42 |     {
43 |         return $this->metadata['transportNames'] ?? [];
44 |     }
45 | }
46 | 


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "symfony/messenger",
 3 |     "type": "library",
 4 |     "description": "Helps applications send and receive messages to/from other applications or via message queues",
 5 |     "keywords": [],
 6 |     "homepage": "https://symfony.com",
 7 |     "license": "MIT",
 8 |     "authors": [
 9 |         {
10 |             "name": "Samuel Roze",
11 |             "email": "samuel.roze@gmail.com"
12 |         },
13 |         {
14 |             "name": "Symfony Community",
15 |             "homepage": "https://symfony.com/contributors"
16 |         }
17 |     ],
18 |     "require": {
19 |         "php": ">=8.2",
20 |         "psr/log": "^1|^2|^3",
21 |         "symfony/clock": "^6.4|^7.0",
22 |         "symfony/deprecation-contracts": "^2.5|^3"
23 |     },
24 |     "require-dev": {
25 |         "psr/cache": "^1.0|^2.0|^3.0",
26 |         "symfony/console": "^7.2",
27 |         "symfony/dependency-injection": "^6.4|^7.0",
28 |         "symfony/event-dispatcher": "^6.4|^7.0",
29 |         "symfony/http-kernel": "^6.4|^7.0",
30 |         "symfony/process": "^6.4|^7.0",
31 |         "symfony/property-access": "^6.4|^7.0",
32 |         "symfony/lock": "^6.4|^7.0",
33 |         "symfony/rate-limiter": "^6.4|^7.0",
34 |         "symfony/routing": "^6.4|^7.0",
35 |         "symfony/serializer": "^6.4|^7.0",
36 |         "symfony/service-contracts": "^2.5|^3",
37 |         "symfony/stopwatch": "^6.4|^7.0",
38 |         "symfony/validator": "^6.4|^7.0"
39 |     },
40 |     "conflict": {
41 |         "symfony/console": "<7.2",
42 |         "symfony/event-dispatcher": "<6.4",
43 |         "symfony/event-dispatcher-contracts": "<2.5",
44 |         "symfony/framework-bundle": "<6.4",
45 |         "symfony/http-kernel": "<6.4",
46 |         "symfony/lock": "<6.4",
47 |         "symfony/serializer": "<6.4"
48 |     },
49 |     "autoload": {
50 |         "psr-4": { "Symfony\\Component\\Messenger\\": "" },
51 |         "exclude-from-classmap": [
52 |             "/Tests/"
53 |         ]
54 |     },
55 |     "minimum-stability": "dev"
56 | }
57 | 


--------------------------------------------------------------------------------