• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / cronjob / 18288101817

06 Oct 2025 04:45PM UTC coverage: 68.511% (-1.0%) from 69.514%
18288101817

push

github

daycry
Update README.md

483 of 705 relevant lines covered (68.51%)

5.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.28
/src/Job.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Daycry\CronJob;
6

7
use CodeIgniter\Events\Events;
8
use Config\Services;
9
use Daycry\CronJob\Config\CronJob as BaseConfig;
10
use Daycry\CronJob\Exceptions\CronJobException;
11
use Daycry\CronJob\Traits\ActivityTrait;
12
use Daycry\CronJob\Traits\FrequenciesTrait;
13
use Daycry\CronJob\Traits\InteractsWithSpark;
14
use Daycry\CronJob\Traits\LogTrait;
15
use Daycry\CronJob\Traits\StatusTrait;
16
use InvalidArgumentException;
17
use ReflectionException;
18
use ReflectionFunction;
19
use SplFileObject;
20

21
/**
22
 * Class Job
23
 *
24
 * Represents a single task that should be scheduled
25
 * and run periodically.
26
 *
27
 * @property-read mixed  $action
28
 * @property-read array  $environments
29
 * @property-read string $name
30
 * @property-read string $type
31
 * @property-read array  $types
32
 */
33
class Job
34
{
35
    use FrequenciesTrait;
36
    use LogTrait;
37
    use ActivityTrait;
38
    use StatusTrait;
39
    use InteractsWithSpark;
40

41
    protected BaseConfig $config;
42

43
    /**
44
     * Supported action types.
45
     *
46
     * @var list<string>
47
     */
48
    protected array $types = [
49
        'command',
50
        'shell',
51
        'closure',
52
        'event',
53
        'url',
54
    ];
55

56
    /**
57
     * The type of cron run.
58
     */
59
    protected string $runType = 'multiple';
60

61
    /**
62
     * If the job will run as a background process
63
     */
64
    protected bool $runInBackground = false;
65

66
    /**
67
     * The type of action.
68
     */
69
    protected string $type;
70

71
    /**
72
     * The actual content that should be run.
73
     */
74
    protected mixed $action;
75

76
    /**
77
     * If not empty, lists the allowed environments
78
     * this can run in.
79
     */
80
    protected array $environments = [];
81

82
    /**
83
     * The alias this task can be run by
84
     */
85
    protected ?string $name = null;
86

87
    /**
88
     * List of job dependencies
89
     *
90
     * @var list<string>|null
91
     */
92
    protected ?array $dependsOn = null;
93

94
    /**
95
     * The maximum number of retries for this job.
96
     */
97
    protected ?int $maxRetries = null;
98

99
    /**
100
     * The timeout (in seconds) for this job.
101
     */
102
    protected ?int $timeout = null;
103
    /**
104
     * Cached computed name hash to avoid recalculating reflection/serialization repeatedly.
105
     */
106
    private ?string $computedName = null;
107

108
    /**
109
     * Job constructor.
110
     *
111
     * @param mixed $action
112
     *
113
     * @throws CronJobException
114
     */
115
    public function __construct(string $type, $action)
116
    {
117
        if (! in_array($type, $this->types, true)) {
40✔
118
            throw CronJobException::forInvalidTaskType($type);
1✔
119
        }
120
        $this->config = config('CronJob');
39✔
121
        $this->type   = $type;
39✔
122
        $this->action = $action;
39✔
123
    }
124

125
    /**
126
     * Set the name to reference this task by
127
     *
128
     * @return $this
129
     */
130
    public function named(string $name): self
131
    {
132
        $this->name = $name;
24✔
133

134
        return $this;
24✔
135
    }
136

137
    /**
138
     * Returns the type.
139
     */
140
    public function getType(): string
141
    {
142
        return $this->type;
14✔
143
    }
144

145
    /**
146
     * Returns the saved action.
147
     */
148
    public function getAction(): mixed
149
    {
150
        return $this->action;
18✔
151
    }
152

153
    /**
154
     * Runs this Task's action.
155
     *
156
     * @throws CronJobException
157
     */
158
    public function run(): mixed
159
    {
160
        $method = 'run' . ucfirst($this->type);
12✔
161
        if (! method_exists($this, $method)) {
12✔
162
            throw CronJobException::forInvalidTaskType($this->type);
×
163
        }
164

165
        return $this->{$method}();
12✔
166
    }
167

168
    /**
169
     * Restricts this task to run within only specified environments.
170
     *
171
     * @param mixed ...$environments
172
     *
173
     * @return $this
174
     */
175
    public function environments(...$environments): self
176
    {
177
        $this->environments = $environments;
2✔
178

179
        return $this;
2✔
180
    }
181

182
    /**
183
     * Returns the environments.
184
     */
185
    public function getEnvironments(): array
186
    {
187
        return $this->environments;
1✔
188
    }
189

190
    /**
191
     * Checks if it runs within the specified environment.
192
     */
193
    protected function runsInEnvironment(string $environment): bool
194
    {
195
        if (empty($this->environments)) {
2✔
196
            return true;
×
197
        }
198

199
        return in_array($environment, $this->environments, true);
2✔
200
    }
201

202
    /**
203
     * Runs a framework Command.
204
     *
205
     * @return string Buffered output from the Command
206
     *
207
     * @throws InvalidArgumentException
208
     */
209
    protected function runCommand(): string
210
    {
211
        if (! $this->shouldRunInBackground()) {
1✔
212
            return command($this->getAction());
1✔
213
        }
214

215
        $output = $this->runCommandInBackground();
×
216

217
        return is_string($output) ? $output : '';
×
218
    }
219

220
    private function runCommandInBackground(): bool|string
221
    {
222
        $this->createFoldersIfNeeded();
×
223

224
        $runCommand = $this->sparkCommandInBackground($this->getAction());
×
225

226
        $afterRunCommand = $this->sparkCommandInBackground(
×
227
            "cronjob:finish --name {$this->getName()} --type {$this->getType()}",
×
228
        );
×
229

230
        return exec("{$runCommand} && {$afterRunCommand}");
×
231
    }
232

233
    /**
234
     * Executes a shell script.
235
     *
236
     * @return array Lines of output from exec
237
     */
238
    protected function runShell(): array
239
    {
240
        exec($this->getAction(), $output);
×
241

242
        return $output;
×
243
    }
244

245
    /**
246
     * Calls a Closure.
247
     *
248
     * @return mixed The result of the closure
249
     */
250
    protected function runClosure()
251
    {
252
        return $this->getAction()->__invoke();
10✔
253
    }
254

255
    /**
256
     * Triggers an Event.
257
     *
258
     * @return bool Result of the trigger
259
     */
260
    protected function runEvent(): bool
261
    {
262
        return Events::trigger($this->getAction());
×
263
    }
264

265
    /**
266
     * Queries a URL.
267
     *
268
     * @return mixed|string Body of the Response
269
     */
270
    protected function runUrl()
271
    {
272
        $response = Services::curlrequest()->request('GET', $this->getAction());
1✔
273

274
        return $response->getBody();
1✔
275
    }
276

277
    /**
278
     * Builds a unique name for the task.
279
     * Used when an existing name doesn't exist.
280
     *
281
     * @return string
282
     *
283
     * @throws ReflectionException
284
     */
285
    protected function buildName()
286
    {
287
        // Get a hash based on the action
288
        // Closures cannot be serialized so do it the hard way
289
        if ($this->getType() === 'closure') {
3✔
290
            $ref  = new ReflectionFunction($this->getAction());
1✔
291
            $file = new SplFileObject($ref->getFileName());
1✔
292
            $file->seek($ref->getStartLine() - 1);
1✔
293
            $content = '';
1✔
294

295
            while ($file->key() < $ref->getEndLine()) {
1✔
296
                $content .= $file->current();
1✔
297
                $file->next();
1✔
298
            }
299
            $actionString = json_encode([
1✔
300
                $content,
1✔
301
                $ref->getStaticVariables(),
1✔
302
            ]);
1✔
303
        } else {
304
            $actionString = serialize($this->getAction());
2✔
305
        }
306

307
        // Get a hash based on the expression
308
        $expHash = $this->getExpression();
3✔
309

310
        return $this->getType() . '_' . md5($actionString . '_' . $expHash);
3✔
311
    }
312

313
    /**
314
     * @return string
315
     */
316
    public function getName()
317
    {
318
        if ($this->name) {
24✔
319
            return $this->name;
22✔
320
        }
321
        // Cache computed hash so multiple calls don't repeat reflection work
322
        if ($this->computedName === null) {
5✔
323
            $this->computedName = $this->buildName();
3✔
324
        }
325

326
        return $this->computedName;
5✔
327
    }
328

329
    /**
330
     * Set the runType of task
331
     *
332
     * @return $this
333
     */
334
    public function setRunType(string $runType): Job
335
    {
336
        $this->runType = $runType;
2✔
337

338
        return $this;
2✔
339
    }
340

341
    /**
342
     * Returns the runType.
343
     */
344
    public function getRunType(): string
345
    {
346
        return $this->runType;
×
347
    }
348

349
    /**
350
     * Mark job to run in background
351
     *
352
     * @return $this
353
     */
354
    public function runInBackground(): Job
355
    {
356
        // Only commands are currently able to execute in background
357
        if ($this->type === 'command') {
1✔
358
            $this->runInBackground = true;
1✔
359
        }
360

361
        return $this;
1✔
362
    }
363

364
    /**
365
     * If the job will run in the background
366
     */
367
    public function shouldRunInBackground(): bool
368
    {
369
        return $this->runInBackground;
13✔
370
    }
371

372
    /**
373
     * Set dependencies for this job.
374
     *
375
     * @return $this
376
     */
377
    public function dependsOn(array|string $jobNames): self
378
    {
379
        $this->dependsOn = is_array($jobNames) ? $jobNames : [$jobNames];
3✔
380

381
        return $this;
3✔
382
    }
383

384
    /**
385
     * Get dependencies for this job.
386
     */
387
    public function getDependsOn(): ?array
388
    {
389
        return $this->dependsOn;
16✔
390
    }
391

392
    /**
393
     * Set the maximum number of retries for this job.
394
     *
395
     * @return $this
396
     */
397
    public function maxRetries(int $retries): self
398
    {
399
        $this->maxRetries = $retries;
4✔
400

401
        return $this;
4✔
402
    }
403

404
    /**
405
     * Set the timeout (in seconds) for this job.
406
     *
407
     * @return $this
408
     */
409
    public function timeout(int $timeout): self
410
    {
411
        $this->timeout = $timeout;
2✔
412

413
        return $this;
2✔
414
    }
415

416
    /**
417
     * Get the maximum number of retries for this job.
418
     */
419
    public function getMaxRetries(): ?int
420
    {
421
        return $this->maxRetries ?? null;
11✔
422
    }
423

424
    /**
425
     * Get the timeout (in seconds) for this job.
426
     */
427
    public function getTimeout(): ?int
428
    {
429
        return $this->timeout ?? null;
12✔
430
    }
431
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc