Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractJob
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
8 / 8
16
100.00% covered (success)
100.00%
1 / 1
 logJobStart
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 logJobSuccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 logJobError
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 executeWithErrorHandling
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 executeWithEntitySave
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 clearContentCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateArguments
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getJobType
n/a
0 / 0
n/a
0 / 0
0
 execute
n/a
0 / 0
n/a
0 / 0
0
1<?php
2declare(strict_types=1);
3
4namespace App\Job;
5
6use Cake\Cache\Cache;
7use Cake\Datasource\EntityInterface;
8use Cake\Log\LogTrait;
9use Cake\ORM\Table;
10use Cake\ORM\TableRegistry;
11use Cake\Queue\Job\JobInterface;
12use Cake\Queue\Job\Message;
13use Exception;
14use Interop\Queue\Processor;
15
16/**
17 * AbstractJob Class
18 *
19 * Base class for all queue jobs providing common functionality for logging,
20 * error handling, and result processing. Reduces code duplication across jobs.
21 */
22abstract class AbstractJob implements JobInterface
23{
24    use LogTrait;
25
26    /**
27     * Maximum number of attempts to process the job
28     *
29     * @var int
30     */
31    public static int $maxAttempts = 3;
32
33    /**
34     * Whether there should be only one instance of a job on the queue at a time
35     *
36     * @var bool
37     */
38    public static bool $shouldBeUnique = true;
39
40    /**
41     * Log the start of a job execution
42     *
43     * @param string $id The entity ID being processed
44     * @param string $title Optional title/name for better logging
45     * @return void
46     */
47    protected function logJobStart(string $id, string $title = ''): void
48    {
49        $this->log(
50            sprintf('Received %s message: %s%s', static::getJobType(), $id, $title ? " : {$title}" : ''),
51            'info',
52            ['group_name' => static::class],
53        );
54    }
55
56    /**
57     * Log successful job completion
58     *
59     * @param string $id The entity ID that was processed
60     * @param string $title Optional title/name for better logging
61     * @return void
62     */
63    protected function logJobSuccess(string $id, string $title = ''): void
64    {
65        $this->log(
66            sprintf('%s completed successfully. ID: %s%s', static::getJobType(), $id, $title ? " ({$title})" : ''),
67            'info',
68            ['group_name' => static::class],
69        );
70    }
71
72    /**
73     * Log job execution error
74     *
75     * @param string $id The entity ID that failed to process
76     * @param string $error The error message
77     * @param string $title Optional title/name for better logging
78     * @return void
79     */
80    protected function logJobError(string $id, string $error, string $title = ''): void
81    {
82        $this->log(
83            sprintf('%s failed. ID: %s%s Error: %s', static::getJobType(), $id, $title ? " ({$title})" : '', $error),
84            'error',
85            ['group_name' => static::class],
86        );
87    }
88
89    /**
90     * Execute job operation with standardized error handling and logging
91     *
92     * @param string $id The entity ID being processed
93     * @param callable $operation The main job operation to execute
94     * @param string $title Optional title/name for better logging
95     * @return string|null Returns Processor::ACK on success, Processor::REJECT on failure
96     */
97    protected function executeWithErrorHandling(string $id, callable $operation, string $title = ''): ?string
98    {
99        $this->logJobStart($id, $title);
100
101        try {
102            $result = $operation();
103
104            if ($result) {
105                $this->logJobSuccess($id, $title);
106                $this->clearContentCache();
107
108                return Processor::ACK;
109            } else {
110                $this->logJobError($id, 'Operation returned false or null', $title);
111
112                return Processor::REJECT;
113            }
114        } catch (Exception $e) {
115            $this->logJobError($id, $e->getMessage(), $title);
116
117            return Processor::REJECT;
118        }
119    }
120
121    /**
122     * Execute job operation with entity save handling
123     *
124     * @param string $id The entity ID being processed
125     * @param callable $operation Operation that should return an entity to save
126     * @param string $title Optional title/name for better logging
127     * @return string|null Returns Processor::ACK on success, Processor::REJECT on failure
128     */
129    protected function executeWithEntitySave(string $id, callable $operation, string $title = ''): ?string
130    {
131        return $this->executeWithErrorHandling($id, function () use ($operation) {
132            $result = $operation();
133
134            if ($result instanceof EntityInterface) {
135                $table = $this->getTable($result->getSource());
136
137                return $table->save($result);
138            }
139
140            return $result;
141        }, $title);
142    }
143
144    /**
145     * Clear content cache after successful operations
146     *
147     * @return void
148     */
149    protected function clearContentCache(): void
150    {
151        Cache::clear('content');
152    }
153
154    /**
155     * Get table instance using TableRegistry
156     *
157     * @param string $tableName The table name to retrieve
158     * @return \Cake\ORM\Table
159     */
160    protected function getTable(string $tableName): Table
161    {
162        return TableRegistry::getTableLocator()->get($tableName);
163    }
164
165    /**
166     * Validate required message arguments
167     *
168     * @param \Cake\Queue\Job\Message $message The queue message
169     * @param array<string> $required Array of required argument names
170     * @return bool True if all required arguments are present
171     */
172    protected function validateArguments(Message $message, array $required): bool
173    {
174        foreach ($required as $arg) {
175            if (!$message->getArgument($arg)) {
176                $this->log(
177                    sprintf('Missing required argument: %s for %s', $arg, static::getJobType()),
178                    'error',
179                    ['group_name' => static::class],
180                );
181
182                return false;
183            }
184        }
185
186        return true;
187    }
188
189    /**
190     * Get the human-readable job type name for logging
191     *
192     * @return string The job type description
193     */
194    abstract protected static function getJobType(): string;
195
196    /**
197     * Execute the job with the given message
198     *
199     * @param \Cake\Queue\Job\Message $message The queue message
200     * @return string|null Returns Processor::ACK on success, Processor::REJECT on failure
201     */
202    abstract public function execute(Message $message): ?string;
203}