├── Classes
├── Annotations
│ └── Defer.php
├── Command
│ ├── JobCommandController.php
│ └── QueueCommandController.php
├── Exception.php
├── Job
│ ├── Aspect
│ │ └── DeferMethodCallAspect.php
│ ├── JobInterface.php
│ ├── JobManager.php
│ └── StaticMethodCallJob.php
├── Queue
│ ├── FakeQueue.php
│ ├── Message.php
│ ├── QueueInterface.php
│ └── QueueManager.php
└── Utility
│ └── VariableDumper.php
├── CodeOfConduct.rst
├── Configuration
├── Caches.yaml
├── Objects.yaml
└── Settings.yaml
├── LICENSE
├── README.md
├── Resources
└── Private
│ └── Schema
│ └── Settings
│ └── Flowpack.JobQueue.Common.schema.yaml
├── Tests
├── Functional
│ ├── AbstractQueueTest.php
│ └── Job
│ │ └── JobManagerTest.php
└── Unit
│ ├── Fixtures
│ ├── TestJob.php
│ └── TestQueue.php
│ ├── Job
│ └── JobManagerTest.php
│ ├── Queue
│ └── QueueManagerTest.php
│ └── Utility
│ └── VariableDumperTest.php
└── composer.json
/Classes/Annotations/Defer.php:
--------------------------------------------------------------------------------
1 | 123) - Supported options depend on the concrete queue implementation)
32 | * @var array
33 | */
34 | public $options;
35 |
36 | /**
37 | * @param string|null $queueName
38 | * @param array|null $options
39 | * @param string|null $value
40 | */
41 | public function __construct(?string $queueName = null, ?array $options = null, ?string $value = null)
42 | {
43 | if ($value === null && $queueName === null) {
44 | throw new \InvalidArgumentException('A Defer attribute must specify a queueName.', 1334128835);
45 | }
46 | $this->queueName = $queueName ?? $value;
47 | $this->options = $options ?? [];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Classes/Command/JobCommandController.php:
--------------------------------------------------------------------------------
1 | exit-after flag can be used in conjunction with cron-jobs in order to manually (re)start
54 | * the worker after a given amount of time.
55 | *
56 | * With the limit flag the number of executed jobs can be limited before the script exits.
57 | * This can be combined with exit-after to exit when either the time or job limit is reached
58 | *
59 | * The verbose flag can be used to gain some insight about which jobs are executed etc.
60 | *
61 | * @param string $queue Name of the queue to fetch messages from. Can also be a comma-separated list of queues.
62 | * @param int $exitAfter If set, this command will exit after the given amount of seconds
63 | * @param int $limit If set, only the given amount of jobs are processed (successful or not) before the script exits
64 | * @param bool $verbose Output debugging information
65 | * @return void
66 | * @throws StopActionException
67 | */
68 | public function workCommand($queue, $exitAfter = null, $limit = null, $verbose = false)
69 | {
70 | if ($verbose) {
71 | $this->output('Watching queue "%s"', [$queue]);
72 | if ($exitAfter !== null) {
73 | $this->output(' for %d seconds', [$exitAfter]);
74 | }
75 | $this->outputLine('...');
76 | }
77 | $startTime = time();
78 | $timeout = null;
79 | $numberOfJobExecutions = 0;
80 | do {
81 | $message = null;
82 | if ($exitAfter !== null) {
83 | $timeout = max(1, $exitAfter - (time() - $startTime));
84 | }
85 | try {
86 | $message = $this->jobManager->waitAndExecute($queue, $timeout);
87 | } catch (JobQueueException $exception) {
88 | $numberOfJobExecutions ++;
89 | $this->outputLine('%s', [$exception->getMessage()]);
90 | if ($verbose && $exception->getPrevious() instanceof \Exception) {
91 | $this->outputLine('Reason:');
92 | $this->outputLine($exception->getPrevious()->getMessage());
93 | }
94 | } catch (\Exception $exception) {
95 | $this->outputLine('Unexpected exception during job execution: %s, aborting...', [$exception->getMessage()]);
96 | $this->quit(1);
97 | }
98 | if ($message !== null) {
99 | $numberOfJobExecutions ++;
100 | if ($verbose) {
101 | $messagePayload = strlen($message->getPayload()) <= 50 ? $message->getPayload() : substr($message->getPayload(), 0, 50) . '...';
102 | $this->outputLine('Successfully executed job "%s" (%s)', [$message->getIdentifier(), $messagePayload]);
103 | }
104 | }
105 | if ($exitAfter !== null && (time() - $startTime) >= $exitAfter) {
106 | if ($verbose) {
107 | $this->outputLine('Quitting after %d seconds due to --exit-after flag', [time() - $startTime]);
108 | }
109 | $this->quit();
110 | }
111 | if ($limit !== null && $numberOfJobExecutions >= $limit) {
112 | if ($verbose) {
113 | $this->outputLine('Quitting after %d executed job%s due to --limit flag', [$numberOfJobExecutions, $numberOfJobExecutions > 1 ? 's' : '']);
114 | }
115 | $this->quit();
116 | }
117 |
118 | } while (true);
119 | }
120 |
121 | /**
122 | * List queued jobs
123 | *
124 | * Shows the label of the next $limit Jobs in a given queue.
125 | *
126 | * @param string $queue The name of the queue
127 | * @param integer $limit Number of jobs to list (some queues only support a limit of 1)
128 | * @return void
129 | * @throws JobQueueException
130 | */
131 | public function listCommand($queue, $limit = 1)
132 | {
133 | $jobs = $this->jobManager->peek($queue, $limit);
134 | $totalCount = $this->queueManager->getQueue($queue)->countReady();
135 | foreach ($jobs as $job) {
136 | $this->outputLine('%s', [$job->getLabel()]);
137 | }
138 |
139 | if ($totalCount > count($jobs)) {
140 | $this->outputLine('(%d omitted) ...', [$totalCount - count($jobs)]);
141 | }
142 | $this->outputLine('(%d total)', [$totalCount]);
143 | }
144 |
145 | /**
146 | * Execute one job
147 | *
148 | * @param string $queue
149 | * @param string $messageCacheIdentifier An identifier to receive the message from the cache
150 | * @return void
151 | * @internal This command is mainly used by the JobManager and FakeQueue in order to execute commands in sub requests
152 | * @throws JobQueueException
153 | */
154 | public function executeCommand($queue, $messageCacheIdentifier)
155 | {
156 | if(!$this->messageCache->has($messageCacheIdentifier)) {
157 | throw new JobQueueException(sprintf('No message with identifier %s was found in the message cache.', $messageCacheIdentifier), 1517868903);
158 | }
159 |
160 | /** @var Message $message */
161 | $message = $this->messageCache->get($messageCacheIdentifier);
162 | $queue = $this->queueManager->getQueue($queue);
163 | $this->jobManager->executeJobForMessage($queue, $message);
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/Classes/Command/QueueCommandController.php:
--------------------------------------------------------------------------------
1 | queueConfigurations as $queueName => $queueConfiguration) {
51 | $queue = $this->queueManager->getQueue($queueName);
52 | try {
53 | $numberOfMessages = $queue->countReady();
54 | } catch (\Exception $e) {
55 | $numberOfMessages = '-';
56 | }
57 | $rows[] = [$queue->getName(), TypeHandling::getTypeForValue($queue), $numberOfMessages];
58 | }
59 | $this->output->outputTable($rows, ['Queue', 'Type', '# messages']);
60 | }
61 |
62 | /**
63 | * Describe a single queue
64 | *
65 | * Displays the configuration for a queue, merged with the preset settings if any.
66 | *
67 | * @param string $queue Name of the queue to describe (e.g. "some-queue")
68 | * @return void
69 | * @throws Exception
70 | */
71 | public function describeCommand($queue)
72 | {
73 | $queueSettings = $this->queueManager->getQueueSettings($queue);
74 | $this->outputLine('Configuration options for Queue %s:', [$queue]);
75 | $rows = [];
76 | foreach ($queueSettings as $name => $value) {
77 | $rows[] = [$name, is_array($value) ? json_encode($value, JSON_PRETTY_PRINT) : $value];
78 | }
79 | $this->output->outputTable($rows, ['Option', 'Value']);
80 | }
81 |
82 | /**
83 | * Initialize a queue
84 | *
85 | * Checks connection to the queue backend and sets up prerequisites (e.g. required database tables)
86 | * Most queue implementations don't need to be initialized explicitly, but it doesn't harm and might help to find misconfigurations
87 | *
88 | * @param string $queue Name of the queue to initialize (e.g. "some-queue")
89 | * @return void
90 | * @throws Exception
91 | * @throws StopActionException
92 | */
93 | public function setupCommand($queue)
94 | {
95 | $queue = $this->queueManager->getQueue($queue);
96 | try {
97 | $queue->setUp();
98 | } catch (\Exception $exception) {
99 | $this->outputLine('An error occurred while trying to setup queue "%s":', [$queue->getName()]);
100 | $this->outputLine('%s (#%s)', [$exception->getMessage(), $exception->getCode()]);
101 | $this->quit(1);
102 | }
103 | $this->outputLine('Queue "%s" has been initialized successfully.', [$queue->getName()]);
104 | }
105 |
106 | /**
107 | * Remove all messages from a queue!
108 | *
109 | * This command will delete all messages from the given queue.
110 | * Thus it should only be used in tests or with great care!
111 | *
112 | * @param string $queue Name of the queue to flush (e.g. "some-queue")
113 | * @param bool $force This flag is required in order to avoid accidental flushes
114 | * @return void
115 | * @throws Exception
116 | * @throws StopActionException
117 | */
118 | public function flushCommand($queue, $force = false)
119 | {
120 | $queue = $this->queueManager->getQueue($queue);
121 | if (!$force) {
122 | $this->outputLine('Use the --force flag if you really want to flush queue "%s"', [$queue->getName()]);
123 | $this->outputLine('Warning: This will delete all messages from the queue!');
124 | $this->quit(1);
125 | }
126 | $queue->flush();
127 | $this->outputLine('Flushed queue "%s".', [$queue->getName()]);
128 | }
129 |
130 | /**
131 | * Submit a message to a given queue
132 | *
133 | * This command can be used to "manually" add messages to a given queue.
134 | *
135 | * Example:
136 | * flow queue:submit some-queue "some payload" --options '{"delay": 14}'
137 | *
138 | * To make this work with the JobManager the payload has to be a serialized
139 | * instance of an object implementing JobInterface.
140 | *
141 | * @param string $queue Name of the queue to submit a message to (e.g. "some-queue")
142 | * @param string $payload Arbitrary payload, for example a serialized instance of a class implementing JobInterface
143 | * @param string $options JSON encoded, for example '{"some-option": "some-value"}'
144 | * @return void
145 | * @throws Exception
146 | */
147 | public function submitCommand($queue, $payload, $options = null)
148 | {
149 | $queue = $this->queueManager->getQueue($queue);
150 | if ($options !== null) {
151 | $options = json_decode($options, true);
152 | }
153 | $messageId = $queue->submit($payload, $options !== null ? $options : []);
154 | $this->outputLine('Submitted payload to queue "%s" with ID "%s".', [$queue->getName(), $messageId]);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/Classes/Exception.php:
--------------------------------------------------------------------------------
1 | processingJob) {
54 | return $joinPoint->getAdviceChain()->proceed($joinPoint);
55 | }
56 | /** @var Defer $deferAnnotation */
57 | $deferAnnotation = $this->reflectionService->getMethodAnnotation($joinPoint->getClassName(), $joinPoint->getMethodName(), Defer::class);
58 | $queueName = $deferAnnotation->queueName;
59 | $job = new StaticMethodCallJob($joinPoint->getClassName(), $joinPoint->getMethodName(), $joinPoint->getMethodArguments());
60 | $this->jobManager->queue($queueName, $job, $deferAnnotation->options);
61 | return null;
62 | }
63 |
64 | /**
65 | * @param boolean $processingJob
66 | */
67 | public function setProcessingJob($processingJob)
68 | {
69 | $this->processingJob = $processingJob;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Classes/Job/JobInterface.php:
--------------------------------------------------------------------------------
1 | queueManager->getQueue($queueName);
92 |
93 | $payload = serialize($job);
94 | $messageId = $queue->submit($payload, $options);
95 | $this->emitMessageSubmitted($queue, $messageId, $payload, $options);
96 | }
97 |
98 | /**
99 | * Wait for a job in the given queue and execute it
100 | * A worker using this method should catch exceptions
101 | *
102 | * @param string $queueName
103 | * @param integer $timeout
104 | * @return Message The message that was processed or NULL if no job was executed and a timeout occurred
105 | * @throws \Exception
106 | * @api
107 | */
108 | public function waitAndExecute(string $queueName, $timeout = null): ?Message
109 | {
110 |
111 | $messageCacheIdentifier = null;
112 | $queue = $this->queueManager->getQueue($queueName);
113 | $message = $queue->waitAndReserve($timeout);
114 | if ($message === null) {
115 | $this->emitMessageTimeout($queue);
116 | // timeout
117 | return null;
118 | }
119 | $this->emitMessageReserved($queue, $message);
120 |
121 | $queueSettings = $this->queueManager->getQueueSettings($queueName);
122 | try {
123 | if (isset($queueSettings['executeIsolated']) && $queueSettings['executeIsolated'] === true) {
124 | $messageCacheIdentifier = sha1(serialize($message));
125 | $this->messageCache->set($messageCacheIdentifier, $message);
126 | Scripts::executeCommand('flowpack.jobqueue.common:job:execute', $this->flowSettings, $queueSettings['outputResults'] ?? false, ['queue' => $queue->getName(), 'messageCacheIdentifier' => $messageCacheIdentifier]);
127 | } else {
128 | $this->executeJobForMessage($queue, $message);
129 | }
130 | } catch (\Throwable $throwable) {
131 | $maximumNumberOfReleases = isset($queueSettings['maximumNumberOfReleases']) ?
132 | (int)$queueSettings['maximumNumberOfReleases'] :
133 | self::DEFAULT_MAXIMUM_NUMBER_RELEASES;
134 | if ($message->getNumberOfReleases() < $maximumNumberOfReleases) {
135 | $releaseOptions = isset($queueSettings['releaseOptions']) ? $queueSettings['releaseOptions'] : [];
136 | $queue->release($message->getIdentifier(), $releaseOptions);
137 | $this->emitMessageReleased($queue, $message, $releaseOptions, new \RuntimeException($throwable->getMessage(), 1659019014, $throwable));
138 | $logMessage = $this->throwableStorage->logThrowable($throwable);
139 | $this->logger->error($logMessage, LogEnvironment::fromMethodName(__METHOD__));
140 | throw new JobQueueException(sprintf('Job execution for job (message: "%s", queue: "%s") failed (%d/%d trials) - RELEASE', $message->getIdentifier(), $queue->getName(), $message->getNumberOfReleases() + 1, $maximumNumberOfReleases + 1), 1334056583, $throwable);
141 | } else {
142 | $queue->abort($message->getIdentifier());
143 | $this->emitMessageFailed($queue, $message, new \RuntimeException($throwable->getMessage(), 1659019015, $throwable));
144 | $logMessage = $this->throwableStorage->logThrowable($throwable);
145 | $this->logger->error($logMessage, LogEnvironment::fromMethodName(__METHOD__));
146 | throw new JobQueueException(sprintf('Job execution for job (message: "%s", queue: "%s") failed (%d/%d trials) - ABORTING', $message->getIdentifier(), $queue->getName(), $message->getNumberOfReleases() + 1, $maximumNumberOfReleases + 1), 1334056584, $throwable);
147 | }
148 | } finally {
149 | if ($messageCacheIdentifier !== null) {
150 | $this->messageCache->remove($messageCacheIdentifier);
151 | }
152 | }
153 |
154 | $queue->finish($message->getIdentifier());
155 | $this->emitMessageFinished($queue, $message);
156 |
157 | return $message;
158 | }
159 |
160 | /**
161 | * @param QueueInterface $queue
162 | * @param Message $message
163 | * @return void
164 | * @throws JobQueueException
165 | * @internal This method has to be public so that it can be run from the command handler (when "executeIsolated" is set). It is not meant to be called from "user land"
166 | */
167 | public function executeJobForMessage(QueueInterface $queue, Message $message): void
168 | {
169 | // TODO stabilize unserialize() call (maybe using PHPs unserialize_callback_func directive)
170 | $job = unserialize($message->getPayload());
171 | if (!$job instanceof JobInterface) {
172 | throw new \RuntimeException(sprintf('The message "%s" in queue "%s" could not be unserialized to a class implementing JobInterface', $message->getIdentifier(), $queue->getName()), 1465901245);
173 | }
174 | $jobExecutionSuccess = $job->execute($queue, $message);
175 | if (!$jobExecutionSuccess) {
176 | throw new JobQueueException(sprintf('execute() for job "%s" did not return TRUE', $job->getLabel()), 1468927872);
177 | }
178 | }
179 |
180 | /**
181 | *
182 | * @param string $queueName
183 | * @param integer $limit
184 | * @return JobInterface[]
185 | * @api
186 | */
187 | public function peek(string $queueName, int $limit = 1): array
188 | {
189 | $queue = $this->queueManager->getQueue($queueName);
190 | $messages = $queue->peek($limit);
191 | return array_map(function (Message $message) {
192 | $job = unserialize($message->getPayload());
193 | return $job;
194 | }, $messages);
195 | }
196 |
197 | /**
198 | * Signal that is triggered when a message has been submitted to a queue
199 | *
200 | * @param QueueInterface $queue The queue a message was submitted to
201 | * @param string $messageId The unique id of the message that was submitted (determined by the queue implementation)
202 | * @param mixed $payload The serialized job that has been added to a queue
203 | * @param array $options Optional array of options passed to JobManager::queue()
204 | * @return void
205 | * @Flow\Signal
206 | * @api
207 | */
208 | protected function emitMessageSubmitted(QueueInterface $queue, $messageId, $payload, array $options = []): void
209 | {
210 | }
211 |
212 | /**
213 | * Signal that is triggered when a message could not be reserved (probably due to a timeout)
214 | *
215 | * @param QueueInterface $queue The queue that returned with a timeout
216 | * @return void
217 | * @Flow\Signal
218 | * @api
219 | */
220 | protected function emitMessageTimeout(QueueInterface $queue): void
221 | {
222 | }
223 |
224 | /**
225 | * Signal that is triggered when a message was reserved
226 | *
227 | * @param QueueInterface $queue The queue the reserved message belongs to
228 | * @param Message $message The message that was reserved
229 | * @return void
230 | * @Flow\Signal
231 | * @api
232 | */
233 | protected function emitMessageReserved(QueueInterface $queue, Message $message): void
234 | {
235 | }
236 |
237 | /**
238 | * Signal that is triggered when a message has been processed successfully
239 | *
240 | * @param QueueInterface $queue The queue the finished message belongs to
241 | * @param Message $message The message that was finished successfully
242 | * @return void
243 | * @Flow\Signal
244 | * @api
245 | */
246 | protected function emitMessageFinished(QueueInterface $queue, Message $message): void
247 | {
248 | }
249 |
250 | /**
251 | * Signal that is triggered when a message has been re-released to the queue
252 | *
253 | * @param QueueInterface $queue The queue the released message belongs to
254 | * @param Message $message The message that was released to the queue again
255 | * @param array $releaseOptions The options that were passed to the release call
256 | * @param \Exception|null $jobExecutionException The exception (if any) thrown by the job execution
257 | * @return void
258 | * @Flow\Signal
259 | * @api
260 | */
261 | protected function emitMessageReleased(QueueInterface $queue, Message $message, array $releaseOptions, ?\Exception $jobExecutionException = null): void
262 | {
263 | }
264 |
265 | /**
266 | * Signal that is triggered when processing of a message failed
267 | *
268 | * @param QueueInterface $queue The queue the failed message belongs to
269 | * @param Message $message The message that could not be executed successfully
270 | * @param \Exception|null $jobExecutionException The exception (if any) thrown by the job execution
271 | * @return void
272 | * @Flow\Signal
273 | * @api
274 | */
275 | protected function emitMessageFailed(QueueInterface $queue, Message $message, ?\Exception $jobExecutionException = null): void
276 | {
277 | }
278 |
279 | }
280 |
--------------------------------------------------------------------------------
/Classes/Job/StaticMethodCallJob.php:
--------------------------------------------------------------------------------
1 | className = $className;
61 | $this->methodName = $methodName;
62 | $this->arguments = $arguments;
63 | }
64 |
65 | /**
66 | * Execute the job
67 | *
68 | * A job should finish itself after successful execution using the queue methods.
69 | *
70 | * @param QueueInterface $queue
71 | * @param Message $message
72 | * @return boolean TRUE If the execution was successful
73 | * @throws \Exception
74 | */
75 | public function execute(QueueInterface $queue, Message $message): bool
76 | {
77 | $service = $this->objectManager->get($this->className);
78 | $this->deferMethodCallAspect->setProcessingJob(true);
79 | try {
80 | $methodName = $this->methodName;
81 | call_user_func_array([$service, $methodName], $this->arguments);
82 | return true;
83 | } catch (\Exception $exception) {
84 | throw $exception;
85 | } finally {
86 | $this->deferMethodCallAspect->setProcessingJob(false);
87 | }
88 | }
89 |
90 | /**
91 | * @return string
92 | */
93 | public function getLabel(): string
94 | {
95 | $arguments = array_map([VariableDumper::class, 'dumpValue'], $this->arguments);
96 | return sprintf('%s::%s(%s)', $this->className, $this->methodName, implode(', ', $arguments));
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Classes/Queue/FakeQueue.php:
--------------------------------------------------------------------------------
1 | name = $name;
55 | if (isset($options['async']) && $options['async'] === true) {
56 | $this->async = true;
57 | }
58 | }
59 |
60 | /**
61 | * @inheritdoc
62 | */
63 | public function setUp(): void
64 | {
65 | // The FakeQueue does not require any setup but we use it to verify the options
66 | if ($this->async && !method_exists(Scripts::class, 'executeCommandAsync')) {
67 | throw new \RuntimeException('The "async" flag is set, but the currently used Flow version doesn\'t support this (Flow 3.3+ is required)', 1468940734);
68 | }
69 | }
70 |
71 | /**
72 | * @inheritdoc
73 | */
74 | public function getName(): string
75 | {
76 | return $this->name;
77 | }
78 |
79 | /**
80 | * @inheritdoc
81 | */
82 | public function submit($payload, array $options = []): string
83 | {
84 | $messageId = Algorithms::generateUUID();
85 | $message = new Message($messageId, $payload);
86 |
87 | $messageCacheIdentifier = sha1(serialize($message));
88 | $this->messageCache->set($messageCacheIdentifier, $message);
89 |
90 | if ($this->async) {
91 | Scripts::executeCommandAsync('flowpack.jobqueue.common:job:execute', $this->flowSettings, ['queue' => $this->name, 'messageCacheIdentifier' => $messageCacheIdentifier]);
92 | } else {
93 | Scripts::executeCommand('flowpack.jobqueue.common:job:execute', $this->flowSettings, true, ['queue' => $this->name, 'messageCacheIdentifier' => $messageCacheIdentifier]);
94 | }
95 | return $messageId;
96 | }
97 |
98 | /**
99 | * @inheritdoc
100 | */
101 | public function waitAndTake(?int $timeout = null): Message
102 | {
103 | throw new \BadMethodCallException('The FakeQueue does not support reserving of messages.' . chr(10) . 'It is not required to use a worker for this queue as messages are handled immediately upon submission.', 1468425275);
104 | }
105 |
106 | /**
107 | * @inheritdoc
108 | */
109 | public function waitAndReserve(?int $timeout = null): Message
110 | {
111 | throw new \BadMethodCallException('The FakeQueue does not support reserving of messages.' . chr(10) . 'It is not required to use a worker for this queue as messages are handled immediately upon submission.', 1468425280);
112 | }
113 |
114 | /**
115 | * @inheritdoc
116 | */
117 | public function release(string $messageId, array $options = []): void
118 | {
119 | throw new \BadMethodCallException('The FakeQueue does not support releasing of failed messages.' . chr(10) . 'The "maximumNumberOfReleases" setting should be removed or set to 0 for this queue!', 1468425285);
120 | }
121 |
122 | /**
123 | * @inheritdoc
124 | */
125 | public function abort(string $messageId): void
126 | {
127 | // The FakeQueue does not support message abortion
128 | }
129 |
130 | /**
131 | * @inheritdoc
132 | */
133 | public function finish(string $messageId): bool
134 | {
135 | // The FakeQueue does not support message finishing
136 | return false;
137 | }
138 |
139 | /**
140 | * @inheritdoc
141 | */
142 | public function peek(int $limit = 1): array
143 | {
144 | return [];
145 | }
146 |
147 | /**
148 | * @inheritdoc
149 | */
150 | public function countReady(): int
151 | {
152 | return 0;
153 | }
154 |
155 | /**
156 | * @inheritdoc
157 | */
158 | public function countReserved(): int
159 | {
160 | return 0;
161 | }
162 |
163 | /**
164 | * @inheritdoc
165 | */
166 | public function countFailed(): int
167 | {
168 | return 0;
169 | }
170 |
171 | /**
172 | * @inheritdoc
173 | */
174 | public function flush(): void
175 | {
176 | // The FakeQueue does not support message flushing
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/Classes/Queue/Message.php:
--------------------------------------------------------------------------------
1 | identifier = $identifier;
49 | $this->payload = $payload;
50 | $this->numberOfReleases = $numberOfReleases;
51 | }
52 |
53 | /**
54 | * @return string
55 | */
56 | public function getIdentifier(): string
57 | {
58 | return $this->identifier;
59 | }
60 |
61 | /**
62 | * @return mixed
63 | */
64 | public function getPayload()
65 | {
66 | return $this->payload;
67 | }
68 |
69 | /**
70 | * @return int
71 | */
72 | public function getNumberOfReleases(): int
73 | {
74 | return $this->numberOfReleases;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Classes/Queue/QueueInterface.php:
--------------------------------------------------------------------------------
1 | queues[$queueName])) {
59 | return $this->queues[$queueName];
60 | }
61 |
62 | $queueSettings = $this->getQueueSettings($queueName);
63 |
64 | if (!isset($queueSettings['className'])) {
65 | throw new JobQueueException(sprintf('Option className for queue "%s" is not configured', $queueName), 1334147126);
66 | }
67 |
68 | $queueObjectName = $queueSettings['className'];
69 | if (!class_exists($queueObjectName)) {
70 | throw new JobQueueException(sprintf('Configured class "%s" for queue "%s" does not exist', $queueObjectName, $queueName), 1445611607);
71 | }
72 |
73 |
74 | if (isset($queueSettings['queueNamePrefix'])) {
75 | $queueNameWithPrefix = $queueSettings['queueNamePrefix'] . $queueName;
76 | } else {
77 | $queueNameWithPrefix = $queueName;
78 | }
79 | $options = isset($queueSettings['options']) ? $queueSettings['options'] : [];
80 | $queue = new $queueObjectName($queueNameWithPrefix, $options);
81 | $this->queues[$queueName] = $queue;
82 |
83 | return $queue;
84 | }
85 |
86 | /**
87 | * Returns the settings for the requested queue, merged with the preset defaults if any
88 | *
89 | * @param string $queueName
90 | * @return array
91 | * @throws JobQueueException if no queue for the given $queueName is configured
92 | * @api
93 | */
94 | public function getQueueSettings(string $queueName): array
95 | {
96 | if (isset($this->queueSettingsRuntimeCache[$queueName])) {
97 | return $this->queueSettingsRuntimeCache[$queueName];
98 | }
99 | if (!isset($this->settings['queues'][$queueName])) {
100 | throw new JobQueueException(sprintf('Queue "%s" is not configured', $queueName), 1334054137);
101 | }
102 | $queueSettings = $this->settings['queues'][$queueName];
103 | if (isset($queueSettings['preset'])) {
104 | $presetName = $queueSettings['preset'];
105 | if (!isset($this->settings['presets'][$presetName])) {
106 | throw new JobQueueException(sprintf('Preset "%s", referred to in settings for queue "%s" is not configured', $presetName, $queueName), 1466677893);
107 | }
108 | $queueSettings = Arrays::arrayMergeRecursiveOverrule($this->settings['presets'][$presetName], $queueSettings);
109 | }
110 | $this->queueSettingsRuntimeCache[$queueName] = $queueSettings;
111 | return $this->queueSettingsRuntimeCache[$queueName];
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/Classes/Utility/VariableDumper.php:
--------------------------------------------------------------------------------
1 | $maximumLength) {
40 | return UnicodeUtilityFunctions::substr($value, 0, $maximumLength - 1) . '…';
41 | }
42 | return $value;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/CodeOfConduct.rst:
--------------------------------------------------------------------------------
1 | Contributor Code of Conduct
2 | ---------------------------
3 |
4 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
5 |
6 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
7 |
8 | Examples of unacceptable behavior by participants include:
9 |
10 | * The use of sexualized language or imagery
11 | * Personal attacks
12 | * Trolling or insulting/derogatory comments
13 | * Public or private harassment
14 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission
15 | * Other unethical or unprofessional conduct.
16 |
17 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
18 |
19 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
20 |
21 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
22 |
23 | This Code of Conduct is adapted from the `Contributor Covenant `_, version 1.2.0, available at (http://contributor-covenant.org/version/1/2/0/
24 |
--------------------------------------------------------------------------------
/Configuration/Caches.yaml:
--------------------------------------------------------------------------------
1 | FlowPackJobQueueCommon_MessageCache:
2 | frontend: Neos\Cache\Frontend\VariableFrontend
3 | persistent: true
4 |
--------------------------------------------------------------------------------
/Configuration/Objects.yaml:
--------------------------------------------------------------------------------
1 | Flowpack\JobQueue\Common\Job\JobManager:
2 | properties:
3 | messageCache:
4 | object:
5 | factoryObjectName: Neos\Flow\Cache\CacheManager
6 | factoryMethodName: getCache
7 | arguments:
8 | 1:
9 | value: FlowPackJobQueueCommon_MessageCache
10 |
11 | Flowpack\JobQueue\Common\Command\JobCommandController:
12 | properties:
13 | messageCache:
14 | object:
15 | factoryObjectName: Neos\Flow\Cache\CacheManager
16 | factoryMethodName: getCache
17 | arguments:
18 | 1:
19 | value: FlowPackJobQueueCommon_MessageCache
20 |
21 | Flowpack\JobQueue\Common\Queue\FakeQueue:
22 | properties:
23 | messageCache:
24 | object:
25 | factoryObjectName: Neos\Flow\Cache\CacheManager
26 | factoryMethodName: getCache
27 | arguments:
28 | 1:
29 | value: FlowPackJobQueueCommon_MessageCache
30 |
--------------------------------------------------------------------------------
/Configuration/Settings.yaml:
--------------------------------------------------------------------------------
1 | Flowpack:
2 | JobQueue:
3 | Common:
4 | presets: []
5 | # 'example':
6 | # # FQN of the queue implementation to use
7 | # className: 'Flownative\JobQueue\Sqlite\Queue\SqliteQueue'
8 | #
9 | # # if defined, all queues are prefixed with the configured "queueNamePrefix". This helps if multiple
10 | # # Flow instances share the same queue service.
11 | # queueNamePrefix: ''
12 | #
13 | # # If set jobs are executed on a separate thread avoiding side-effects and memory-leaks
14 | # executeIsolated: true
15 | #
16 | # # If set to true, forwards the full output (stdout + stderr) of the respective job to the stdout of its "parent"
17 | # outputResults: false
18 | #
19 | # # The max number of times a message is released when job execution failed
20 | # maximumNumberOfReleases: 3
21 | #
22 | # # Default options when submitting new jobs
23 | # options:
24 | # 'foo': 'bar'
25 | # # Default options when a message is released again
26 | # releaseOptions:
27 | # delay: 300
28 | #
29 | queues: []
30 | # 'example':
31 | # preset: 'example'
32 | # options:
33 | # 'overridden': 'option'
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Neos project contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flowpack.JobQueue.Common
2 |
3 | Neos Flow package that allows for asynchronous and distributed execution of tasks.
4 |
5 | ### Table of contents
6 |
7 | * [Quickstart](#quickstart-tldr)
8 | * [Introduction](#introduction)
9 | * [Message Queue](#message-queue)
10 | * [Job Queue](#job-queue)
11 | * [Command Line Interface](#command-line-interface)
12 | * [Signals & Slots](#signal--slots)
13 | * [License](#license)
14 | * [Contributions](#contributions)
15 |
16 | ## Quickstart (TL;DR)
17 |
18 | 1. **Install this package using composer:**
19 |
20 | ```
21 | composer require flowpack/jobqueue-common
22 | ```
23 | (or by adding the dependency to the composer manifest of an installed package)
24 |
25 | 2. **Configure a basic queue by adding the following to your `Settings.yaml`:**
26 |
27 | ```yaml
28 | Flowpack:
29 | JobQueue:
30 | Common:
31 | queues:
32 | 'some-queue':
33 | className: 'Flowpack\JobQueue\Common\Queue\FakeQueue'
34 | ```
35 |
36 | 3. **Initialize the queue (if required)**
37 |
38 | With
39 |
40 | ```
41 | ./flow queue:setup some-queue
42 | ```
43 |
44 | you can setup the queue and/or verify its configuration.
45 | In the case of the `FakeQueue` that step is not required.
46 |
47 | *Note:* The `queue:setup` command won't remove any existing messages, there is no harm in calling it multiple times
48 |
49 | 4. **Annotate any *public* method you want to be executed asynchronously:**
50 |
51 | ```php
52 | use Flowpack\JobQueue\Common\Annotations as Job;
53 |
54 | class SomeClass {
55 |
56 | /**
57 | * @Job\Defer(queueName="some-queue")
58 | */
59 | public function sendEmail($emailAddress)
60 | {
61 | // send some email to $emailAddress
62 | }
63 | }
64 | ```
65 |
66 | or use attributes instead of annotations (PHP 8.0 and later):
67 |
68 | ```php
69 | use Flowpack\JobQueue\Common\Annotations as Job;
70 |
71 | class SomeClass {
72 |
73 | #[Job\Defer(queueName: "some-queue")]
74 | public function sendEmail($emailAddress)
75 | {
76 | // send some email to $emailAddress
77 | }
78 | }
79 | ```
80 |
81 | *Note:* The method needs to be *public* and it must not return anything
82 |
83 | 5. **Start the worker (if required)**
84 |
85 | With the above code in place, whenever the method `SomeClass::sendEmail()` is about to be called that method call is converted into a job that is executed asynchronously[1].
86 |
87 | Unless you use the `FakeQueue` like in the example, a so called `worker` has to be started, to listen for new jobs and execute them::
88 |
89 | ```
90 | ./flow flowpack.jobqueue.common:job:work some-queue --verbose
91 | ```
92 |
93 | ## Introduction
94 |
95 | To get started let's first define some terms:
96 |
97 |
98 | - Message
99 | -
100 | A piece of information passed between programs or systems, sometimes also referred to as "Event".
101 | In the JobQueue packages we use messages to transmit `Jobs`.
102 |
103 | - Message Queue
104 | -
105 | According to Wikipedia "message queues [...] are software-engineering components used for inter-process communication (IPC), or for inter-thread communication within the same process".
106 | In the context of the JobQueue packages we refer to "Message Queue" as a FIFO buffer that distributes messages to one or more consumers, so that every message is only processed once.
107 |
108 | - Job
109 | -
110 | A unit of work to be executed (asynchronously).
111 | In the JobQueue packages we use the Message Queue to store serialized jobs, so it acts as a "Job stream".
112 |
113 | - Job Manager
114 | -
115 | Central authority allowing adding and fetching jobs to/from the Message Queue.
116 |
117 | - Worker
118 | -
119 | The worker watches a queue and triggers the job execution.
120 | This package comes with a `job:work` command that does this (see below)
121 |
122 | - submit
123 | -
124 | New messages are *submitted* to a queue to be processed by a worker
125 |
126 | - reserve
127 | -
128 | Before a message can be processed it has to be *reserved*.
129 | The queue guarantees that a single message can never be reserved by two workers (unless it has been released again)
130 |
131 | - release
132 | -
133 | A reserved message can be *released* to the queue to be processed at a later time.
134 | The *JobManager* does this if Job execution failed and the `maximumNumberOfReleases` setting for the queue is greater than zero
135 |
136 | - abort
137 | -
138 | If a message could not be processed successfully it is *aborted* marking it *failed* in the respective queue so that it can't be reserved again.
139 | The *JobManager* aborts a message if Job execution failed and the message can't be released (again)
140 |
141 | - finish
142 | -
143 | If a message was processed successfully it is marked *finished*.
144 | The *JobManager* finishes a message if Job execution succeeded.
145 |
146 |
147 |
148 | ## Message Queue
149 |
150 | The `Flowpack.JobQueue.Common` package comes with a *very basic* Message Queue implementation `Flowpack\JobQueue\Common\Queue\FakeQueue` that allows for execution of Jobs using sub requests.
151 | It doesn't need any 3rd party tools or server loops and works for basic scenarios. But it has a couple of limitations to be aware of:
152 |
153 | 1. It is not actually a queue, but dispatches jobs immediately as they are queued. So it's not possible to distribute the work to multiple workers
154 |
155 | 2. The `JobManager` is not involved in processing of jobs so the jobs need to take care of error handling themselves.
156 |
157 | 3. For the same reason [Signals](#signal--slots) are *not* emitted for the `FakeQueue`.
158 |
159 | 4. With Flow 3.3+ The `FakeQueue` supports a flag `async`. Without that flag set, executing jobs *block* the main thread!
160 |
161 | For advanced usage it is recommended to use one of the implementing packages like one of the following:
162 | * [Flowpack.JobQueue.Doctrine](https://github.com/Flowpack/jobqueue-doctrine)
163 | * [Flowpack.JobQueue.Beanstalkd](https://github.com/Flowpack/jobqueue-beanstalkd)
164 | * [Flowpack.JobQueue.Redis](https://github.com/Flowpack/jobqueue-redis)
165 |
166 | ### Configuration
167 |
168 | This is the simplest configuration for a queue:
169 |
170 | ```yaml
171 | Flowpack:
172 | JobQueue:
173 | Common:
174 | queues:
175 | 'test':
176 | className: 'Flowpack\JobQueue\Common\Queue\FakeQueue'
177 | ```
178 |
179 | With this a queue named `test` will be available.
180 |
181 | *Note:* For reusable packages you should consider adding a vendor specific prefixes to avoid collisions. We recommend to use a classname or the package name with the function name (e.g. Flowpack.ElasticSearch.ContentRepositoryQueueIndexer.
182 |
183 | ### Queue parameters
184 |
185 | The following parameters are supported by all queues:
186 |
187 | | Parameter | Type | Default | Description |
188 | | ----------------------- |---------|--------:|---------------------------------------------------------------------------------------------------------------------------------|
189 | | className | string | - | FQN of the class implementing the queue |
190 | | maximumNumberOfReleases | integer | 3 | Max. number of times a message is re-
released to the queue if a job failed |
191 | | executeIsolated | boolean | FALSE | If TRUE jobs for this queue are executed in a separate Thread. This makes sense in order to avoid memory leaks and side-effects |
192 | | outputResults | boolean | FALSE | If TRUE the full output (stdout + stderr) of the respective job is forwarded to the stdout of its "parent" (only applicable if `executeIsolated` is `true`) |
193 | | queueNamePrefix | string | - | Optional prefix for the internal queue name,
allowing to re-use the same backend over multiple installations |
194 | | options | array | - | Options for the queue.
Implementation specific (see corresponding package) |
195 | | releaseOptions | array | - | Options that will be passed to `release()` when a job failed.
Implementation specific (see corresponding package) |
196 |
197 | A more complex example could look something like:
198 |
199 | ```yaml
200 | Flowpack:
201 | JobQueue:
202 | Common:
203 | queues:
204 | 'email':
205 | className: 'Flowpack\JobQueue\Beanstalkd\Queue\BeanstalkdQueue'
206 | maximumNumberOfReleases: 5
207 | executeIsolated: true
208 | outputResults: true
209 | queueNamePrefix: 'staging-'
210 | options:
211 | client:
212 | host: 127.0.0.11
213 | port: 11301
214 | defaultTimeout: 50
215 | releaseOptions:
216 | priority: 512
217 | delay: 120
218 | 'log':
219 | className: 'Flowpack\JobQueue\Redis\Queue\RedisQueue'
220 | options:
221 | defaultTimeout: 10
222 | ```
223 |
224 | As you can see, you can have multiple queues in one installations. That allows you to use different backends/options for queues depending on the requirements.
225 |
226 | ### Presets
227 |
228 | If multiple queries share common configuration **presets** can be used to ease readability and maintainability:
229 |
230 | ```yaml
231 | Flowpack:
232 | JobQueue:
233 | Common:
234 | presets:
235 | 'staging-default':
236 | className: 'Flowpack\JobQueue\Doctrine\Queue\DoctrineQueue'
237 | queueNamePrefix: 'staging-'
238 | options:
239 | pollInterval: 2
240 | queues:
241 | 'email':
242 | preset: 'staging-default'
243 | options:
244 | tableName: 'queue_email' # default table name would be "flowpack_jobqueue_messages_email"
245 | 'log':
246 | preset: 'staging-default'
247 | options:
248 | pollInterval: 1 # overrides "pollInterval" of the preset
249 | ```
250 |
251 | This will configure two `DoctrineQueue`s "email" and "log" with some common options but different table names and poll intervals.
252 |
253 | ## Job Queue
254 |
255 |
256 | The job is an arbitrary class implementing `Flowpack\JobQueue\Common\Job\JobInterface`.
257 | This package comes with one implementation `StaticMethodCallJob` that allows for invoking a public method (see [Quickstart](#quickstart-tldr))
258 | but often it makes sense to create a custom Job:
259 |
260 | ```php
261 | emailAddress = $emailAddress;
273 | }
274 |
275 |
276 | public function execute(QueueInterface $queue, Message $message)
277 | {
278 | // TODO: send the email to $this->emailAddress
279 | return true;
280 | }
281 |
282 | public function getIdentifier()
283 | {
284 | return 'SendEmailJob';
285 | }
286 |
287 | public function getLabel()
288 | {
289 | return sprintf('SendEmailJob (email: "%S")', $this->emailAddress);
290 | }
291 | }
292 | ```
293 |
294 | *Note:* It's crucial that the `execute()` method returns TRUE on success, otherwise the corresponding message will be released again and/or marked *failed*.
295 |
296 |
297 | With that in place, the new job can be added to a queue like this:
298 |
299 |
300 | ```php
301 | use Flowpack\JobQueue\Common\Job\JobInterface;
302 | use Flowpack\JobQueue\Common\Job\JobManager;
303 | use Neos\Flow\Annotations as Flow;
304 |
305 | class SomeClass {
306 |
307 | /**
308 | * @Flow\Inject
309 | * @var JobManager
310 | */
311 | protected $jobManager;
312 |
313 | /**
314 | * @return void
315 | */
316 | public function queueJob()
317 | {
318 | $job = new SendEmailJob('some@email.com');
319 | $this->jobManager->queue('queue-name', $job);
320 | }
321 | }
322 | ```
323 |
324 | ## Command Line Interface
325 |
326 | Use the `flowpack.jobqueue.common:queue:*` and `flowpack.jobqueue.common:job:*` commands to interact with the job queues:
327 |
328 | | Command | Description |
329 | | --------------- |----------------------------------------------------------------------------|
330 | | queue:list | List configured queues |
331 | | queue:describe | Shows details for a given queue (settings, ..) |
332 | | queue:setup | Initialize a queue (i.e. create required db tables, check connection, ...) |
333 | | queue:flush | Remove all messages from a queue (requires --force flag) |
334 | | queue:submit | Submit a message to a queue (mainly for testing) |
335 | | job:work | Work on a queue and execute jobs |
336 | | job:list | List queued jobs |
337 |
338 | ## Signal & Slots
339 |
340 | When working with JobQueues proper monitoring is crucial as failures might not be visible immediately.
341 | The `JobManager` emits signals for all relevant events, namely:
342 |
343 | * messageSubmitted
344 | * messageTimeout
345 | * messageReserved
346 | * messageFinished
347 | * messageReleased
348 | * messageFailed
349 |
350 | Those can be used to implement some more sophisticated logging for example:
351 |
352 | ```php
353 | getSignalSlotDispatcher();
373 |
374 | $dispatcher->connect(
375 | JobManager::class, 'messageFailed',
376 | function(QueueInterface $queue, Message $message, ?\Exception $jobExecutionException = null) use ($bootstrap) {
377 | $additionalData = [
378 | 'queue' => $queue->getName(),
379 | 'message' => $message->getIdentifier()
380 | ];
381 | if ($jobExecutionException !== null) {
382 | $additionalData['exception'] = $jobExecutionException->getMessage();
383 | }
384 | $bootstrap->getObjectManager()->get(SystemLoggerInterface::class)->log('Job failed', LOG_ERR, $additionalData);
385 | }
386 | );
387 | }
388 | }
389 | ```
390 |
391 | This would log every failed message to the system log.
392 |
393 | ## License
394 |
395 | This package is licensed under the MIT license
396 |
397 | ## Contributions
398 |
399 | Pull-Requests are more than welcome. Make sure to read the [Code Of Conduct](CodeOfConduct.rst).
400 |
401 | ---
402 |
403 | [1] The `FakeQueue` actually executes Jobs *synchronously* unless the `async` flag is set (requires Flow 3.3+)
404 |
--------------------------------------------------------------------------------
/Resources/Private/Schema/Settings/Flowpack.JobQueue.Common.schema.yaml:
--------------------------------------------------------------------------------
1 | type: dictionary
2 | properties:
3 | 'presets':
4 | type: dictionary
5 | required: TRUE
6 | additionalProperties:
7 | type: dictionary
8 | required: TRUE
9 | additionalProperties: FALSE
10 | properties:
11 | 'maximumNumberOfReleases': { type: integer }
12 | 'className': { type: string, format: class-name }
13 | 'queueNamePrefix': { type: string }
14 | 'executeIsolated': { type: boolean }
15 | 'outputResults': { type: boolean }
16 | 'options': { type: dictionary }
17 | 'releaseOptions': { type: dictionary }
18 | 'queues':
19 | type: dictionary
20 | required: TRUE
21 | additionalProperties:
22 | type: dictionary
23 | required: TRUE
24 | additionalProperties: FALSE
25 | properties:
26 | 'preset': { type: string }
27 | 'maximumNumberOfReleases': { type: integer }
28 | 'className': { type: string, format: class-name }
29 | 'queueNamePrefix': { type: string }
30 | 'executeIsolated': { type: boolean }
31 | 'outputResults': { type: boolean }
32 | 'options': { type: dictionary }
33 | 'releaseOptions': { type: dictionary }
34 |
--------------------------------------------------------------------------------
/Tests/Functional/AbstractQueueTest.php:
--------------------------------------------------------------------------------
1 | objectManager->get(ConfigurationManager::class);
40 | $packageKey = $this->objectManager->getPackageKeyByObjectName(TypeHandling::getTypeForValue($this));
41 | $packageSettings = $configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, $packageKey);
42 | if (!isset($packageSettings['testing']['enabled']) || $packageSettings['testing']['enabled'] !== true) {
43 | $this->markTestSkipped(sprintf('Queue is not configured (%s.testing.enabled != TRUE)', $packageKey));
44 | }
45 | $this->queueSettings = $packageSettings['testing'];
46 | $this->queue = $this->getQueue();
47 | $this->queue->setUp();
48 | $this->queue->flush();
49 | }
50 |
51 | public function tearDown(): void
52 | {
53 | parent::tearDown();
54 | $this->queue->flush();
55 | }
56 |
57 | /**
58 | * @return QueueInterface
59 | */
60 | abstract protected function getQueue();
61 |
62 | /**
63 | * @test
64 | */
65 | public function submitReturnsMessageId()
66 | {
67 | $messageId = $this->queue->submit('some message payload');
68 | self::assertIsString($messageId);
69 | }
70 |
71 | /**
72 | * @test
73 | */
74 | public function submitAndWaitWithMessageWorks()
75 | {
76 | $payload = 'Yeah, tell someone it works!';
77 | $this->queue->submit($payload);
78 |
79 | $message = $this->queue->waitAndTake(1);
80 | self::assertInstanceOf(Message::class, $message, 'waitAndTake should return message');
81 | self::assertEquals($payload, $message->getPayload(), 'message should have payload as before');
82 | }
83 |
84 | /**
85 | * @test
86 | */
87 | public function submitWithDelaySchedulesMessage()
88 | {
89 | $messageId = $this->queue->submit('some message payload', ['delay' => 2]);
90 | self::assertNull($this->queue->waitAndTake(1), 'message was available too soon');
91 | $message = $this->queue->waitAndTake(2);
92 | self::assertInstanceOf(Message::class, $message, 'waitAndTake should return message');
93 | }
94 |
95 | /**
96 | * @test
97 | */
98 | public function waitForMessageTimesOut()
99 | {
100 | self::assertNull($this->queue->waitAndTake(1), 'wait should return NULL after timeout');
101 | }
102 |
103 | /**
104 | * @test
105 | */
106 | public function peekReturnsNextMessagesIfQueueHasMessages()
107 | {
108 | $this->queue->submit('First message');
109 | $this->queue->submit('Another message');
110 |
111 | $messages = $this->queue->peek(1);
112 | self::assertCount(1, $messages, 'peek should return a message');
113 | /** @var Message $firstMessage */
114 | $firstMessage = array_shift($messages);
115 | self::assertEquals('First message', $firstMessage->getPayload());
116 |
117 | $messages = $this->queue->peek(1);
118 | self::assertCount(1, $messages, 'peek should return a message again');
119 | /** @var Message $firstMessage */
120 | $firstMessage = array_shift($messages);
121 | self::assertEquals('First message', $firstMessage->getPayload(), 'second peek should return the same message again');
122 | }
123 |
124 | /**
125 | * @test
126 | */
127 | public function peekReturnsEmptyArrayIfQueueHasNoMessage()
128 | {
129 | self::assertEquals([], $this->queue->peek(), 'peek should not return a message');
130 | }
131 |
132 | /**
133 | * @test
134 | */
135 | public function waitAndReserveWithFinishRemovesMessage()
136 | {
137 | $payload = 'A message';
138 | $messageId = $this->queue->submit($payload);
139 |
140 | $message = $this->queue->waitAndReserve(1);
141 | self::assertNotNull($message, 'waitAndReserve should receive message');
142 | self::assertSame($payload, $message->getPayload(), 'message should have payload as before');
143 |
144 | $message = $this->queue->peek();
145 | self::assertEquals([], $message, 'no message should be present in queue');
146 |
147 | self::assertTrue($this->queue->finish($messageId));
148 | }
149 |
150 | /**
151 | * @test
152 | */
153 | public function releasePutsMessageBackToQueue()
154 | {
155 | $messageId = $this->queue->submit('A message');
156 |
157 | $this->queue->waitAndReserve(1);
158 | self::assertSame(0, $this->queue->countReady());
159 |
160 | $this->queue->release($messageId);
161 | self::assertSame(1, $this->queue->countReady());
162 | }
163 |
164 | /**
165 | * @test
166 | */
167 | public function releaseIncreasesNumberOfReleases()
168 | {
169 | $messageId = $this->queue->submit('A message');
170 |
171 | $message = $this->queue->waitAndReserve(1);
172 | self::assertSame(0, $message->getNumberOfReleases());
173 |
174 | $this->queue->release($messageId);
175 | $message = $this->queue->waitAndReserve(1);
176 | self::assertSame(1, $message->getNumberOfReleases());
177 |
178 | $this->queue->release($messageId);
179 | $message = $this->queue->waitAndReserve(1);
180 | self::assertSame(2, $message->getNumberOfReleases());
181 |
182 | $this->queue->abort($messageId);
183 | }
184 |
185 | /**
186 | * @test
187 | */
188 | public function abortRemovesMessageFromActiveQueue()
189 | {
190 | $messageId = $this->queue->submit('A message');
191 |
192 | $this->queue->waitAndReserve(1);
193 |
194 | $this->queue->abort($messageId);
195 | self::assertSame(0, $this->queue->countReady());
196 | self::assertNull($this->queue->waitAndTake(1));
197 | }
198 |
199 | /**
200 | * @test
201 | */
202 | public function countReadyReturnsZeroByDefault()
203 | {
204 | self::assertSame(0, $this->queue->countReady());
205 | }
206 |
207 | /**
208 | * @test
209 | */
210 | public function countReadyReturnsNumberOfReadyJobs()
211 | {
212 | $this->queue->submit('First message');
213 | $this->queue->submit('Second message');
214 |
215 | self::assertSame(2, $this->queue->countReady());
216 | }
217 |
218 | /**
219 | * @test
220 | */
221 | public function countFailedReturnsZeroByDefault()
222 | {
223 | self::assertSame(0, $this->queue->countFailed());
224 | }
225 |
226 | /**
227 | * @test
228 | */
229 | public function countFailedReturnsNumberOfFailedMessages()
230 | {
231 | $messageId = $this->queue->submit('A message');
232 |
233 | $this->queue->waitAndReserve(1);
234 | self::assertSame(0, $this->queue->countFailed());
235 |
236 | $this->queue->abort($messageId);
237 | self::assertSame(1, $this->queue->countFailed());
238 | }
239 |
240 | /**
241 | * @test
242 | */
243 | public function countReservedReturnsZeroByDefault()
244 | {
245 | self::assertSame(0, $this->queue->countReserved());
246 | }
247 |
248 | /**
249 | * @test
250 | */
251 | public function countReservedReturnsNumberOfReservedMessages()
252 | {
253 | $messageId = $this->queue->submit('A message');
254 |
255 | $this->queue->waitAndReserve(1);
256 | self::assertSame(1, $this->queue->countReserved());
257 |
258 | $this->queue->abort($messageId);
259 | self::assertSame(0, $this->queue->countReserved());
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/Tests/Functional/Job/JobManagerTest.php:
--------------------------------------------------------------------------------
1 | mockQueueManager = $this->getMockBuilder(QueueManager::class)->disableOriginalConstructor()->getMock();
60 | $this->testQueue = new TestQueue('TestQueue');
61 | $this->mockQueueManager->expects($this->any())->method('getQueue')->with('TestQueue')->will($this->returnValue($this->testQueue));
62 | $this->mockQueueManager->expects($this->any())->method('getQueueSettings')->with('TestQueue')->will($this->returnCallback(function() { return $this->queueSettings; }));
63 |
64 | $this->jobManager = new JobManager();
65 | $this->inject($this->jobManager, 'queueManager', $this->mockQueueManager);
66 |
67 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageSubmitted', $this, 'logSignal');
68 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageTimeout', $this, 'logSignal');
69 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageReserved', $this, 'logSignal');
70 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageFinished', $this, 'logSignal');
71 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageReleased', $this, 'logSignal');
72 | self::$bootstrap->getSignalSlotDispatcher()->connect(JobManager::class, 'messageFailed', $this, 'logSignal');
73 | }
74 |
75 | public function tearDown(): void
76 | {
77 | parent::tearDown();
78 | $this->emittedSignals = [];
79 | }
80 |
81 | /**
82 | * Slot for the JobManager signals (see setUp())
83 | *
84 | * @return void
85 | */
86 | public function logSignal()
87 | {
88 | $arguments = func_get_args();
89 | $signalName = array_pop($arguments);
90 | if (!isset($this->emittedSignals[$signalName])) {
91 | $this->emittedSignals[$signalName] = [];
92 | }
93 | $this->emittedSignals[$signalName][] = $arguments;
94 | }
95 |
96 | /**
97 | * @param string $signalName
98 | * @param array $arguments
99 | */
100 | protected function assertSignalEmitted($signalName, array $arguments = [])
101 | {
102 | $fullSignalName = JobManager::class . '::' . $signalName;
103 | if (!isset($this->emittedSignals[$fullSignalName])) {
104 | $this->fail('Signal "' . $signalName . '" has not been emitted!');
105 | }
106 | self::assertCount(1, $this->emittedSignals[$fullSignalName]);
107 | foreach ($arguments as $argumentIndex => $expectedArgument) {
108 | $actualArgument = $this->emittedSignals[$fullSignalName][0][$argumentIndex];
109 | if ($expectedArgument instanceof Constraint) {
110 | $expectedArgument->evaluate($actualArgument);
111 | } else {
112 | self::assertSame($expectedArgument, $actualArgument);
113 | }
114 | }
115 | }
116 |
117 | /**
118 | * @test
119 | */
120 | public function queueEmitsMessageSubmittedSignal()
121 | {
122 | $options = ['foo' => 'bar'];
123 | $this->jobManager->queue('TestQueue', new TestJob(), $options);
124 | $this->assertSignalEmitted('messageSubmitted', [0 => $this->testQueue, 3 => $options]);
125 | }
126 |
127 | /**
128 | * @test
129 | */
130 | public function waitAndExecuteEmitsMessageTimeoutSignal()
131 | {
132 | $this->jobManager->queue('TestQueue', new TestJob());
133 | $this->jobManager->waitAndExecute('TestQueue', 0);
134 | $this->assertSignalEmitted('messageTimeout', [0 => $this->testQueue]);
135 | }
136 |
137 | /**
138 | * @test
139 | */
140 | public function waitAndExecuteEmitsMessageReservedSignal()
141 | {
142 | $this->jobManager->queue('TestQueue', new TestJob());
143 | $this->jobManager->waitAndExecute('TestQueue');
144 | $this->assertSignalEmitted('messageReserved', [0 => $this->testQueue, 1 => new IsInstanceOf(Message::class)]);
145 | }
146 |
147 | /**
148 | * @test
149 | */
150 | public function waitAndExecuteEmitsMessageFinishedSignal()
151 | {
152 | $this->jobManager->queue('TestQueue', new TestJob());
153 | $this->jobManager->waitAndExecute('TestQueue');
154 | $this->assertSignalEmitted('messageFinished', [0 => $this->testQueue, 1 => new IsInstanceOf(Message::class)]);
155 | }
156 |
157 | /**
158 | * @test
159 | */
160 | public function waitAndExecuteEmitsMessageReleasedSignal()
161 | {
162 | $releaseOptions = ['some' => 'releaseOption'];
163 | $this->queueSettings = ['maximumNumberOfReleases' => 1, 'releaseOptions' => $releaseOptions];
164 | $this->jobManager->queue('TestQueue', new TestJob(2));
165 | try {
166 | $this->jobManager->waitAndExecute('TestQueue');
167 | } catch (JobQueueException $exception) {
168 | }
169 | $this->assertSignalEmitted('messageReleased', [$this->testQueue, new IsInstanceOf(Message::class), $releaseOptions, new IsInstanceOf(JobQueueException::class)]);
170 | }
171 |
172 | /**
173 | * @test
174 | */
175 | public function waitAndExecuteEmitsMessageFailedSignal()
176 | {
177 | $this->jobManager->queue('TestQueue', new TestJob(JobManager::DEFAULT_MAXIMUM_NUMBER_RELEASES + 1));
178 | for ($i = 0; $i <= JobManager::DEFAULT_MAXIMUM_NUMBER_RELEASES; $i ++) {
179 | try {
180 | $this->jobManager->waitAndExecute('TestQueue');
181 | } catch (JobQueueException $exception) {
182 | }
183 | }
184 | $this->assertSignalEmitted('messageFailed', [$this->testQueue, new IsInstanceOf(Message::class)]);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/TestJob.php:
--------------------------------------------------------------------------------
1 | failNumberOfTimes = $failNumberOfTimes;
34 | }
35 |
36 | /**
37 | * Do nothing
38 | *
39 | * @param QueueInterface $queue
40 | * @param Message $message
41 | * @return bool
42 | */
43 | public function execute(QueueInterface $queue, Message $message): bool
44 | {
45 | if ($this->failNumberOfTimes > $message->getNumberOfReleases()) {
46 | return false;
47 | }
48 | return true;
49 | }
50 |
51 | /**
52 | * Get a readable label for the job
53 | *
54 | * @return string A label for the job
55 | */
56 | public function getLabel(): string
57 | {
58 | return 'Test Job';
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/TestQueue.php:
--------------------------------------------------------------------------------
1 | name = $name;
82 | if (isset($options['defaultTimeout'])) {
83 | $this->defaultTimeout = (integer)$options['defaultTimeout'];
84 | }
85 | $this->options = $options;
86 | }
87 |
88 | /**
89 | * @inheritdoc
90 | */
91 | public function setUp(): void
92 | {
93 | // The TestQueue does not require any setup
94 | }
95 |
96 | /**
97 | * @inheritdoc
98 | */
99 | public function getName(): string
100 | {
101 | return $this->name;
102 | }
103 |
104 | /**
105 | * @inheritdoc
106 | */
107 | public function submit($payload, array $options = []): string
108 | {
109 | $this->lastSubmitOptions = $options;
110 | $messageId = Algorithms::generateUUID();
111 | $this->readyMessages[$messageId] = $payload;
112 | return $messageId;
113 | }
114 |
115 | /**
116 | * @inheritdoc
117 | */
118 | public function waitAndTake(?int $timeout = null): ?Message
119 | {
120 | $message = $this->reserveMessage($timeout);
121 | if ($message === null) {
122 | return null;
123 | }
124 | unset($this->processingMessages[$message->getIdentifier()]);
125 |
126 | return $message;
127 | }
128 |
129 | /**
130 | * @inheritdoc
131 | */
132 | public function waitAndReserve(?int $timeout = null): ?Message
133 | {
134 | return $this->reserveMessage($timeout);
135 | }
136 |
137 | /**
138 | * @param int|null $timeout
139 | * @return Message
140 | */
141 | protected function reserveMessage(?int $timeout = null): ?Message
142 | {
143 | if ($timeout === null) {
144 | $timeout = $this->defaultTimeout;
145 | }
146 | $startTime = time();
147 |
148 | do {
149 | $nextMessageIdAndPayload = array_slice($this->readyMessages, 0, 1);
150 | if (time() - $startTime >= $timeout) {
151 | return null;
152 | }
153 | } while ($nextMessageIdAndPayload === []);
154 |
155 | $messageId = key($nextMessageIdAndPayload);
156 | $payload = $nextMessageIdAndPayload[$messageId];
157 | unset($this->readyMessages[$messageId]);
158 | $this->processingMessages[$messageId] = $nextMessageIdAndPayload[$messageId];
159 |
160 | $numberOfReleases = isset($this->numberOfReleases[$messageId]) ? $this->numberOfReleases[$messageId] : 0;
161 | return new Message($messageId, $payload, $numberOfReleases);
162 | }
163 |
164 | /**
165 | * @inheritdoc
166 | */
167 | public function release(string $messageId, array $options = []): void
168 | {
169 | $this->lastReleaseOptions = $options;
170 | if (!isset($this->processingMessages[$messageId])) {
171 | return;
172 | }
173 | $payload = $this->processingMessages[$messageId];
174 | $this->numberOfReleases[$messageId] = isset($this->numberOfReleases[$messageId]) ? $this->numberOfReleases[$messageId] + 1 : 1;
175 | unset($this->processingMessages[$messageId]);
176 | $this->readyMessages[$messageId] = $payload;
177 | }
178 |
179 | /**
180 | * @inheritdoc
181 | */
182 | public function abort(string $messageId): void
183 | {
184 | if (!isset($this->readyMessages[$messageId])) {
185 | return;
186 | }
187 | $this->failedMessages[$messageId] = $this->readyMessages[$messageId];
188 | unset($this->readyMessages[$messageId]);
189 | }
190 |
191 | /**
192 | * @inheritdoc
193 | */
194 | public function finish(string $messageId): bool
195 | {
196 | unset($this->processingMessages[$messageId]);
197 | return true;
198 | }
199 |
200 | /**
201 | * @inheritdoc
202 | */
203 | public function peek(int $limit = 1): array
204 | {
205 | $messageIdsAndPayload = array_slice($this->readyMessages, 0, $limit);
206 | $messages = [];
207 | foreach ($messageIdsAndPayload as $messageId => $payload) {
208 | $messages[] = new Message($messageId, $payload);
209 | }
210 | return $messages;
211 | }
212 |
213 | /**
214 | * @inheritdoc
215 | */
216 | public function countReady(): int
217 | {
218 | return count($this->readyMessages);
219 | }
220 |
221 | /**
222 | * @inheritdoc
223 | */
224 | public function countReserved(): int
225 | {
226 | return count($this->reservedMessages);
227 | }
228 |
229 | /**
230 | * @inheritdoc
231 | */
232 | public function countFailed(): int
233 | {
234 | return count($this->failedMessages);
235 | }
236 |
237 | /**
238 | * @inheritdoc
239 | */
240 | public function flush(): void
241 | {
242 | $this->readyMessages = $this->processingMessages = $this->failedMessages = $this->numberOfReleases = [];
243 | }
244 |
245 | /**
246 | * @return array
247 | */
248 | public function getOptions(): array
249 | {
250 | return $this->options;
251 | }
252 |
253 | /**
254 | * @return array
255 | */
256 | public function getLastSubmitOptions(): array
257 | {
258 | return $this->lastSubmitOptions;
259 | }
260 |
261 | /**
262 | * @return array
263 | */
264 | public function getLastReleaseOptions(): array
265 | {
266 | return $this->lastReleaseOptions;
267 | }
268 |
269 | }
270 |
--------------------------------------------------------------------------------
/Tests/Unit/Job/JobManagerTest.php:
--------------------------------------------------------------------------------
1 | mockQueueManager = $this->getMockBuilder(QueueManager::class)->disableOriginalConstructor()->getMock();
46 | $this->testQueue = new TestQueue('TestQueue');
47 | $this->mockQueueManager->expects($this->any())->method('getQueue')->with('TestQueue')->will(self::returnValue($this->testQueue));
48 |
49 | $this->jobManager = new JobManager();
50 | $this->inject($this->jobManager, 'queueManager', $this->mockQueueManager);
51 | }
52 |
53 | /**
54 | * @test
55 | */
56 | public function queueSubmitsMessageToQueue()
57 | {
58 | $job = new TestJob();
59 | $this->jobManager->queue('TestQueue', $job);
60 |
61 | $messageId = $this->testQueue->peek();
62 | self::assertNotNull($messageId);
63 | }
64 |
65 | /**
66 | * @test
67 | */
68 | public function queuePassesOptionsToQueue()
69 | {
70 | $mockOptions = ['foo' => 'Bar', 'baz' => 'Foos'];
71 | $job = new TestJob();
72 | $this->jobManager->queue('TestQueue', $job, $mockOptions);
73 |
74 | self::assertSame($mockOptions, $this->testQueue->getLastSubmitOptions());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Tests/Unit/Queue/QueueManagerTest.php:
--------------------------------------------------------------------------------
1 | queueManager = new QueueManager();
31 | $this->inject($this->queueManager, 'settings', [
32 | 'queues' => [
33 | 'TestQueue' => [
34 | 'className' => TestQueue::class
35 | ]
36 | ]
37 | ]);
38 | }
39 |
40 | /**
41 | * @test
42 | */
43 | public function getQueueSettingsMergesPresetWithQueueSettings()
44 | {
45 | $this->inject($this->queueManager, 'settings', [
46 | 'presets' => [
47 | 'somePreset' => [
48 | 'className' => 'Some\Preset\ClassName',
49 | 'maximumNumberOfReleases' => 123,
50 | 'queueNamePrefix' => 'presetPrefix',
51 | 'options' => [
52 | 'option1' => 'from preset',
53 | 'option2' => 'from preset',
54 | ],
55 | 'releaseOptions' => [
56 | 'bar' => 'from preset',
57 | ]
58 | ]
59 | ],
60 | 'queues' => [
61 | 'TestQueue' => [
62 | 'preset' => 'somePreset',
63 | 'className' => TestQueue::class,
64 | 'maximumNumberOfReleases' => 321,
65 | 'queueNamePrefix' => 'queuePrefix',
66 | 'options' => [
67 | 'option2' => 'overridden from queue',
68 | 'option3' => 'from queue',
69 | ],
70 | 'releaseOptions' => [
71 | 'bar' => 'from queue',
72 | ]
73 | ]
74 | ]
75 | ]);
76 |
77 | $expectedSettings = [
78 | 'className' => TestQueue::class,
79 | 'maximumNumberOfReleases' => 321,
80 | 'queueNamePrefix' => 'queuePrefix',
81 | 'options' => [
82 | 'option1' => 'from preset',
83 | 'option2' => 'overridden from queue',
84 | 'option3' => 'from queue',
85 | ],
86 | 'releaseOptions' => [
87 | 'bar' => 'from queue',
88 | ],
89 | 'preset' => 'somePreset'
90 | ];
91 |
92 | $queueSettings = $this->queueManager->getQueueSettings('TestQueue');
93 | self::assertSame($expectedSettings, $queueSettings);
94 | }
95 |
96 | /**
97 | * @test
98 | */
99 | public function getQueueCreatesInstanceByQueueName()
100 | {
101 | /** @var TestQueue $queue */
102 | $queue = $this->queueManager->getQueue('TestQueue');
103 | self::assertInstanceOf(TestQueue::class, $queue);
104 | self::assertSame('TestQueue', $queue->getName());
105 | }
106 |
107 | /**
108 | * @test
109 | */
110 | public function getQueueSetsOptionsOnInstance()
111 | {
112 | $this->inject($this->queueManager, 'settings', [
113 | 'queues' => [
114 | 'TestQueue' => [
115 | 'className' => TestQueue::class,
116 | 'options' => [
117 | 'foo' => 'bar'
118 | ]
119 | ]
120 | ]
121 | ]);
122 |
123 | /** @var TestQueue $queue */
124 | $queue = $this->queueManager->getQueue('TestQueue');
125 | self::assertEquals(['foo' => 'bar'], $queue->getOptions());
126 | }
127 |
128 | /**
129 | * @test
130 | */
131 | public function getQueueReusesInstances()
132 | {
133 | $queue = $this->queueManager->getQueue('TestQueue');
134 | self::assertSame($queue, $this->queueManager->getQueue('TestQueue'));
135 | }
136 |
137 | /**
138 | * @test
139 | */
140 | public function getQueueThrowsExceptionWhenSettingsReferToNonExistingPreset()
141 | {
142 | self::expectException(\Flowpack\JobQueue\Common\Exception::class);
143 | $this->inject($this->queueManager, 'settings', [
144 | 'queues' => [
145 | 'TestQueue' => [
146 | 'className' => TestQueue::class,
147 | 'preset' => 'NonExistingPreset'
148 | ]
149 | ]
150 | ]);
151 | $this->queueManager->getQueue('TestQueue');
152 | }
153 |
154 |
155 | /**
156 | * @test
157 | */
158 | public function queueNamesArePrefixedWithDefaultQueueNamePrefix()
159 | {
160 | $this->inject($this->queueManager, 'settings', [
161 | 'queues' => [
162 | 'TestQueue' => [
163 | 'className' => TestQueue::class,
164 | 'queueNamePrefix' => 'specialQueue',
165 | ]
166 | ]
167 | ]);
168 |
169 | /** @var TestQueue $queue */
170 | $queue = $this->queueManager->getQueue('TestQueue');
171 | self::assertSame('specialQueueTestQueue', $queue->getName());
172 | }
173 |
174 | /**
175 | * @test
176 | */
177 | public function queueNamePrefixFromPresetCanBeOverruled()
178 | {
179 | $this->inject($this->queueManager, 'settings', [
180 | 'presets' => [
181 | 'somePreset' => [
182 | 'queueNamePrefix' => 'presetPrefix',
183 | ]
184 | ],
185 | 'queues' => [
186 | 'TestQueue' => [
187 | 'preset' => 'somePreset',
188 | 'queueNamePrefix' => 'overriddenPrefix',
189 | 'className' => TestQueue::class,
190 | ]
191 | ]
192 | ]);
193 |
194 | /** @var TestQueue $queue */
195 | $queue = $this->queueManager->getQueue('TestQueue');
196 | self::assertSame('overriddenPrefixTestQueue', $queue->getName());
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Tests/Unit/Utility/VariableDumperTest.php:
--------------------------------------------------------------------------------
1 |