Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
QueueJob
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
7 / 7
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __call
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 recordProgress
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 markRunning
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 markCompleted
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 markFailed
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 resetForRetry
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Automation\Queue\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use BadMethodCallException;
12use DateTimeImmutable;
13
14/**
15 * QueueJob Domain Entity.
16 *
17 * Encapsulates background queue job data and state transitions.
18 *
19 * @package App\Modules\Automation\Queue\Domain\Model
20 */
21final class QueueJob
22{
23    public const string STATUS_PENDING = 'pending';
24    public const string STATUS_RUNNING = 'running';
25    public const string STATUS_COMPLETED = 'completed';
26    public const string STATUS_FAILED = 'failed';
27    public const string STATUS_CANCELLED = 'cancelled';
28
29    /**
30     * QueueJob constructor.
31     *
32     * @param int $id Unique job identifier.
33     * @param string $jobType Machine name of the job handler type.
34     * @param string $label Human-readable title of the task.
35     * @param string $status Current execution status.
36     * @param array<string, mixed> $payload Serialized execution parameters.
37     * @param int $progressPercent Execution progress (0-100).
38     * @param int $totalItems Total work items count.
39     * @param int $processedItems Processed work items count.
40     * @param int $attempts Number of execution attempts.
41     * @param int $maxAttempts Maximum allowed retry attempts.
42     * @param DateTimeImmutable|null $heartbeatAt Last recorded heartbeat timestamp.
43     * @param DateTimeImmutable|null $startedAt Execution start timestamp.
44     * @param DateTimeImmutable|null $completedAt Completion timestamp.
45     * @param DateTimeImmutable|null $failedAt Failure timestamp.
46     * @param string|null $errorMessage Last recorded error message.
47     * @param string|null $outputLog Execution output and diagnostics log.
48     * @param DateTimeImmutable|null $createdAt Creation timestamp.
49     * @param DateTimeImmutable|null $updatedAt Last update timestamp.
50     * @param int $createdBy User ID who enqueued the job.
51     * @param int $owner User ID who owns the record.
52     */
53    public function __construct(
54        public readonly int $id,
55        public readonly string $jobType,
56        public private(set) string $label,
57        public private(set) string $status = self::STATUS_PENDING,
58        public private(set) array $payload = [],
59        public private(set) int $progressPercent = 0,
60        public private(set) int $totalItems = 0,
61        public private(set) int $processedItems = 0,
62        public private(set) int $attempts = 0,
63        public private(set) int $maxAttempts = 3,
64        public private(set) ?DateTimeImmutable $heartbeatAt = null,
65        public private(set) ?DateTimeImmutable $startedAt = null,
66        public private(set) ?DateTimeImmutable $completedAt = null,
67        public private(set) ?DateTimeImmutable $failedAt = null,
68        public private(set) ?string $errorMessage = null,
69        public private(set) ?string $outputLog = null,
70        public private(set) ?DateTimeImmutable $createdAt = null,
71        public private(set) ?DateTimeImmutable $updatedAt = null,
72        public private(set) int $createdBy = 1,
73        public private(set) int $owner = 1,
74    ) {
75    }
76
77    /**
78     * Magic getter for property access and backwards compatibility.
79     *
80     * @param string $name Method name.
81     * @param array<int, mixed> $arguments Method arguments.
82     * @return mixed Property value.
83     */
84    public function __call(string $name, array $arguments): mixed
85    {
86        if (str_starts_with($name, 'get')) {
87            $prop = lcfirst(substr($name, 3));
88            if (property_exists($this, $prop)) {
89                return $this->{$prop};
90            }
91        }
92
93        throw new BadMethodCallException("Method {$name} does not exist on " . self::class);
94    }
95
96    /**
97     * Updates execution progress and heartbeat.
98     *
99     * @param int $processed Processed items count.
100     * @param int $total Total items count.
101     * @param DateTimeImmutable $now Current timestamp.
102     * @return void
103     */
104    public function recordProgress(int $processed, int $total, DateTimeImmutable $now): void
105    {
106        $this->processedItems = $processed;
107        $this->totalItems = $total;
108        $this->progressPercent = $total > 0 ? (int) min(100, round(($processed / $total) * 100)) : 100;
109        $this->heartbeatAt = $now;
110    }
111
112    /**
113     * Marks job as started and running.
114     *
115     * @param DateTimeImmutable $now Current timestamp.
116     * @return void
117     */
118    public function markRunning(DateTimeImmutable $now): void
119    {
120        $this->status = self::STATUS_RUNNING;
121        $this->startedAt = $now;
122        $this->heartbeatAt = $now;
123        $this->attempts++;
124    }
125
126    /**
127     * Marks job as completed.
128     *
129     * @param DateTimeImmutable $now Current timestamp.
130     * @param string|null $outputLog Optional final execution log.
131     * @return void
132     */
133    public function markCompleted(DateTimeImmutable $now, ?string $outputLog = null): void
134    {
135        $this->status = self::STATUS_COMPLETED;
136        $this->completedAt = $now;
137        $this->progressPercent = 100;
138        if ($outputLog !== null) {
139            $this->outputLog = $outputLog;
140        }
141    }
142
143    /**
144     * Marks job as failed with an error message.
145     *
146     * @param string $errorMessage Error description.
147     * @param DateTimeImmutable $now Current timestamp.
148     * @return void
149     */
150    public function markFailed(string $errorMessage, DateTimeImmutable $now): void
151    {
152        $this->status = self::STATUS_FAILED;
153        $this->failedAt = $now;
154        $this->errorMessage = $errorMessage;
155    }
156
157    /**
158     * Resets job back to pending state for automatic retry.
159     *
160     * @param string|null $retryReason Reason for retry.
161     * @return void
162     */
163    public function resetForRetry(?string $retryReason = null): void
164    {
165        $this->status = self::STATUS_PENDING;
166        $this->startedAt = null;
167        $this->heartbeatAt = null;
168        if ($retryReason !== null) {
169            $this->errorMessage = $retryReason;
170        }
171    }
172}