├── Module.php
├── src
├── Exception
│ ├── LogicException.php
│ ├── RuntimeException.php
│ ├── JobNotFoundException.php
│ └── ExceptionInterface.php
├── Module.php
├── Persistence
│ └── ObjectManagerAwareInterface.php
├── ConfigProvider.php
├── Job
│ └── Exception
│ │ ├── BuryableException.php
│ │ └── ReleasableException.php
├── Queue
│ ├── DoctrineQueueInterface.php
│ └── DoctrineQueue.php
├── Factory
│ └── DoctrineQueueFactory.php
├── Strategy
│ ├── ClearObjectManagerStrategy.php
│ └── IdleNapStrategy.php
├── Worker
│ └── DoctrineWorker.php
├── Command
│ └── RecoverJobsCommand.php
└── Options
│ └── DoctrineOptions.php
├── phpcs.xml
├── UPGRADE.md
├── data
├── queue_default.sql
└── DefaultQueue.php
├── phpunit.xml
├── .github
└── workflows
│ ├── phpcs.yml
│ └── main.yml
├── config
└── module.config.php
├── LICENSE
├── CHANGELOG.md
├── composer.json
└── README.md
/Module.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | config
9 | src
10 | tests
11 |
12 | tests/*/vendor/*$
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Persistence/ObjectManagerAwareInterface.php:
--------------------------------------------------------------------------------
1 | = 3.0
6 |
7 | Upgrading to `slm\queue` 3+ will require some changes, see [upgrade notes](https://github.com/JouwWeb/SlmQueue/blob/master/UPGRADE.md).
8 |
9 | ## BC BREAK: changed recover CLI command
10 |
11 | The recover CLI can now be invoked with the following command:
12 |
13 | ```sh
14 | vendor/bin/laminas slm-queue:doctrine:recover [--executionTime=]
15 | ```
16 |
--------------------------------------------------------------------------------
/src/ConfigProvider.php:
--------------------------------------------------------------------------------
1 | getConfig();
14 |
15 | return [
16 | 'dependencies' => $config['service_manager'],
17 | 'slm_queue' => $config['slm_queue'],
18 | 'laminas-cli' => $config['laminas-cli'],
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/data/queue_default.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `queue_default` (
2 | `id` int(11) NOT NULL AUTO_INCREMENT,
3 | `queue` varchar(64) NOT NULL,
4 | `data` mediumtext NOT NULL,
5 | `status` smallint(1) NOT NULL,
6 | `created` datetime(6) NOT NULL,
7 | `scheduled` datetime(6) NOT NULL,
8 | `executed` datetime(6) DEFAULT NULL,
9 | `finished` datetime(6) DEFAULT NULL,
10 | `priority` int DEFAULT 1024 NOT NULL,
11 | `message` text,
12 | `trace` text,
13 | PRIMARY KEY (`id`),
14 | KEY `pop` (`status`,`queue`,`scheduled`,`priority`),
15 | KEY `prune` (`status`,`queue`,`finished`)
16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
17 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
21 | ./tests/src
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Job/Exception/BuryableException.php:
--------------------------------------------------------------------------------
1 | options = $options;
28 | }
29 |
30 | /**
31 | * Get the options
32 | */
33 | public function getOptions(): array
34 | {
35 | return $this->options;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/phpcs.yml:
--------------------------------------------------------------------------------
1 | name: "PHPCS"
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - "**.php"
7 | - "phpcs.xml"
8 | - ".github/workflows/phpcs.yml"
9 |
10 | jobs:
11 | phpcs:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0 # important!
17 |
18 | # we may use whatever way to install phpcs, just specify the path on the next step
19 | # however, curl seems to be the fastest
20 | - name: Install PHP_CodeSniffer
21 | run: |
22 | curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
23 | php phpcs.phar --version
24 |
25 | - uses: tinovyatkin/action-php-codesniffer@v1
26 | with:
27 | files: "**.php" # you may customize glob as needed
28 | phpcs_path: php phpcs.phar
29 | standard: phpcs.xml
30 |
--------------------------------------------------------------------------------
/src/Queue/DoctrineQueueInterface.php:
--------------------------------------------------------------------------------
1 | options = $options;
29 | }
30 |
31 | /**
32 | * Get the options
33 | */
34 | public function getOptions(): array
35 | {
36 | return $this->options;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Factory/DoctrineQueueFactory.php:
--------------------------------------------------------------------------------
1 | get('config');
22 | $queuesOptions = $config['slm_queue']['queues'];
23 | $options = isset($queuesOptions[$requestedName]) ? $queuesOptions[$requestedName] : [];
24 | $queueOptions = new DoctrineOptions($options);
25 |
26 | /** @var $connection \Doctrine\DBAL\Connection */
27 | $connection = $container->get($queueOptions->getConnection());
28 | $jobPluginManager = $container->get(JobPluginManager::class);
29 |
30 | return new DoctrineQueue(
31 | $connection,
32 | $queueOptions,
33 | $requestedName,
34 | $jobPluginManager
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Strategy/ClearObjectManagerStrategy.php:
--------------------------------------------------------------------------------
1 | listeners[] = $events->attach(
20 | WorkerEventInterface::EVENT_PROCESS_JOB,
21 | [$this, 'onClear'],
22 | 1000
23 | );
24 | }
25 |
26 | public function onClear(ProcessJobEvent $event): void
27 | {
28 | /** @var ObjectManagerAwareInterface $job */
29 | $job = $event->getJob();
30 |
31 |
32 | if (! ($job instanceof ObjectManagerAwareInterface || $job instanceof DMObjectManagerAwareInterface)) {
33 | return;
34 | }
35 |
36 | if (!$manager = $job->getObjectManager()) {
37 | return;
38 | }
39 |
40 | $manager->clear();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/module.config.php:
--------------------------------------------------------------------------------
1 | [
9 | 'factories' => [
10 | RecoverJobsCommand::class => \Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class,
11 | ]
12 | ],
13 | 'laminas-cli' => [
14 | 'commands' => [
15 | 'slm-queue:doctrine:recover' => RecoverJobsCommand::class,
16 | ],
17 | ],
18 | 'slm_queue' => [
19 | /**
20 | * Worker Strategies
21 | */
22 | 'worker_strategies' => [
23 | 'default' => [
24 | IdleNapStrategy::class => ['nap_duration' => 1],
25 | ClearObjectManagerStrategy::class
26 | ],
27 | 'queues' => [
28 | ],
29 | ],
30 | /**
31 | * Strategy manager configuration
32 | */
33 | 'strategy_manager' => [
34 | 'factories' => [
35 | IdleNapStrategy::class => \Laminas\ServiceManager\Factory\InvokableFactory::class,
36 | ClearObjectManagerStrategy::class => \Laminas\ServiceManager\Factory\InvokableFactory::class,
37 | ],
38 | ],
39 | ],
40 | ];
41 |
--------------------------------------------------------------------------------
/src/Strategy/IdleNapStrategy.php:
--------------------------------------------------------------------------------
1 | napDuration = $napDuration;
24 | }
25 |
26 | public function getNapDuration(): int
27 | {
28 | return $this->napDuration;
29 | }
30 |
31 | /**
32 | * {@inheritDoc}
33 | */
34 | public function attach(EventManagerInterface $events, $priority = 1): void
35 | {
36 | $this->listeners[] = $events->attach(
37 | WorkerEventInterface::EVENT_PROCESS_IDLE,
38 | [$this, 'onIdle'],
39 | 1
40 | );
41 | }
42 |
43 | public function onIdle(ProcessIdleEvent $event): void
44 | {
45 | $queue = $event->getQueue();
46 |
47 | if ($queue instanceof DoctrineQueueInterface) {
48 | sleep($this->napDuration);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: "Unit tests"
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "master"
7 | push:
8 | branches:
9 | - "master"
10 |
11 | jobs:
12 | unit-test:
13 | name: "Unit tests"
14 | runs-on: "ubuntu-20.04"
15 | strategy:
16 | matrix:
17 | php-version:
18 | - "7.4"
19 | - "8.0"
20 | - "8.1"
21 | - "8.2"
22 | - "8.3"
23 | dependencies:
24 | - "highest"
25 | - "lowest"
26 | steps:
27 | - name: "Checkout"
28 | uses: "actions/checkout@v2"
29 |
30 | - name: "Install PHP"
31 | uses: "shivammathur/setup-php@v2"
32 | with:
33 | php-version: "${{ matrix.php-version }}"
34 | env:
35 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36 |
37 | - name: "Install dependencies with Composer"
38 | uses: "ramsey/composer-install@v1"
39 | with:
40 | dependency-versions: "${{ matrix.dependencies }}"
41 | env:
42 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43 |
44 | - name: "Run tests"
45 | run: "composer test"
46 | env:
47 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | New BSD License
2 | ===============
3 |
4 | Copyright (c) 2012-2014, Jurian Sluiman
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice,
11 | this list of conditions and the following disclaimer.
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 | * Neither the names of the copyright holders nor the names of its
16 | contributors may be used to endorse or promote products derived from this
17 | software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/src/Worker/DoctrineWorker.php:
--------------------------------------------------------------------------------
1 | execute();
30 | $queue->delete($job);
31 |
32 | return ProcessJobEvent::JOB_STATUS_SUCCESS;
33 | } catch (ReleasableException $exception) {
34 | $queue->release($job, $exception->getOptions());
35 |
36 | return ProcessJobEvent::JOB_STATUS_FAILURE_RECOVERABLE;
37 | } catch (BuryableException $exception) {
38 | $queue->bury($job, $exception->getOptions());
39 |
40 | return ProcessJobEvent::JOB_STATUS_FAILURE;
41 | } catch (Throwable $exception) {
42 | $queue->bury($job, [
43 | 'message' => $exception->getMessage(),
44 | 'trace' => $exception->getTraceAsString()
45 | ]);
46 |
47 | return ProcessJobEvent::JOB_STATUS_FAILURE;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Command/RecoverJobsCommand.php:
--------------------------------------------------------------------------------
1 | queuePluginManager = $queuePluginManager;
30 | }
31 |
32 | protected function configure(): void
33 | {
34 | $this
35 | ->addArgument('queue', InputArgument::REQUIRED)
36 | ->addOption('executionTime', null, InputOption::VALUE_REQUIRED, '', 0);
37 | }
38 |
39 | /**
40 | * Recover long running jobs
41 | */
42 | protected function execute(InputInterface $input, OutputInterface $output): int
43 | {
44 | $queueName = $input->getArgument('queue');
45 | $executionTime = $input->getOption('executionTime');
46 | $queue = $this->queuePluginManager->get($queueName);
47 |
48 | if (! $queue instanceof DoctrineQueueInterface) {
49 | return sprintf("\nQueue % does not support the recovering of job\n\n", $queueName);
50 | }
51 |
52 | try {
53 | $count = $queue->recover($executionTime);
54 | } catch (ExceptionInterface $exception) {
55 | throw new WorkerProcessException("An error occurred", $exception->getCode(), $exception);
56 | }
57 |
58 | $output->writeln(sprintf(
59 | "\nWork for queue %s is done, %s jobs were recovered\n\n",
60 | $queueName,
61 | $count
62 | ));
63 |
64 | return 0;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Options/DoctrineOptions.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
50 | }
51 |
52 | /**
53 | * Get the connection service name
54 | */
55 | public function getConnection(): string
56 | {
57 | return $this->connection;
58 | }
59 |
60 | public function setBuriedLifetime(int $buriedLifetime): void
61 | {
62 | $this->buriedLifetime = (int) $buriedLifetime;
63 | }
64 |
65 | public function getBuriedLifetime(): int
66 | {
67 | return $this->buriedLifetime;
68 | }
69 |
70 | public function setDeletedLifetime(int $deletedLifetime): void
71 | {
72 | $this->deletedLifetime = (int) $deletedLifetime;
73 | }
74 |
75 | public function getDeletedLifetime(): int
76 | {
77 | return $this->deletedLifetime;
78 | }
79 |
80 | public function setTableName(string $tableName): void
81 | {
82 | $this->tableName = $tableName;
83 | }
84 |
85 | public function getTableName(): string
86 | {
87 | return $this->tableName;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.3 And later
2 |
3 | Changelog for these releases can be found on the releases page, see https://github.com/juriansluiman/SlmQueueDoctrine/releases.
4 |
5 | # 2.0.2
6 |
7 | - fixed incorrect run priority for the ClearObjectManagerStrategy
8 | - correctly acquire lock for update to avoid race condition when using multiple workers
9 |
10 | # 2.0.1
11 |
12 | - fixes incorrect entity for migration purposes
13 |
14 | # 2.0.0
15 |
16 | - introduces concept job prioritization (you will need to update you schema)
17 |
18 | # 1.0.0
19 |
20 | - identical to 0.7.1
21 |
22 | # 0.7.1
23 |
24 | - We now use querybuilder for poping job from the queue to better support "Hardcoded LIMIT 1 doesn't work wih Oracle, as Oracle doesn't know LIMIT". This means we also require doctrine/dbal:^2.5 minimal, which could be an issue if you're using older packages.
25 | - Synchronized the indexes between the provided entity and the sql script. You may recreate the tables if you do don't have the correct indexes present.
26 |
27 | # 0.7.0
28 |
29 | - [BC] Synchronize with SlmQueue release 0.7.0 which adds the ability to store binary data in job content
30 |
31 | # 0.6.1
32 |
33 | - Fixes an issue with timezones
34 | - Adds support for microtimes
35 |
36 | # 0.6.0
37 |
38 | - [BC] Synchronize with SlmQueue release 0.6.0 which adds compatibility with PHP7, zend-servicemanager 3 and zend-eventmanager 3
39 |
40 | # 0.5.0
41 |
42 | - [BC] Minimum dependency has been raised to PHP 5.5
43 | - Project has been migrated to PSR-4
44 |
45 | # 0.4.0
46 |
47 | - First stable release in 0.4.x branch
48 |
49 | # 0.4.0-beta3
50 |
51 | * [BC] Due to changes in SlmQueue ([changelog](https://github.com/juriansluiman/SlmQueue/blob/master/CHANGELOG.md)) existing jobs won't be able to be executed correctly.
52 |
53 | # 0.4.0-beta1
54 |
55 | * [BC] SlmQueueDoctrine has been upgraded to SlmQueue 0.4. This feature includes a new, flexible and modular event system.
56 |
57 | # 0.3.0
58 |
59 | * Initial release for 0.3.* branch
60 |
61 | # 0.3.0-beta1
62 |
63 | First release of SlmQueueDoctrine which bring it on par with its parent SlmQueue 0.3.0-beta1
64 |
65 | Comes with unittests, bug fixes and general happiness but please be aware of the following, if you are upgrading from an earlier version...
66 |
67 | - Existing jobs are not compatible (Job content is now serialized). Make sure you empty the queue before upgrading.
68 | - It is suggested that you recreate the table from data/queue_default.sql as the schema changed in minor ways.
69 | - The ability to configure the queue defaults has been removed. Any queue specific configuration now goes into the queues configuration of slm_queue.local.php
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slm/queue-doctrine",
3 | "description": "Laminas Framework module that integrates Doctrine as queuing system",
4 | "license": "BSD-3-Clause",
5 | "type": "library",
6 | "keywords": [
7 | "laminas",
8 | "mezzio",
9 | "queue",
10 | "job",
11 | "doctrine",
12 | "db",
13 | "database"
14 | ],
15 | "homepage": "https://github.com/Webador/SlmQueueDoctrine",
16 | "authors": [
17 | {
18 | "name": "Stefan Kleff",
19 | "email": "s.kleff@goalio.de",
20 | "homepage": "https://www.goalio.de"
21 | },
22 | {
23 | "name": "Bas Kamer",
24 | "email": "baskamer@gmail.com",
25 | "homepage": "https://bushbaby.nl"
26 | }
27 | ],
28 | "require": {
29 | "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
30 | "laminas/laminas-eventmanager": "^3.4",
31 | "laminas/laminas-router": "^3.5",
32 | "slm/queue": "^3.1",
33 | "doctrine/dbal": "^3.1.2",
34 | "doctrine/annotations": "^1.8",
35 | "laminas/laminas-config": "^3.7"
36 | },
37 | "require-dev": {
38 | "ext-sqlite3": "*",
39 | "doctrine/doctrine-orm-module": "^4.1 || ^5.0 || ^6.0",
40 | "laminas/laminas-modulemanager": "^2.11",
41 | "laminas/laminas-view": "^2.13",
42 | "laminas/laminas-log": "^2.15",
43 | "laminas/laminas-i18n": "^2.12",
44 | "laminas/laminas-serializer": "^2.11",
45 | "laminas/laminas-mvc": "^3.3",
46 | "doctrine/orm": "^2.11.1",
47 | "phpunit/phpunit": "^9.3",
48 | "squizlabs/php_codesniffer": "^3.6.2"
49 | },
50 | "conflict": {
51 | "doctrine/cache": ">=2.0"
52 | },
53 | "suggest": {
54 | "doctrine/doctrine-orm-module": "If you use Doctrine in combination with Laminas",
55 | "roave/psr-container-doctrine": "Configures Doctrine services automatically."
56 | },
57 | "extra": {
58 | "branch-alias": {
59 | "dev-master": "3.1.x-dev"
60 | },
61 | "laminas": {
62 | "module": "SlmQueueDoctrine\\Module",
63 | "config-provider": "SlmQueueDoctrine\\ConfigProvider",
64 | "component-whitelist": [
65 | "slm/queue"
66 | ]
67 | }
68 | },
69 | "autoload": {
70 | "psr-4": {
71 | "SlmQueueDoctrine\\": "src/"
72 | }
73 | },
74 | "autoload-dev": {
75 | "psr-4": {
76 | "SlmQueueDoctrineTest\\": "tests/src"
77 | }
78 | },
79 | "scripts": {
80 | "cs-check": "phpcs",
81 | "cs-fix": "phpcbf",
82 | "test": [
83 | "phpunit",
84 | "@composer test --working-dir=tests/laminas-runner",
85 | "@composer test --working-dir=tests/mezzio-runner"
86 | ]
87 | },
88 | "config": {
89 | "platform": {
90 | "php": "7.4.0"
91 | },
92 | "allow-plugins": {
93 | "composer/package-versions-deprecated": true
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/data/DefaultQueue.php:
--------------------------------------------------------------------------------
1 | id;
112 | }
113 |
114 | /**
115 | * Set queue
116 | *
117 | * @param string $queue
118 | * @return DefaultQueue
119 | */
120 | public function setQueue($queue)
121 | {
122 | $this->queue = $queue;
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * Get queue
129 | *
130 | * @return string
131 | */
132 | public function getQueue()
133 | {
134 | return $this->queue;
135 | }
136 |
137 | /**
138 | * Set data
139 | *
140 | * @param string $data
141 | * @return DefaultQueue
142 | */
143 | public function setData($data)
144 | {
145 | $this->data = $data;
146 |
147 | return $this;
148 | }
149 |
150 | /**
151 | * Get data
152 | *
153 | * @return string
154 | */
155 | public function getData()
156 | {
157 | return $this->data;
158 | }
159 |
160 | /**
161 | * Set status
162 | *
163 | * @param int $status
164 | * @return DefaultQueue
165 | */
166 | public function setStatus($status)
167 | {
168 | $this->status = $status;
169 |
170 | return $this;
171 | }
172 |
173 | /**
174 | * Get status
175 | *
176 | * @return int
177 | */
178 | public function getStatus()
179 | {
180 | return $this->status;
181 | }
182 |
183 | /**
184 | * Set created
185 | *
186 | * @param \DateTime $created
187 | * @return DefaultQueue
188 | */
189 | public function setCreated(DateTime $created)
190 | {
191 | $this->created = clone $created;
192 |
193 | return $this;
194 | }
195 |
196 | /**
197 | * Get created
198 | *
199 | * @return \DateTime
200 | */
201 | public function getCreated()
202 | {
203 | if ($this->created) {
204 | return clone $this->created;
205 | }
206 |
207 | return null;
208 | }
209 |
210 | /**
211 | * Set scheduled
212 | *
213 | * @param \DateTime $scheduled
214 | * @return DefaultQueue
215 | */
216 | public function setScheduled(DateTime $scheduled)
217 | {
218 | $this->scheduled = clone $scheduled;
219 |
220 | return $this;
221 | }
222 |
223 | /**
224 | * Get scheduled
225 | *
226 | * @return \DateTime
227 | */
228 | public function getScheduled()
229 | {
230 | if ($this->scheduled) {
231 | return clone $this->scheduled;
232 | }
233 |
234 | return null;
235 | }
236 |
237 | /**
238 | * Set executed
239 | *
240 | * @param \DateTime $executed
241 | * @return DefaultQueue
242 | */
243 | public function setExecuted(DateTime $executed = null)
244 | {
245 | $this->executed = $executed ? clone $executed : null;
246 |
247 | return $this;
248 | }
249 |
250 | /**
251 | * Get executed
252 | *
253 | * @return \DateTime
254 | */
255 | public function getExecuted()
256 | {
257 | if ($this->executed) {
258 | return clone $this->executed;
259 | }
260 |
261 | return null;
262 | }
263 |
264 | /**
265 | * Set finished
266 | *
267 | * @param \DateTime $finished
268 | * @return DefaultQueue
269 | */
270 | public function setFinished(DateTime $finished = null)
271 | {
272 | $this->finished = $finished ? clone $finished : null;
273 |
274 | return $this;
275 | }
276 |
277 | /**
278 | * Get finished
279 | *
280 | * @return \DateTime
281 | */
282 | public function getFinished()
283 | {
284 | if ($this->finished) {
285 | return clone $this->finished;
286 | }
287 |
288 | return null;
289 | }
290 |
291 | /**
292 | * Set message
293 | *
294 | * @param string $message
295 | * @return DefaultQueue
296 | */
297 | public function setMessage($message)
298 | {
299 | $this->message = $message;
300 |
301 | return $this;
302 | }
303 |
304 | /**
305 | * Get message
306 | *
307 | * @return string
308 | */
309 | public function getMessage()
310 | {
311 | return $this->message;
312 | }
313 |
314 | /**
315 | * Set trace
316 | *
317 | * @param string $trace
318 | * @return DefaultQueue
319 | */
320 | public function setTrace($trace)
321 | {
322 | $this->trace = $trace;
323 |
324 | return $this;
325 | }
326 |
327 | /**
328 | * Get trace
329 | *
330 | * @return string
331 | */
332 | public function getTrace()
333 | {
334 | return $this->trace;
335 | }
336 |
337 | /**
338 | * @return int
339 | */
340 | public function getPriority()
341 | {
342 | return $this->priority;
343 | }
344 |
345 | /***
346 | * @param int $priority
347 | * @return $this
348 | */
349 | public function setPriority($priority)
350 | {
351 | $this->priority = $priority;
352 |
353 | return $this;
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SlmQueueDoctrine
2 | ================
3 |
4 | Important notice
5 | ----------
6 |
7 | **We decided to move onto Symfony Messenger and we are therefore not maintaining this repository anymore. Feel free to fork it and
8 | make it your own.**
9 |
10 | [](https://packagist.org/packages/slm/queue-doctrine)
11 | [](https://packagist.org/packages/slm/queue-doctrine)
12 |
13 | Created by Stefan Kleff
14 |
15 | Requirements
16 | ------------
17 | * [SlmQueue](https://github.com/JouwWeb/SlmQueue)
18 | * [Doctrine 2 ORM Module](https://github.com/doctrine/DoctrineORMModule) or [roave/psr-container-doctrine](https://github.com/roave/psr-container-doctrine)
19 |
20 | Note: it's necessary require the doctrine package in composer.json file.
21 |
22 | Installation
23 | ------------
24 |
25 | Run `composer require slm/queue-doctrine`.
26 |
27 | If you have the [laminas/laminas-component-installer](https://github.com/laminas/laminas-component-installer) package installed, it will ask you to enable the module (and `SlmQueue`), both in Laminas and Mezzio. Otherwise, add the module to the list:
28 | * in Laminas MVC, enable the module by adding `SlmQueueDoctrine` in your application.config.php file.
29 | * in Mezzio, enable the module by adding `SlmQueueDoctrine\ConfigProvider::class,` in your config.php file.
30 |
31 | Note: Don't forget install [SlmQueue](https://github.com/JouwWeb/SlmQueue) in you config file, which is required.
32 |
33 | Documentation
34 | -------------
35 |
36 | Before reading SlmQueueDoctrine documentation, please read [SlmQueue documentation](https://github.com/JouwWeb/SlmQueue).
37 |
38 | ### Configuring the connection
39 |
40 | You need to register a doctrine connection which SlmQueueDoctrine will use to access the database into the service manager. Here are some [examples](https://github.com/Roave/psr-container-doctrine/tree/master/example).
41 |
42 | Connection parameters can be defined in the application configuration:
43 |
44 | ```php
45 | [
48 | 'connection' => [
49 | // default connection name
50 | 'orm_default' => [
51 | 'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
52 | 'params' => [
53 | 'host' => 'localhost',
54 | 'port' => '3306',
55 | 'user' => 'username',
56 | 'password' => 'password',
57 | 'dbname' => 'database',
58 | ]
59 | ]
60 | ]
61 | ],
62 | ];
63 | ```
64 |
65 | ### Creating the table from SQL file
66 |
67 | You must create the required table that will contain the queue's you may use the schema located in 'data/queue_default.sql'. If you change the table name look at [Configuring queues](./#configuring-queues)
68 |
69 | ```
70 | >mysql database < data/queue_default.sql
71 | ```
72 | ### Creating the table from Doctrine Entity
73 | There is an alternative way to create 'queue_default' table in your database by copying Doctrine Entity 'date/DefaultQueue.php' to your entity folder ('Application\Entity' in our example) and executing Doctrine's 'orm:schema-tool:update' command which should create the table for you. Notice that DefaultQueue entity is only used for table creation and is not used by this module internally.
74 |
75 |
76 | ### Adding queues
77 |
78 | ```php
79 | return [
80 | 'slm_queue' => [
81 | 'queue_manager' => [
82 | 'factories' => [
83 | 'foo' => 'SlmQueueDoctrine\Factory\DoctrineQueueFactory'
84 | ]
85 | ]
86 | ]
87 | ];
88 | ```
89 | ### Adding jobs
90 |
91 | ```php
92 | return [
93 | 'slm_queue' => [
94 | 'job_manager' => [
95 | 'factories' => [
96 | 'My\Job' => 'My\JobFactory'
97 | ]
98 | ]
99 | ]
100 | ];
101 |
102 | ```
103 | ### Configuring queues
104 |
105 | The following options can be set per queue ;
106 |
107 | - connection (defaults to 'doctrine.connection.orm_default') : Name of the registered doctrine connection service
108 | - table_name (defaults to 'queue_default') : Table name which should be used to store jobs
109 | - deleted_lifetime (defaults to 0) : How long to keep deleted (successful) jobs (in minutes)
110 | - buried_lifetime (defaults to 0) : How long to keep buried (failed) jobs (in minutes)
111 |
112 |
113 | ```php
114 | return [
115 | 'slm_queue' => [
116 | 'queues' => [
117 | 'foo' => [
118 | // ...
119 | ]
120 | ]
121 | ]
122 | ];
123 | ```
124 |
125 | Provided Worker Strategies
126 | --------------------------
127 |
128 | In addition to the provided strategies by [SlmQueue](https://github.com/JouwWeb/SlmQueue/blob/master/docs/6.Events.md) SlmQueueDoctrine comes with these strategies;
129 |
130 | #### ClearObjectManagerStrategy
131 |
132 | This strategy will clear the ObjectManager before execution of individual jobs. The job must implement the [DoctrineModule\Persistence\ObjectManagerAwareInterface](https://github.com/doctrine/DoctrineModule/blob/master/src/DoctrineModule/Persistence/ObjectManagerAwareInterface.php) or [SlmQueueDoctrine\Persistence\ObjectManagerAwareInterface](https://github.com/JouwWeb/SlmQueueDoctrine/blob/master/src/Persistence/ObjectManagerAwareInterface.php).
133 |
134 | listens to:
135 |
136 | - `process.job` event at priority 1000
137 |
138 | options:
139 |
140 | - none
141 |
142 | This strategy is enabled by default.
143 |
144 | #### IdleNapStrategy
145 |
146 | When no jobs are available in the queue this strategy will make the worker wait for a specific amount time before quering the database again.
147 |
148 | listens to:
149 |
150 | - `process.idle` event at priority 1
151 |
152 | options:
153 |
154 | - `nap_duration` defaults to 1 (second)
155 |
156 | This strategy is enabled by default.
157 |
158 | ### Operations on queues
159 |
160 | #### push
161 |
162 | Valid options are:
163 |
164 | * scheduled: the time when the job will be scheduled to run next
165 | * numeric string or integer - interpreted as a timestamp
166 | * string parserable by the DateTime object
167 | * DateTime instance
168 | * delay: the delay before a job become available to be popped (defaults to 0 - no delay -)
169 | * numeric string or integer - interpreted as seconds
170 | * string parserable (ISO 8601 duration) by DateTimeInterval::__construct
171 | * string parserable (relative parts) by DateTimeInterval::createFromDateString
172 | * DateTimeInterval instance
173 | * priority: the lower the priority is, the sooner the job get popped from the queue (default to 1024)
174 |
175 | Examples:
176 | ```php
177 | // scheduled for execution asap
178 | $queue->push($job);
179 |
180 | // will get executed before jobs that have higher priority
181 | $queue->push($job, [
182 | 'priority' => 200,
183 | ]);
184 |
185 | // scheduled for execution 2015-01-01 00:00:00 (system timezone applies)
186 | $queue->push($job, [
187 | 'scheduled' => 1420070400,
188 | ]);
189 |
190 | // scheduled for execution 2015-01-01 00:00:00 (system timezone applies)
191 | $queue->push($job, [
192 | 'scheduled' => '2015-01-01 00:00:00'
193 | ]);
194 |
195 | // scheduled for execution at 2015-01-01 01:00:00
196 | $queue->push($job, [
197 | 'scheduled' => '2015-01-01 00:00:00',
198 | 'delay' => 3600
199 | ]);
200 |
201 | // scheduled for execution at now + 300 seconds
202 | $queue->push($job, [
203 | 'delay' => 'PT300S'
204 | ]);
205 |
206 | // scheduled for execution at now + 2 weeks (1209600 seconds)
207 | $queue->push($job, [
208 | 'delay' => '2 weeks'
209 | ]);
210 |
211 | // scheduled for execution at now + 300 seconds
212 | $queue->push($job, [
213 | 'delay' => new DateInterval("PT300S"))
214 | ]);
215 | ```
216 |
217 |
218 | ### Worker actions
219 |
220 | Interact with workers from the command line from within the public folder of your Laminas Framework 2 application
221 |
222 | #### Starting a worker
223 | Start a worker that will keep monitoring a specific queue for jobs scheduled to be processed. This worker will continue until it has reached certain criteria (exceeds a memory limit or has processed a specified number of jobs).
224 |
225 | `vendor/bin/laminas slm-queue:start `
226 |
227 | A worker will exit when you press cntr-C *after* it has finished the current job it is working on. (PHP doesn't support signal handling on Windows)
228 |
229 | #### Recovering jobs
230 |
231 | To recover jobs which are in the 'running' state for prolonged period of time (specified in minutes) use the following command.
232 |
233 | `vendor/bin/laminas slm-queue:doctrine:recover [--executionTime=]`
234 |
235 | *Note : Workers that are processing a job that is being recovered are NOT stopped.*
236 |
237 |
--------------------------------------------------------------------------------
/src/Queue/DoctrineQueue.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
57 | $this->options = clone $options;
58 |
59 | parent::__construct($name, $jobPluginManager);
60 | }
61 |
62 | public function getOptions(): DoctrineOptions
63 | {
64 | return $this->options;
65 | }
66 |
67 | /**
68 | * Valid options are:
69 | * - priority: the lower the priority is, the sooner the job get popped from the queue (default to 1024)
70 | *
71 | * Note : see DoctrineQueue::parseOptionsToDateTime for schedule and delay options
72 | */
73 | public function push(JobInterface $job, array $options = []): void
74 | {
75 | $time = microtime(true);
76 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
77 | $this->now = new DateTime(
78 | date('Y-m-d H:i:s.' . $micro, (int)$time),
79 | new DateTimeZone(date_default_timezone_get())
80 | );
81 | $scheduled = $this->parseOptionsToDateTime($options);
82 |
83 | $this->connection->insert($this->options->getTableName(), [
84 | 'queue' => $this->getName(),
85 | 'status' => self::STATUS_PENDING,
86 | 'created' => $this->now->format('Y-m-d H:i:s.u'),
87 | 'data' => $this->serializeJob($job),
88 | 'scheduled' => $scheduled->format('Y-m-d H:i:s.u'),
89 | 'priority' => isset($options['priority']) ? $options['priority'] : self::DEFAULT_PRIORITY,
90 | ], [
91 | Types::STRING,
92 | Types::SMALLINT,
93 | Types::STRING,
94 | Types::TEXT,
95 | Types::STRING,
96 | Types::INTEGER,
97 | ]);
98 |
99 | if (self::DATABASE_PLATFORM_POSTGRES == $this->connection->getDatabasePlatform()->getName()) {
100 | $id = $this->connection->lastInsertId($this->options->getTableName() . '_id_seq');
101 | } else {
102 | $id = $this->connection->lastInsertId();
103 | }
104 |
105 | $job->setId($id);
106 | }
107 |
108 | /**
109 | * {@inheritDoc}
110 | */
111 | public function pop(array $options = []): ?JobInterface
112 | {
113 | // First run garbage collection
114 | $this->purge();
115 |
116 | $conn = $this->connection;
117 | $conn->beginTransaction();
118 |
119 | $time = microtime(true);
120 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
121 | $this->now = new DateTime(
122 | date('Y-m-d H:i:s.' . $micro, (int)$time),
123 | new DateTimeZone(date_default_timezone_get())
124 | );
125 |
126 | try {
127 | $platform = $conn->getDatabasePlatform();
128 |
129 | $queryBuilder = $conn->createQueryBuilder();
130 |
131 | $queryBuilder
132 | ->select('*')
133 | ->from($platform->appendLockHint($this->options->getTableName(), LockMode::PESSIMISTIC_WRITE))
134 | ->where('status = ?')
135 | ->andWhere('queue = ?')
136 | ->andWhere('scheduled <= ?')
137 | ->addOrderBy('priority', 'ASC')
138 | ->addOrderBy('scheduled', 'ASC')
139 | ->setParameter(0, static::STATUS_PENDING)
140 | ->setParameter(1, $this->getName())
141 | ->setParameter(2, $this->now->format('Y-m-d H:i:s.u'))
142 | ->setMaxResults(1);
143 |
144 | // Modify the query so it supports row locking (if applicable for database provider)
145 | // @see https://github.com/doctrine/doctrine2/blob/5f3afa4c4ffb8cb49870d794cc7daf6a49406966/lib/Doctrine/ORM/Query/SqlWalker.php#L556-L558
146 | $sql = $queryBuilder->getSQL() . ' ' . $platform->getWriteLockSQL();
147 |
148 | $query = $this->connection->executeQuery(
149 | $sql,
150 | $queryBuilder->getParameters(),
151 | $queryBuilder->getParameterTypes()
152 | );
153 |
154 | if ($row = $query->fetch()) {
155 | $update = 'UPDATE ' . $this->options->getTableName() . ' ' .
156 | 'SET status = ?, executed = ? ' .
157 | 'WHERE id = ? AND status = ?';
158 |
159 | $rows = $conn->executeUpdate(
160 | $update,
161 | [
162 | static::STATUS_RUNNING,
163 | $this->now->format('Y-m-d H:i:s.u'),
164 | $row['id'],
165 | static::STATUS_PENDING
166 | ],
167 | [Types::SMALLINT, Types::STRING, Types::INTEGER, Types::SMALLINT]
168 | );
169 |
170 | if ($rows !== 1) {
171 | throw new LogicException("Race-condition detected while updating item in queue.");
172 | }
173 | }
174 |
175 | $conn->commit();
176 | } catch (DBALException $e) {
177 | $conn->rollback();
178 | $conn->close();
179 | throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
180 | }
181 |
182 | if ($row === false) {
183 | return null;
184 | }
185 |
186 | // Add job ID to meta data
187 | return $this->unserializeJob($row['data'], ['__id__' => $row['id']]);
188 | }
189 |
190 | /**
191 | * {@inheritDoc}
192 | *
193 | * Note: When $deletedLifetime === 0 the job will be deleted immediately
194 | */
195 | public function delete(JobInterface $job, array $options = []): void
196 | {
197 | if ($this->options->getDeletedLifetime() === static::LIFETIME_DISABLED) {
198 | $this->connection->delete($this->options->getTableName(), ['id' => $job->getId()]);
199 | } else {
200 | $update = 'UPDATE ' . $this->options->getTableName() . ' ' .
201 | 'SET status = ?, finished = ? ' .
202 | 'WHERE id = ? AND status = ?';
203 |
204 | $time = microtime(true);
205 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
206 | $this->now = new DateTime(
207 | date('Y-m-d H:i:s.' . $micro, (int)$time),
208 | new DateTimeZone(date_default_timezone_get())
209 | );
210 |
211 | $rows = $this->connection->executeUpdate(
212 | $update,
213 | [
214 | static::STATUS_DELETED,
215 | $this->now->format('Y-m-d H:i:s.u'),
216 | $job->getId(),
217 | static::STATUS_RUNNING
218 | ],
219 | [Types::SMALLINT, Types::STRING, Types::INTEGER, Types::SMALLINT]
220 | );
221 |
222 | if ($rows !== 1) {
223 | throw new LogicException("Race-condition detected while updating item in queue.");
224 | }
225 | }
226 | }
227 |
228 | /**
229 | * {@inheritDoc}
230 | *
231 | * Note: When $buriedLifetime === 0 the job will be deleted immediately
232 | */
233 | public function bury(JobInterface $job, array $options = []): void
234 | {
235 | if ($this->options->getBuriedLifetime() === static::LIFETIME_DISABLED) {
236 | $this->connection->delete($this->options->getTableName(), ['id' => $job->getId()]);
237 | } else {
238 | $message = isset($options['message']) ? $options['message'] : null;
239 | $trace = isset($options['trace']) ? $options['trace'] : null;
240 |
241 | $update = 'UPDATE ' . $this->options->getTableName() . ' ' .
242 | 'SET status = ?, finished = ?, message = ?, trace = ? ' .
243 | 'WHERE id = ? AND status = ?';
244 |
245 | $time = microtime(true);
246 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
247 | $this->now = new DateTime(
248 | date('Y-m-d H:i:s.' . $micro, (int)$time),
249 | new DateTimeZone(date_default_timezone_get())
250 | );
251 |
252 | $rows = $this->connection->executeUpdate(
253 | $update,
254 | [
255 | static::STATUS_BURIED,
256 | $this->now->format('Y-m-d H:i:s.u'),
257 | $message,
258 | $trace,
259 | $job->getId(),
260 | static::STATUS_RUNNING
261 | ],
262 | [Types::SMALLINT, Types::STRING, Types::STRING, Types::TEXT, Types::INTEGER, Types::SMALLINT]
263 | );
264 |
265 | if ($rows !== 1) {
266 | throw new LogicException("Race-condition detected while updating item in queue.");
267 | }
268 | }
269 | }
270 |
271 | /**
272 | * {@inheritDoc}
273 | */
274 | public function recover(int $executionTime): int
275 | {
276 | $executedLifetime = $this->parseOptionsToDateTime(['delay' => - ($executionTime * 60)]);
277 |
278 | $update = 'UPDATE ' . $this->options->getTableName() . ' ' .
279 | 'SET status = ? ' .
280 | 'WHERE executed < ? AND status = ? AND queue = ? AND finished IS NULL';
281 |
282 | return $this->connection->executeUpdate(
283 | $update,
284 | [
285 | static::STATUS_PENDING,
286 | $executedLifetime->format('Y-m-d H:i:s.u'),
287 | static::STATUS_RUNNING,
288 | $this->getName()
289 | ],
290 | [Types::SMALLINT, Types::STRING, Types::SMALLINT, Types::STRING]
291 | );
292 | }
293 |
294 | /**
295 | * Create a concrete instance of a job from the queue
296 | */
297 | public function peek(int $id): JobInterface
298 | {
299 | $sql = 'SELECT * FROM ' . $this->options->getTableName() . ' WHERE id = ?';
300 | $row = $this->connection->fetchAssociative($sql, [$id], [Types::SMALLINT]);
301 |
302 | if (!$row) {
303 | throw new JobNotFoundException(sprintf("Job with id '%s' does not exists.", $id));
304 | }
305 |
306 | // Add job ID to meta data
307 | return $this->unserializeJob($row['data'], ['__id__' => $row['id']]);
308 | }
309 |
310 | /**
311 | * Reschedules a specific running job
312 | *
313 | * Note : see DoctrineQueue::parseOptionsToDateTime for schedule and delay options
314 | */
315 | public function release(JobInterface $job, array $options = []): void
316 | {
317 | $scheduled = $this->parseOptionsToDateTime($options);
318 |
319 | $update = 'UPDATE ' . $this->options->getTableName() . ' ' .
320 | 'SET status = ?, finished = ? , scheduled = ?, data = ? ' .
321 | 'WHERE id = ? AND status = ?';
322 |
323 | $time = microtime(true);
324 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
325 |
326 | $rows = $this->connection->executeUpdate(
327 | $update,
328 | [
329 | static::STATUS_PENDING,
330 | $this->now->format('Y-m-d H:i:s.u'),
331 | $scheduled->format('Y-m-d H:i:s.u'),
332 | $this->serializeJob($job),
333 | $job->getId(),
334 | static::STATUS_RUNNING,
335 | ],
336 | [Types::SMALLINT, Types::STRING, Types::STRING, Types::STRING, Types::INTEGER, Types::SMALLINT]
337 | );
338 |
339 | if ($rows !== 1) {
340 | throw new LogicException("Race-condition detected while updating item in queue.");
341 | }
342 | }
343 |
344 | /**
345 | * Parses options to a datetime object
346 | *
347 | * valid options keys:
348 | *
349 | * scheduled: the time when the job will be scheduled to run next
350 | * - numeric string or integer - interpreted as a timestamp
351 | * - string parserable by the DateTime object
352 | * - DateTime instance
353 | * delay: the delay before a job become available to be popped (defaults to 0 - no delay -)
354 | * - numeric string or integer - interpreted as seconds
355 | * - string parserable (ISO 8601 duration) by DateTimeInterval::__construct
356 | * - string parserable (relative parts) by DateTimeInterval::createFromDateString
357 | * - DateTimeInterval instance
358 | *
359 | * @see http://en.wikipedia.org/wiki/Iso8601#Durations
360 | * @see http://www.php.net/manual/en/datetime.formats.relative.php
361 | */
362 | protected function parseOptionsToDateTime(array $options): DateTime
363 | {
364 | $time = microtime(true);
365 | $micro = sprintf("%06d", ($time - floor($time)) * 1000000);
366 | $this->now = new DateTime(
367 | date('Y-m-d H:i:s.' . $micro, (int)$time),
368 | new DateTimeZone(date_default_timezone_get())
369 | );
370 | $scheduled = clone ($this->now);
371 |
372 | if (isset($options['scheduled'])) {
373 | switch (true) {
374 | case is_numeric($options['scheduled']):
375 | $scheduled = new DateTime(
376 | sprintf("@%d", (int) $options['scheduled']),
377 | new DateTimeZone(date_default_timezone_get())
378 | );
379 | break;
380 | case is_string($options['scheduled']):
381 | $scheduled = new DateTime($options['scheduled'], new DateTimeZone(date_default_timezone_get()));
382 | break;
383 | case $options['scheduled'] instanceof DateTime:
384 | $scheduled = $options['scheduled'];
385 | break;
386 | }
387 | }
388 |
389 | if (isset($options['delay'])) {
390 | switch (true) {
391 | case is_numeric($options['delay']):
392 | $delay = new DateInterval(sprintf("PT%dS", abs((int) $options['delay'])));
393 | $delay->invert = ($options['delay'] < 0) ? 1 : 0;
394 | break;
395 | case is_string($options['delay']):
396 | try {
397 | // first try ISO 8601 duration specification
398 | $delay = new DateInterval($options['delay']);
399 | } catch (\Exception $e) {
400 | // then try normal date parser
401 | $delay = DateInterval::createFromDateString($options['delay']);
402 | }
403 | break;
404 | case $options['delay'] instanceof DateInterval:
405 | $delay = $options['delay'];
406 | break;
407 | default:
408 | $delay = null;
409 | }
410 |
411 | if ($delay instanceof DateInterval) {
412 | $scheduled->add($delay);
413 | }
414 | }
415 |
416 | return $scheduled;
417 | }
418 |
419 | /**
420 | * Cleans old jobs in the table according to the configured lifetime of successful and failed jobs.
421 | */
422 | protected function purge(): void
423 | {
424 | if ($this->options->getBuriedLifetime() > static::LIFETIME_UNLIMITED) {
425 | $options = ['delay' => - ($this->options->getBuriedLifetime() * 60)];
426 | $buriedLifetime = $this->parseOptionsToDateTime($options);
427 |
428 | $delete = 'DELETE FROM ' . $this->options->getTableName() . ' ' .
429 | 'WHERE finished < ? AND status = ? AND queue = ? AND finished IS NOT NULL';
430 |
431 | $this->connection->executeUpdate(
432 | $delete,
433 | [$buriedLifetime->format('Y-m-d H:i:s.u'), static::STATUS_BURIED, $this->getName()],
434 | [Types::STRING, Types::INTEGER, Types::STRING]
435 | );
436 | }
437 |
438 | if ($this->options->getDeletedLifetime() > static::LIFETIME_UNLIMITED) {
439 | $options = ['delay' => - ($this->options->getDeletedLifetime() * 60)];
440 | $deletedLifetime = $this->parseOptionsToDateTime($options);
441 |
442 | $delete = 'DELETE FROM ' . $this->options->getTableName() . ' ' .
443 | 'WHERE finished < ? AND status = ? AND queue = ? AND finished IS NOT NULL';
444 |
445 | $this->connection->executeUpdate(
446 | $delete,
447 | [$deletedLifetime->format('Y-m-d H:i:s.u'), static::STATUS_DELETED, $this->getName()],
448 | [Types::STRING, Types::INTEGER, Types::STRING]
449 | );
450 | }
451 | }
452 | }
453 |
--------------------------------------------------------------------------------