From e628ea8eafe2fadf907c11e55338d350c344184b Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Wed, 17 Jun 2026 09:47:37 +0200 Subject: [PATCH 1/9] Refactor task integration --- src/Director/RunDirector.php | 7 +- src/Director/TaskDirector.php | 21 ++- src/Entity/TaskLog.php | 6 +- src/Identification/TaskIdStamp.php | 21 +++ .../TaskIdentificationMiddleware.php | 122 ++++++++++++++++++ src/Identification/TaskIdentifier.php | 63 +++++++++ src/Listener/MessengerEventListener.php | 39 +----- src/Manager/TaskManager.php | 20 ++- src/Model/TaskLogModel.php | 10 +- src/Normalizer/TaskDetailsNormalizer.php | 5 + .../DispatchAfterRunTaskHandler.php | 12 +- src/Task/Task.php | 2 +- tests/Entity/TaskLogTest.php | 18 ++- tests/Entity/TaskRunTest.php | 2 +- tests/Log/LogCleanerTest.php | 2 +- .../Normalizer/TaskDetailsNormalizerTest.php | 21 +-- tests/Task/TaskTest.php | 37 ------ 17 files changed, 288 insertions(+), 120 deletions(-) create mode 100644 src/Identification/TaskIdStamp.php create mode 100644 src/Identification/TaskIdentificationMiddleware.php create mode 100644 src/Identification/TaskIdentifier.php delete mode 100644 tests/Task/TaskTest.php diff --git a/src/Director/RunDirector.php b/src/Director/RunDirector.php index 0692c29..83c6f57 100644 --- a/src/Director/RunDirector.php +++ b/src/Director/RunDirector.php @@ -17,7 +17,7 @@ final class RunDirector */ public function __construct ( private readonly TaskLogModel $logModel, - private readonly TaskRun $run, + private readonly ?TaskRun $run, ) { $this->output = new ChainOutput(); @@ -40,6 +40,11 @@ public function getIo () : TorrStyle */ public function finish (bool $success) : void { + if (null === $this->run) + { + return; + } + $this->run->finish($success, $this->output->getBufferedOutput()); $this->logModel->flush(); } diff --git a/src/Director/TaskDirector.php b/src/Director/TaskDirector.php index 2d21622..80210bc 100644 --- a/src/Director/TaskDirector.php +++ b/src/Director/TaskDirector.php @@ -2,6 +2,8 @@ namespace Torr\TaskManager\Director; +use Psr\Log\LoggerInterface; +use Torr\TaskManager\Identification\TaskIdentifier; use Torr\TaskManager\Model\TaskLogModel; use Torr\TaskManager\Task\Task; @@ -11,6 +13,8 @@ */ public function __construct ( private TaskLogModel $logModel, + private LoggerInterface $logger, + private TaskIdentifier $taskIdentifier, ) {} /** @@ -18,12 +22,27 @@ public function __construct ( */ public function startRun (Task $task) : RunDirector { - $log = $this->logModel->getLogForTask($task); + $uuid = $this->taskIdentifier->getUuid($task); + + if (null === $uuid) + { + $this->logger->critical("Could not identify task of type {type}", [ + "type" => get_debug_type($task), + "task" => $task, + ]); + + return new RunDirector($this->logModel, null); + } + + $log = $this->logModel->getLogForUuid($task, $uuid); // create run $run = $this->logModel->createRunForTask($log); $this->logModel->flush(); + // store latest run + $this->taskIdentifier->setLatestRun($log, $run); + return new RunDirector($this->logModel, $run); } } diff --git a/src/Entity/TaskLog.php b/src/Entity/TaskLog.php index e446d43..ab90918 100644 --- a/src/Entity/TaskLog.php +++ b/src/Entity/TaskLog.php @@ -69,12 +69,12 @@ class TaskLog /** */ public function __construct ( - Task $task, + object $task, + string $uuid, ) { $this->taskClass = $task::class; - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ - $this->taskId = $task->ulid; + $this->taskId = $uuid; $this->runs = new ArrayCollection(); $this->timeQueued = now(); } diff --git a/src/Identification/TaskIdStamp.php b/src/Identification/TaskIdStamp.php new file mode 100644 index 0000000..96ccab7 --- /dev/null +++ b/src/Identification/TaskIdStamp.php @@ -0,0 +1,21 @@ +taskId = new UuidV7()->toRfc4122(); + } +} diff --git a/src/Identification/TaskIdentificationMiddleware.php b/src/Identification/TaskIdentificationMiddleware.php new file mode 100644 index 0000000..5eea23d --- /dev/null +++ b/src/Identification/TaskIdentificationMiddleware.php @@ -0,0 +1,122 @@ +last(TaskIdStamp::class); + + if (null === $stamp) + { + $stamp = new TaskIdStamp(); + $envelope = $envelope->with($stamp); + } + + $this->taskIdentifier->setUuid($envelope->getMessage(), $stamp->taskId); + + // create task run before + $logEntry = $this->logModel->getLogForUuid($envelope->getMessage(), $stamp->taskId); + $logEntry->setTaskDetails($this->detailsNormalizer->normalizeTaskDetails($envelope)); + $this->logModel->flush(); + + try + { + // push message through the stack + $envelope = $stack->next()->handle($envelope, $stack); + } + catch (ExceptionInterface $e) + { + $run = $this->getTaskRun($logEntry, $envelope->getMessage()); + + if (null !== $run && !$run->isFinished) + { + $run->abort(false, $e->getMessage()); + $this->logModel->flush(); + } + + throw $e; + } + + $handledStamp = $envelope->last(HandledStamp::class); + + if (null !== $handledStamp) + { + $run = $this->getTaskRun($logEntry, $envelope->getMessage()); + + if (null !== $run && !$run->isFinished) + { + $run->abort( + success: true, + output: $this->extractResultContent($handledStamp), + ); + } + } + + // update log with most up-to-date details + $logEntry->setTaskDetails($this->detailsNormalizer->normalizeTaskDetails($envelope)); + $this->logModel->flush(); + + return $envelope; + } + + /** + * + */ + private function getTaskRun (TaskLog $log, object $message) : ?TaskRun + { + // if the message is not a task, no task director will be called. + // so we can create a run here + if (!$message instanceof Task) + { + return $this->logModel->createRunForTask($log); + } + + return $this->taskIdentifier->getLatestRun($message); + } + + /** + */ + private function extractResultContent (HandledStamp $handledStamp) : ?string + { + $result = $handledStamp->getResult(); + + if (\is_string($result)) + { + return $result; + } + + if ($result instanceof RunCommandContext) + { + return $result->output; + } + + return null; + } +} diff --git a/src/Identification/TaskIdentifier.php b/src/Identification/TaskIdentifier.php new file mode 100644 index 0000000..a938218 --- /dev/null +++ b/src/Identification/TaskIdentifier.php @@ -0,0 +1,63 @@ + */ + private \WeakMap $uuidMap; + + /** @var \WeakMap */ + private \WeakMap $latestRuns; + + /** + */ + public function __construct () + { + $this->uuidMap = new \WeakMap(); + $this->latestRuns = new \WeakMap(); + } + + /** + * + */ + public function setUuid (object $message, string $uuid) : void + { + $this->uuidMap[$message] = $uuid; + } + + /** + * + */ + public function getUuid (object $message) : ?string + { + return $this->uuidMap[$message] + ?? null; + } + + public function setLatestRun (object $message, TaskRun $run) : void + { + $this->latestRuns[$message] = $run; + } + + public function getLatestRun (object $message) : ?TaskRun + { + return $this->latestRuns[$message] + ?? null; + } + + /** + * + */ + public function reset () : void + { + $this->uuidMap = new \WeakMap(); + $this->latestRuns = new \WeakMap(); + } +} diff --git a/src/Listener/MessengerEventListener.php b/src/Listener/MessengerEventListener.php index bbacb18..5fb4af6 100644 --- a/src/Listener/MessengerEventListener.php +++ b/src/Listener/MessengerEventListener.php @@ -4,13 +4,11 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\Messenger\Envelope; -use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Torr\TaskManager\Entity\TaskLog; +use Torr\TaskManager\Identification\TaskIdStamp; use Torr\TaskManager\Model\TaskLogModel; -use Torr\TaskManager\Normalizer\TaskDetailsNormalizer; -use Torr\TaskManager\Task\Task; use Torr\TaskManager\Task\TaskManagerInternalTask; /** @@ -20,28 +18,8 @@ { public function __construct ( private TaskLogModel $logModel, - private TaskDetailsNormalizer $detailsNormalizer, ) {} - /** - * - */ - #[AsEventListener] - public function onSendMessageToTransports (SendMessageToTransportsEvent $event) : void - { - $envelope = $event->getEnvelope(); - $taskLog = $this->getLogForEvent($envelope); - - // make sure that the log entry is created and flushed - if (null !== $taskLog) - { - // update envelope with current version - $taskLog->setTaskDetails($this->detailsNormalizer->normalizeTaskDetails($envelope)); - - $this->logModel->flush(); - } - } - /** * Automatically integrate */ @@ -56,10 +34,7 @@ public function onWorkerMessageHandled (WorkerMessageHandledEvent $event) : void return; } - // update envelope with current version - $taskLog->setTaskDetails($this->detailsNormalizer->normalizeTaskDetails($envelope)); - - // abort run as success. It wasn't marked as finished manually, but it succeeded nonetheless. + // abort run as "success". It wasn't marked as finished manually, but it succeeded nonetheless. $run = $taskLog->getLastUnfinishedRun(); $run?->abort(true); @@ -77,10 +52,7 @@ public function onWorkerMessageFailed (WorkerMessageFailedEvent $event) : void return; } - // update envelope with current version - $taskLog->setTaskDetails($this->detailsNormalizer->normalizeTaskDetails($envelope)); - - // abort run as failure. It wasn't marked as finished manually and it failed. + // abort run as "failure". It wasn't marked as finished manually, and it failed. $run = $taskLog->getLastUnfinishedRun(); $run?->abort(false, $event->getThrowable()->getMessage()); @@ -92,11 +64,12 @@ public function onWorkerMessageFailed (WorkerMessageFailedEvent $event) : void */ private function getLogForEvent (Envelope $envelope) : ?TaskLog { + $uuid = $envelope->last(TaskIdStamp::class)?->taskId; $message = $envelope->getMessage(); // filter out task manager internal tasks - return $message instanceof Task && !$message instanceof TaskManagerInternalTask - ? $this->logModel->getLogForTask($message) + return null !== $uuid && !$message instanceof TaskManagerInternalTask + ? $this->logModel->getLogForUuid($message, $uuid) : null; } } diff --git a/src/Manager/TaskManager.php b/src/Manager/TaskManager.php index c53414f..66583f1 100644 --- a/src/Manager/TaskManager.php +++ b/src/Manager/TaskManager.php @@ -6,6 +6,7 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Stamp\DeduplicateStamp; use Symfony\Component\Messenger\Stamp\StampInterface; +use Torr\TaskManager\Identification\TaskIdStamp; use Torr\TaskManager\Task\Task; use Torr\TaskManager\Transport\TransportsHelper; @@ -27,21 +28,26 @@ public function __construct ( * * @api */ - public function enqueue (Task $task, array $stamps = []) : string + public function enqueue (object $task, array $stamps = []) : string { - $uniqueTaskId = $task->getMetaData()->uniqueTaskId; - - if (null !== $uniqueTaskId) + if ($task instanceof Task) { - $stamps[] = new DeduplicateStamp($uniqueTaskId); + $uniqueTaskId = $task->getMetaData()->uniqueTaskId; + + if (null !== $uniqueTaskId) + { + $stamps[] = new DeduplicateStamp($uniqueTaskId); + } } + $id = new TaskIdStamp(); + $stamps[] = $id; + $this->messageBus->dispatch( new Envelope($task, $stamps), ); - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ - return $task->ulid; + return $id->taskId; } /** diff --git a/src/Model/TaskLogModel.php b/src/Model/TaskLogModel.php index 58cea57..17013a3 100644 --- a/src/Model/TaskLogModel.php +++ b/src/Model/TaskLogModel.php @@ -8,7 +8,6 @@ use Torr\TaskManager\Entity\TaskLog; use Torr\TaskManager\Entity\TaskRun; use Torr\TaskManager\Task\DispatchAfterRunTask\DispatchAfterRunTask; -use Torr\TaskManager\Task\Task; final class TaskLogModel { @@ -47,12 +46,11 @@ public function findByTaskId (string $taskId) : ?TaskLog } /** - * Gets or creates the log entry for the given task + * Gets or creates the log entry for the given task uuid */ - public function getLogForTask (Task $task) : TaskLog + public function getLogForUuid (object $task, string $uuid) : TaskLog { - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ - $log = $this->findByTaskId($task->ulid); + $log = $this->findByTaskId($uuid); if (null !== $log) { @@ -60,7 +58,7 @@ public function getLogForTask (Task $task) : TaskLog } // if it isn't created yet, create a new one - $log = new TaskLog($task); + $log = new TaskLog($task, $uuid); $this->entityManager->persist($log); return $log; diff --git a/src/Normalizer/TaskDetailsNormalizer.php b/src/Normalizer/TaskDetailsNormalizer.php index 4d3466f..21f7acc 100644 --- a/src/Normalizer/TaskDetailsNormalizer.php +++ b/src/Normalizer/TaskDetailsNormalizer.php @@ -40,6 +40,11 @@ public function normalizeTaskDetails (Envelope $envelope) : array $details["label"] = $task->getMetaData()->label; $details["task"] = $this->serializer->serialize($task->prepareForTaskLog(), JsonEncoder::FORMAT); } + else + { + $details["label"] = get_debug_type($task); + $details["task"] = $this->serializer->serialize($task, JsonEncoder::FORMAT); + } return $details; } diff --git a/src/Task/DispatchAfterRunTask/DispatchAfterRunTaskHandler.php b/src/Task/DispatchAfterRunTask/DispatchAfterRunTaskHandler.php index 21611cd..fbca4e8 100644 --- a/src/Task/DispatchAfterRunTask/DispatchAfterRunTaskHandler.php +++ b/src/Task/DispatchAfterRunTask/DispatchAfterRunTaskHandler.php @@ -38,11 +38,13 @@ public function onDispatchAfterRunTask (DispatchAfterRunTask $task) : void $task->task::class, )); - $stamps = !empty($task->transportNames) - ? [new TransportNamesStamp($task->transportNames)] - : []; + $stamps = []; - $wrappedTask = $task->task->withNewTaskUlid(); - $this->taskManager->enqueue($wrappedTask, $stamps); + if (!empty($task->transportNames)) + { + $stamps[] = new TransportNamesStamp($task->transportNames); + } + + $this->taskManager->enqueue($task->task, $stamps); } } diff --git a/src/Task/Task.php b/src/Task/Task.php index 1fca471..9425814 100644 --- a/src/Task/Task.php +++ b/src/Task/Task.php @@ -23,7 +23,7 @@ public function __construct () { $uuid = new UuidV7()->toString(); - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ + /** @phpstan-ignore-next-line property.deprecated (We still need to support the deprecated property) */ $this->ulid = $uuid; } diff --git a/tests/Entity/TaskLogTest.php b/tests/Entity/TaskLogTest.php index 0a6da1b..6eeebb1 100644 --- a/tests/Entity/TaskLogTest.php +++ b/tests/Entity/TaskLogTest.php @@ -3,6 +3,7 @@ namespace Tests\Torr\TaskManager\Entity; use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\Test\ClockSensitiveTrait; use Torr\TaskManager\Entity\TaskLog; use Torr\TaskManager\Exception\Log\InvalidLogActionException; use Torr\TaskManager\Task\Task; @@ -13,8 +14,9 @@ */ final class TaskLogTest extends TestCase { - // region Helpers + use ClockSensitiveTrait; + // region Helpers private function createTask () : Task { // @phpstan-ignore-next-line 21torr.custom.task.suffix @@ -29,7 +31,7 @@ public function getMetaData () : TaskMetaData private function createLog () : TaskLog { - return new TaskLog($this->createTask()); + return new TaskLog($this->createTask(), "my-uuid"); } // endregion @@ -144,17 +146,20 @@ public function testGetTotalDurationSumsAllRuns () : void { $log = $this->createLog(); + self::mockTime("2026-06-18 12:00:00"); $run1 = $log->createRun(); + self::mockTime("2026-06-18 12:00:05"); $run1->finish(false, null); $run2 = $log->createRun(); + self::mockTime("2026-06-18 12:00:10"); $run2->finish(true, null); $total = $log->getTotalDuration(); self::assertGreaterThan(0, $total); self::assertEqualsWithDelta( - ($run1->duration ?? 0) + ($run2->duration ?? 0), + 10e9, $total, 0.001, ); @@ -182,16 +187,15 @@ public function testTaskDetailsDefaultsToNull () : void public function testTaskIdMatchesTaskUlid () : void { $task = $this->createTask(); - $log = new TaskLog($task); + $log = new TaskLog($task, "my-uuid"); - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ - self::assertSame($task->ulid, $log->taskId); + self::assertSame("my-uuid", $log->taskId); } public function testTaskClassMatchesTaskClass () : void { $task = $this->createTask(); - $log = new TaskLog($task); + $log = new TaskLog($task, "my-uuid"); self::assertSame($task::class, $log->taskClass); } diff --git a/tests/Entity/TaskRunTest.php b/tests/Entity/TaskRunTest.php index 15c75ab..469a874 100644 --- a/tests/Entity/TaskRunTest.php +++ b/tests/Entity/TaskRunTest.php @@ -29,7 +29,7 @@ public function getMetaData () : TaskMetaData } }; - return new TaskLog($task); + return new TaskLog($task, "my-uuid"); } // endregion diff --git a/tests/Log/LogCleanerTest.php b/tests/Log/LogCleanerTest.php index 6d99792..f684fc9 100644 --- a/tests/Log/LogCleanerTest.php +++ b/tests/Log/LogCleanerTest.php @@ -163,7 +163,7 @@ public function testCutoffEntryOverridesTtlWhenNewer () : void // The TaskLog created below has timeQueued ≈ now (real system clock), // which is newer than TTL purge date, so the cutoff entry should override. $clock = new MockClock(); - $cutoffEntry = new TaskLog($this->createTask()); + $cutoffEntry = new TaskLog($this->createTask(), "my-uuid"); $capturedOldestTimestamp = null; diff --git a/tests/Normalizer/TaskDetailsNormalizerTest.php b/tests/Normalizer/TaskDetailsNormalizerTest.php index 6eb2b46..9fc3d23 100644 --- a/tests/Normalizer/TaskDetailsNormalizerTest.php +++ b/tests/Normalizer/TaskDetailsNormalizerTest.php @@ -103,19 +103,6 @@ public function testNormalizeWithoutStampsHasNullTransportAndHandledBy () : void self::assertArrayHasKey("handledBy", $details); self::assertNull($details["handledBy"]); } - - public function testNormalizeNonTaskMessageSkipsLabelAndTask () : void - { - $message = new \stdClass(); - $serializer = $this->createMock(SerializerInterface::class); - $serializer->expects(self::never())->method("serialize"); - - $details = $this->createNormalizer($serializer)->normalizeTaskDetails(new Envelope($message)); - - self::assertArrayNotHasKey("label", $details); - self::assertArrayNotHasKey("task", $details); - } - // endregion // region deserializeTask @@ -123,7 +110,7 @@ public function testNormalizeNonTaskMessageSkipsLabelAndTask () : void public function testDeserializeReturnsNullWhenNoTaskStored () : void { $task = $this->createTask(); - $log = new TaskLog($task); + $log = new TaskLog($task, "my-uuid"); // taskDetails is empty by default $result = $this->createNormalizer()->deserializeTask($log); @@ -134,7 +121,7 @@ public function testDeserializeReturnsNullWhenNoTaskStored () : void public function testDeserializeReturnsTask () : void { $originalTask = $this->createTask(); - $log = new TaskLog($originalTask); + $log = new TaskLog($originalTask, "my-uuid"); $log->setTaskDetails(["task" => '{"ulid":"abc"}']); $serializer = $this->createMock(SerializerInterface::class); @@ -152,7 +139,7 @@ public function testDeserializeReturnsTask () : void public function testDeserializeReturnsNullWhenDeserializedObjectIsNotATask () : void { $task = $this->createTask(); - $log = new TaskLog($task); + $log = new TaskLog($task, "my-uuid"); $log->setTaskDetails(["task" => "{}'"]); $serializer = self::createStub(SerializerInterface::class); @@ -166,7 +153,7 @@ public function testDeserializeReturnsNullWhenDeserializedObjectIsNotATask () : public function testDeserializeLogsErrorAndReturnsNullOnSerializerException () : void { $task = $this->createTask(); - $log = new TaskLog($task); + $log = new TaskLog($task, "my-uuid"); $log->setTaskDetails(["task" => "invalid-json"]); $serializer = self::createStub(SerializerInterface::class); diff --git a/tests/Task/TaskTest.php b/tests/Task/TaskTest.php deleted file mode 100644 index 22ab5da..0000000 --- a/tests/Task/TaskTest.php +++ /dev/null @@ -1,37 +0,0 @@ -ulid; - $newTask = $task->withNewTaskUlid(); - /** @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) */ - self::assertNotSame($initialUlid, $newTask->ulid, "Task ULID should change on PHP 8.4"); - } -} From 48aa8effb749de29d83a972464895cf92a5d229e Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 11:49:27 +0200 Subject: [PATCH 2/9] Fetch last unfinished run instead of storing it externally --- src/Director/TaskDirector.php | 3 --- .../TaskIdentificationMiddleware.php | 6 +++--- src/Identification/TaskIdentifier.php | 17 ----------------- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/Director/TaskDirector.php b/src/Director/TaskDirector.php index 80210bc..7ea8213 100644 --- a/src/Director/TaskDirector.php +++ b/src/Director/TaskDirector.php @@ -40,9 +40,6 @@ public function startRun (Task $task) : RunDirector $run = $this->logModel->createRunForTask($log); $this->logModel->flush(); - // store latest run - $this->taskIdentifier->setLatestRun($log, $run); - return new RunDirector($this->logModel, $run); } } diff --git a/src/Identification/TaskIdentificationMiddleware.php b/src/Identification/TaskIdentificationMiddleware.php index 5eea23d..ece9de4 100644 --- a/src/Identification/TaskIdentificationMiddleware.php +++ b/src/Identification/TaskIdentificationMiddleware.php @@ -55,7 +55,7 @@ public function handle (Envelope $envelope, StackInterface $stack) : Envelope { $run = $this->getTaskRun($logEntry, $envelope->getMessage()); - if (null !== $run && !$run->isFinished) + if (null !== $run) { $run->abort(false, $e->getMessage()); $this->logModel->flush(); @@ -70,7 +70,7 @@ public function handle (Envelope $envelope, StackInterface $stack) : Envelope { $run = $this->getTaskRun($logEntry, $envelope->getMessage()); - if (null !== $run && !$run->isFinished) + if (null !== $run) { $run->abort( success: true, @@ -98,7 +98,7 @@ private function getTaskRun (TaskLog $log, object $message) : ?TaskRun return $this->logModel->createRunForTask($log); } - return $this->taskIdentifier->getLatestRun($message); + return $log->getLastUnfinishedRun(); } /** diff --git a/src/Identification/TaskIdentifier.php b/src/Identification/TaskIdentifier.php index a938218..3324e03 100644 --- a/src/Identification/TaskIdentifier.php +++ b/src/Identification/TaskIdentifier.php @@ -3,7 +3,6 @@ namespace Torr\TaskManager\Identification; use Symfony\Contracts\Service\ResetInterface; -use Torr\TaskManager\Entity\TaskRun; /** * @final @@ -13,15 +12,11 @@ class TaskIdentifier implements ResetInterface /** @var \WeakMap */ private \WeakMap $uuidMap; - /** @var \WeakMap */ - private \WeakMap $latestRuns; - /** */ public function __construct () { $this->uuidMap = new \WeakMap(); - $this->latestRuns = new \WeakMap(); } /** @@ -41,23 +36,11 @@ public function getUuid (object $message) : ?string ?? null; } - public function setLatestRun (object $message, TaskRun $run) : void - { - $this->latestRuns[$message] = $run; - } - - public function getLatestRun (object $message) : ?TaskRun - { - return $this->latestRuns[$message] - ?? null; - } - /** * */ public function reset () : void { $this->uuidMap = new \WeakMap(); - $this->latestRuns = new \WeakMap(); } } From bb2ef5ac7d54a962470c55d8c8b95d1ae7cd9474 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 11:50:08 +0200 Subject: [PATCH 3/9] Remove event listener It was reimplemented in middleware --- src/Listener/MessengerEventListener.php | 75 ------------------------- 1 file changed, 75 deletions(-) delete mode 100644 src/Listener/MessengerEventListener.php diff --git a/src/Listener/MessengerEventListener.php b/src/Listener/MessengerEventListener.php deleted file mode 100644 index 5fb4af6..0000000 --- a/src/Listener/MessengerEventListener.php +++ /dev/null @@ -1,75 +0,0 @@ -getEnvelope(); - $taskLog = $this->getLogForEvent($envelope); - - if (null === $taskLog) - { - return; - } - - // abort run as "success". It wasn't marked as finished manually, but it succeeded nonetheless. - $run = $taskLog->getLastUnfinishedRun(); - $run?->abort(true); - - $this->logModel->flush(); - } - - #[AsEventListener] - public function onWorkerMessageFailed (WorkerMessageFailedEvent $event) : void - { - $envelope = $event->getEnvelope(); - $taskLog = $this->getLogForEvent($envelope); - - if (null === $taskLog) - { - return; - } - - // abort run as "failure". It wasn't marked as finished manually, and it failed. - $run = $taskLog->getLastUnfinishedRun(); - $run?->abort(false, $event->getThrowable()->getMessage()); - - $this->logModel->flush(); - } - - /** - * - */ - private function getLogForEvent (Envelope $envelope) : ?TaskLog - { - $uuid = $envelope->last(TaskIdStamp::class)?->taskId; - $message = $envelope->getMessage(); - - // filter out task manager internal tasks - return null !== $uuid && !$message instanceof TaskManagerInternalTask - ? $this->logModel->getLogForUuid($message, $uuid) - : null; - } -} From 2347e11f20aed2cd485200101d1c5526051734cb Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 11:54:14 +0200 Subject: [PATCH 4/9] Allow object in `DispatchAfterRunTask` --- src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php b/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php index 345ae7b..5d66905 100644 --- a/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php +++ b/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php @@ -18,7 +18,7 @@ readonly class DispatchAfterRunTask extends Task implements TaskManagerInternalTask { public function __construct ( - public Task $task, + public object $task, public array|string $transportNames = [], ) { @@ -31,8 +31,12 @@ public function __construct ( #[\Override] public function getMetaData () : TaskMetaData { + $label = $this->task instanceof Task + ? $this->task->getMetaData()->label + : get_debug_type($this->task); + return new TaskMetaData( - \sprintf("Redispatch task '%s' after the current run", $this->task->getMetaData()->label), + \sprintf("Redispatch task '%s' after the current run", $label), ); } } From 49851564bddfad3f8abfb9d99fe031e22877b77a Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 11:54:37 +0200 Subject: [PATCH 5/9] Allow `object` in schedule --- src/Schedule/WrappedSchedule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schedule/WrappedSchedule.php b/src/Schedule/WrappedSchedule.php index 0f0c368..9a62602 100644 --- a/src/Schedule/WrappedSchedule.php +++ b/src/Schedule/WrappedSchedule.php @@ -42,7 +42,7 @@ public function __construct ( */ public function cron ( string $cronExpression, - Task $task, + object $task, \DateTimeZone|string|null $timezone = null, ) : static { @@ -60,7 +60,7 @@ public function cron ( */ public function every ( string|int|\DateInterval $frequency, - Task $task, + object $task, string|\DateTimeImmutable|null $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01'), ) : static From f92a296e2010a9233f95c7aac07805876fa9c843 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 12:16:44 +0200 Subject: [PATCH 6/9] Improve variable name --- src/Identification/TaskIdentificationMiddleware.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Identification/TaskIdentificationMiddleware.php b/src/Identification/TaskIdentificationMiddleware.php index ece9de4..76b52b4 100644 --- a/src/Identification/TaskIdentificationMiddleware.php +++ b/src/Identification/TaskIdentificationMiddleware.php @@ -51,17 +51,17 @@ public function handle (Envelope $envelope, StackInterface $stack) : Envelope // push message through the stack $envelope = $stack->next()->handle($envelope, $stack); } - catch (ExceptionInterface $e) + catch (ExceptionInterface $exception) { $run = $this->getTaskRun($logEntry, $envelope->getMessage()); if (null !== $run) { - $run->abort(false, $e->getMessage()); + $run->abort(false, $exception->getMessage()); $this->logModel->flush(); } - throw $e; + throw $exception; } $handledStamp = $envelope->last(HandledStamp::class); From 43d99cf7cd168416e72ae8622f1aaeabd82403a0 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 12:16:50 +0200 Subject: [PATCH 7/9] Simplify code --- src/Manager/TaskManager.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Manager/TaskManager.php b/src/Manager/TaskManager.php index 66583f1..627e0fc 100644 --- a/src/Manager/TaskManager.php +++ b/src/Manager/TaskManager.php @@ -30,14 +30,13 @@ public function __construct ( */ public function enqueue (object $task, array $stamps = []) : string { - if ($task instanceof Task) - { - $uniqueTaskId = $task->getMetaData()->uniqueTaskId; + $uniqueTaskId = $task instanceof Task + ? $task->getMetaData()->uniqueTaskId + : null; - if (null !== $uniqueTaskId) - { - $stamps[] = new DeduplicateStamp($uniqueTaskId); - } + if (null !== $uniqueTaskId) + { + $stamps[] = new DeduplicateStamp($uniqueTaskId); } $id = new TaskIdStamp(); From 65b79475fa7ed13722ee408c7fb4b0c381ac0d17 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 12:16:56 +0200 Subject: [PATCH 8/9] Remove obsolete ulid in task --- src/Task/Task.php | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/Task/Task.php b/src/Task/Task.php index 9425814..f80a166 100644 --- a/src/Task/Task.php +++ b/src/Task/Task.php @@ -2,31 +2,11 @@ namespace Torr\TaskManager\Task; -use Symfony\Component\Uid\UuidV7; - /** * A runnable task */ abstract readonly class Task { - /** - * @deprecated use $taskId instead - * - * @todo remove in 4.0 - * - * @phpstan-ignore-next-line property.deprecated (The uuid integration will be refactored in v4) - */ - public string $ulid; - - /** - */ - public function __construct () - { - $uuid = new UuidV7()->toString(); - /** @phpstan-ignore-next-line property.deprecated (We still need to support the deprecated property) */ - $this->ulid = $uuid; - } - /** * Defines the metadata for this task. * @@ -35,17 +15,6 @@ public function __construct () */ abstract public function getMetaData () : TaskMetaData; - /** - * - */ - public function withNewTaskUlid () : static - { - $uuid = new UuidV7()->toString(); - - return clone($this, [ - "ulid" => $uuid, - ]); - } /** * Is called before the task is stored in the task log entry. From ac67d6e7bb74f645530aeac72b445a6d10a7dc50 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Thu, 18 Jun 2026 12:19:56 +0200 Subject: [PATCH 9/9] Remove constructor --- UPGRADE.md | 1 + src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php | 5 +---- src/Task/Task.php | 1 - tests/Event/RegisterTasksEventTest.php | 5 +---- tests/Manager/TaskManagerTest.php | 7 ++----- tests/Normalizer/TaskDetailsNormalizerTest.php | 5 +---- tests/Registry/TaskRegistryTest.php | 5 +---- 7 files changed, 7 insertions(+), 22 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index cb91a2d..5b84bad 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -3,6 +3,7 @@ * `TaskLog::getTaskObject()` was removed. Use `TaskDetailsNormalizer::deserializeTask($log)` instead. * `TaskLog::$ulid` was removed. +* `Task` has no constructor anymore, so you need to remove your `parent::__construct()` calls. 1.x to 2.0 diff --git a/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php b/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php index 5d66905..733cae9 100644 --- a/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php +++ b/src/Task/DispatchAfterRunTask/DispatchAfterRunTask.php @@ -20,10 +20,7 @@ public function __construct ( public object $task, public array|string $transportNames = [], - ) - { - parent::__construct(); - } + ) {} /** * diff --git a/src/Task/Task.php b/src/Task/Task.php index f80a166..c10ec5a 100644 --- a/src/Task/Task.php +++ b/src/Task/Task.php @@ -15,7 +15,6 @@ */ abstract public function getMetaData () : TaskMetaData; - /** * Is called before the task is stored in the task log entry. * You can clone the task here to remove / redact / truncate fields in the normalized task. diff --git a/tests/Event/RegisterTasksEventTest.php b/tests/Event/RegisterTasksEventTest.php index 46b34ad..266738b 100644 --- a/tests/Event/RegisterTasksEventTest.php +++ b/tests/Event/RegisterTasksEventTest.php @@ -22,10 +22,7 @@ private function createTask (string $label, ?string $group = null) : Task public function __construct ( private string $label, private ?string $group, - ) - { - parent::__construct(); - } + ) {} #[\Override] public function getMetaData () : TaskMetaData diff --git a/tests/Manager/TaskManagerTest.php b/tests/Manager/TaskManagerTest.php index 159bd8e..6c3dc64 100644 --- a/tests/Manager/TaskManagerTest.php +++ b/tests/Manager/TaskManagerTest.php @@ -28,10 +28,7 @@ private function createTask (?string $uniqueTaskId = null) : Task return new readonly class($uniqueTaskId) extends Task { public function __construct ( private ?string $uniqueTaskId, - ) - { - parent::__construct(); - } + ) {} #[\Override] public function getMetaData () : TaskMetaData @@ -142,7 +139,7 @@ static function (Envelope $envelope) use (&$capturedEnvelope) : Envelope $stamp = new class() implements StampInterface {}; $manager = $this->createManager(["queue" => $this->createListableTransport()], $bus); - $manager->enqueue($this->createTask(null), [$stamp]); + $manager->enqueue($this->createTask(), [$stamp]); self::assertNotNull($capturedEnvelope); self::assertNotEmpty($capturedEnvelope->all($stamp::class)); diff --git a/tests/Normalizer/TaskDetailsNormalizerTest.php b/tests/Normalizer/TaskDetailsNormalizerTest.php index 9fc3d23..8e61e67 100644 --- a/tests/Normalizer/TaskDetailsNormalizerTest.php +++ b/tests/Normalizer/TaskDetailsNormalizerTest.php @@ -28,10 +28,7 @@ private function createTask (string $label = "Test Task") : Task return new readonly class($label) extends Task { public function __construct ( private string $label, - ) - { - parent::__construct(); - } + ) {} #[\Override] public function getMetaData () : TaskMetaData diff --git a/tests/Registry/TaskRegistryTest.php b/tests/Registry/TaskRegistryTest.php index 3d75cf9..f82f622 100644 --- a/tests/Registry/TaskRegistryTest.php +++ b/tests/Registry/TaskRegistryTest.php @@ -23,10 +23,7 @@ private function createTask (string $label, ?string $group = null) : Task public function __construct ( private string $label, private ?string $group, - ) - { - parent::__construct(); - } + ) {} #[\Override] public function getMetaData () : TaskMetaData