Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.40% covered (success)
98.40%
123 / 125
96.08% covered (success)
96.08%
49 / 51
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractJob
98.40% covered (success)
98.40%
123 / 125
96.08% covered (success)
96.08%
49 / 51
81
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 generateJobId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setJobId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getJobId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasJobId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setJobDescription
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getJobDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasJobDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasResults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCallable
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 setCommand
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setExec
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getCallable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCommand
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExec
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasCallable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasCommand
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasExec
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMaxAttempts
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMaxAttempts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMaxAttempts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAttemptOnce
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttempts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAttempts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 runUntil
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasRunUntil
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRunUntil
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isExpired
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 hasExceededMaxAttempts
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isValid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 hasNotRun
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 start
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getStarted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasStarted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRunning
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 complete
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getCompleted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isComplete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 failed
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 hasFailed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFailed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addFailedMessage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 hasFailedMessages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFailedMessages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 loadCallable
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 runCommand
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 runExec
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 __sleep
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 __wakeup
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Queue\Process;
15
16use Pop\Application;
17use Pop\Utils\CallableObject;
18use Opis\Closure\SerializableClosure;
19
20/**
21 * Abstract job class
22 *
23 * @category   Pop
24 * @package    Pop\Queue
25 * @author     Nick Sagona, III <dev@nolainteractive.com>
26 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
27 * @license    http://www.popphp.org/license     New BSD License
28 * @version    2.0.0
29 */
30abstract class AbstractJob implements JobInterface
31{
32
33    /**
34     * Job ID
35     * @var ?string
36     */
37    protected ?string $id = null;
38
39    /**
40     * Job Description
41     * @var ?string
42     */
43    protected ?string $description = null;
44
45    /**
46     * Job callable
47     * @var ?CallableObject
48     */
49    protected ?CallableObject $callable = null;
50
51    /**
52     * Job application command
53     * @var ?string
54     */
55    protected ?string $command = null;
56
57    /**
58     * Job CLI executable command
59     * @var ?string
60     */
61    protected ?string $exec = null;
62
63    /**
64     * Job started timestamp
65     * @var ?int
66     */
67    protected ?int $started = null;
68
69    /**
70     * Job completed timestamp
71     * @var ?int
72     */
73    protected ?int $completed = null;
74
75    /**
76     * Job failed timestamp
77     * @var ?int
78     */
79    protected ?int $failed = null;
80
81    /**
82     * Job failed messages
83     * @var array
84     */
85    protected array $failedMessages = [];
86
87    /**
88     * Max attempts
89     * @var int
90     */
91    protected int $maxAttempts = 0;
92
93    /**
94     * Attempts
95     * @var int
96     */
97    protected int $attempts = 0;
98
99    /**
100     * Run until property
101     * @var int|string|null
102     */
103    protected int|string|null $runUntil = null;
104
105    /**
106     * Serialize closure
107     * @var ?string
108     */
109    protected ?string $serializedClosure = null;
110
111    /**
112     * Serialize parameters
113     * @var ?array
114     */
115    protected ?array $serializedParameters = null;
116
117    /**
118     * Job results
119     * @var mixed
120     */
121    protected mixed $results = null;
122
123    /**
124     * Constructor
125     *
126     * Instantiate the job object
127     *
128     * @param  mixed   $callable
129     * @param  mixed   $params
130     * @param  ?string $id
131     */
132    public function __construct(mixed $callable = null, mixed $params = null, ?string $id = null)
133    {
134        if ($callable !== null) {
135            $this->setCallable($callable, $params);
136        }
137        if ($id !== null) {
138            $this->setJobId($id);
139        }
140    }
141
142    /**
143     * Generate job ID
144     *
145     * @return string
146     */
147    public function generateJobId(): string
148    {
149        $this->id = sha1(uniqid(rand()) . time());
150        return $this->id;
151    }
152
153    /**
154     * Set job ID
155     *
156     * @param  string $id
157     * @return AbstractJob
158     */
159    public function setJobId(string $id): AbstractJob
160    {
161        $this->id = $id;
162        return $this;
163    }
164
165    /**
166     * Get job ID
167     *
168     * @return ?string
169     */
170    public function getJobId(): ?string
171    {
172        if (!$this->hasJobId()) {
173            $this->generateJobId();
174        }
175        return $this->id;
176    }
177
178    /**
179     * Has job ID
180     *
181     * @return bool
182     */
183    public function hasJobId(): bool
184    {
185        return ($this->id !== null);
186    }
187
188    /**
189     * Set job description
190     *
191     * @param  string $description
192     * @return AbstractJob
193     */
194    public function setJobDescription(string $description): AbstractJob
195    {
196        $this->description = $description;
197        return $this;
198    }
199
200    /**
201     * Get job description
202     *
203     * @return ?string
204     */
205    public function getJobDescription(): ?string
206    {
207        return $this->description;
208    }
209
210    /**
211     * Has job description
212     *
213     * @return bool
214     */
215    public function hasJobDescription(): bool
216    {
217        return ($this->description !== null);
218    }
219
220    /**
221     * Get job results
222     *
223     * @return mixed
224     */
225    public function getResults(): mixed
226    {
227        return $this->results;
228    }
229
230    /**
231     * Has job results
232     *
233     * @return bool
234     */
235    public function hasResults(): bool
236    {
237        return !empty($this->results);
238    }
239
240    /**
241     * Set job callable
242     *
243     * @param  mixed $callable
244     * @param  mixed $params
245     * @return AbstractJob
246     */
247    public function setCallable(mixed $callable, mixed $params = null): AbstractJob
248    {
249
250        if (!($callable instanceof CallableObject)) {
251            $this->callable = new CallableObject($callable, $params);
252        } else {
253            $this->callable = $callable;
254            if ($params !== null) {
255                if (is_array($params)) {
256                    $this->callable->addParameters($params);
257                } else {
258                    $this->callable->addParameter($params);
259                }
260            }
261        }
262
263        return $this;
264    }
265
266    /**
267     * Set job application command
268     *
269     * @param  string $command
270     * @return AbstractJob
271     */
272    public function setCommand(string $command): AbstractJob
273    {
274        $this->command = $command;
275        return $this;
276    }
277
278    /**
279     * Set job CLI executable command
280     *
281     * @param  string $command
282     * @return AbstractJob
283     */
284    public function setExec(string $command): AbstractJob
285    {
286        $this->exec = $command;
287        return $this;
288    }
289
290    /**
291     * Get job callable
292     *
293     * @return ?CallableObject
294     */
295    public function getCallable(): ?CallableObject
296    {
297        return $this->callable;
298    }
299
300    /**
301     * Get job application command
302     *
303     * @return ?string
304     */
305    public function getCommand(): ?string
306    {
307        return $this->command;
308    }
309
310    /**
311     * Get job CLI executable command
312     *
313     * @return ?string
314     */
315    public function getExec(): ?string
316    {
317        return $this->exec;
318    }
319
320    /**
321     * Has job callable
322     *
323     * @return bool
324     */
325    public function hasCallable(): bool
326    {
327        return ($this->callable !== null);
328    }
329
330    /**
331     * Has job application command
332     *
333     * @return bool
334     */
335    public function hasCommand(): bool
336    {
337        return ($this->command !== null);
338    }
339
340    /**
341     * Has job CLI executable command
342     *
343     * @return bool
344     */
345    public function hasExec(): bool
346    {
347        return ($this->exec !== null);
348    }
349
350    /**
351     * Set max attempts
352     *
353     * @param  int $maxAttempts
354     * @return AbstractJob
355     */
356    public function setMaxAttempts(int $maxAttempts): AbstractJob
357    {
358        $this->maxAttempts = $maxAttempts;
359        return $this;
360    }
361
362    /**
363     * Get max attempts
364     *
365     * @return int
366     */
367    public function getMaxAttempts(): int
368    {
369        return $this->maxAttempts;
370    }
371
372    /**
373     * Has max attempts
374     *
375     * @return bool
376     */
377    public function hasMaxAttempts(): bool
378    {
379        return ($this->maxAttempts > 0);
380    }
381
382    /**
383     * Is job set for only one max attempt
384     *
385     * @return bool
386     */
387    public function isAttemptOnce(): bool
388    {
389        return ($this->maxAttempts == 1);
390    }
391
392    /**
393     * Get actual attempts
394     *
395     * @return int
396     */
397    public function getAttempts(): int
398    {
399        return $this->attempts;
400    }
401
402    /**
403     * Has actual attempts
404     *
405     * @return bool
406     */
407    public function hasAttempts(): bool
408    {
409        return ($this->attempts > 0);
410    }
411
412    /**
413     * Set the run until property
414     *
415     * @param  int|string $runUntil
416     * @return AbstractJob
417     */
418    public function runUntil(int|string $runUntil): AbstractJob
419    {
420        $this->runUntil = $runUntil;
421        return $this;
422    }
423
424    /**
425     * Has run until
426     *
427     * @return bool
428     */
429    public function hasRunUntil(): bool
430    {
431        return ($this->runUntil !== null);
432    }
433
434    /**
435     * Get run until value
436     *
437     * @return int|string|null
438     */
439    public function getRunUntil(): int|string|null
440    {
441        return $this->runUntil;
442    }
443
444    /**
445     * Determine if the job has expired
446     *
447     * @return bool
448     */
449    public function isExpired(): bool
450    {
451        if (!empty($this->runUntil)) {
452            $runUntil = null;
453            if (is_string($this->runUntil) && (strtotime($this->runUntil) !== false)) {
454                $runUntil = strtotime($this->runUntil);
455            } else if (is_numeric($this->runUntil) && ((string)(int)$this->runUntil == $this->runUntil)) {
456                $runUntil = $this->runUntil;
457            }
458
459            if ($runUntil !== null) {
460                return (time() > $runUntil);
461            }
462        }
463
464        return false;
465    }
466
467    /**
468     * Determine if the job has exceeded max attempts
469     *
470     * @return bool
471     */
472    public function hasExceededMaxAttempts(): bool
473    {
474        if ($this->hasMaxAttempts()) {
475            return ($this->attempts >= $this->maxAttempts);
476        }
477
478        return false;
479    }
480
481    /**
482     * Determine if the job is still valid
483     *
484     * @return bool
485     */
486    public function isValid(): bool
487    {
488        return ((!$this->isExpired()) && (!$this->hasExceededMaxAttempts()));
489    }
490
491    /**
492     * Has job run yet
493     *
494     * @return bool
495     */
496    public function hasNotRun(): bool
497    {
498        return (($this->started === null) && ($this->completed === null));
499    }
500
501    /**
502     * Start job
503     *
504     * @return AbstractJob
505     */
506    public function start(): AbstractJob
507    {
508        $this->started = time();
509        return $this;
510    }
511
512    /**
513     * Get started timestamp
514     *
515     * @return ?int
516     */
517    public function getStarted(): ?int
518    {
519        return $this->started;
520    }
521
522    /**
523     * Has job started
524     *
525     * @return bool
526     */
527    public function hasStarted(): bool
528    {
529        return ($this->started !== null);
530    }
531
532    /**
533     * Is job running and has not completed or failed yet
534     *
535     * @return bool
536     */
537    public function isRunning(): bool
538    {
539        return (($this->started !== null) && ($this->completed === null) && ($this->failed === null));
540    }
541
542    /**
543     * Complete job
544     *
545     * @return AbstractJob
546     */
547    public function complete(): AbstractJob
548    {
549        $this->completed = time();
550        $this->attempts++;
551        return $this;
552    }
553
554    /**
555     * Get completed timestamp
556     *
557     * @return ?int
558     */
559    public function getCompleted(): ?int
560    {
561        return $this->completed;
562    }
563
564    /**
565     * Is job complete
566     *
567     * @return bool
568     */
569    public function isComplete(): bool
570    {
571        return ($this->completed !== null);
572    }
573
574    /**
575     * Set job as failed
576     *
577     * @param  ?string $message
578     * @return AbstractJob
579     */
580    public function failed(?string $message = null): AbstractJob
581    {
582        $this->failed = time();
583        $this->attempts++;
584
585        if ($message !== null) {
586            $this->addFailedMessage($message);
587        }
588
589        return $this;
590    }
591
592    /**
593     * Has job failed
594     *
595     * @return bool
596     */
597    public function hasFailed(): bool
598    {
599        return ($this->failed !== null);
600    }
601
602    /**
603     * Get failed timestamp
604     *
605     * @return ?int
606     */
607    public function getFailed(): ?int
608    {
609        return $this->failed;
610    }
611
612    /**
613     * Add failed message
614     *
615     * @param  string $message
616     * @return AbstractJob
617     */
618    public function addFailedMessage(string $message): AbstractJob
619    {
620        $index = $this->failed ?? time();
621        $this->failedMessages[$index] = $message;
622        return $this;
623    }
624
625    /**
626     * Has failed messages
627     *
628     * @return bool
629     */
630    public function hasFailedMessages(): bool
631    {
632        return !empty($this->failedMessages);
633    }
634
635    /**
636     * Get failed messages
637     *
638     * @return array
639     */
640    public function getFailedMessages(): array
641    {
642        return $this->failedMessages;
643    }
644
645    /**
646     * Run job
647     *
648     * @param  ?Application $application
649     * @return mixed
650     */
651    public function run(?Application $application = null): mixed
652    {
653        $this->start();
654
655        if ($this->hasCallable()) {
656            return $this->loadCallable($application);
657        }
658        if (($this->hasCommand()) && ($application !== null)) {
659            return $this->runCommand($application);
660        }
661        if ($this->hasExec()) {
662            return $this->runExec();
663        }
664
665        return null;
666    }
667
668    /**
669     * Load callable
670     *
671     * @param  ?Application $application
672     * @throws Exception|\Pop\Utils\Exception|\ReflectionException
673     * @return mixed
674     */
675    protected function loadCallable(?Application $application = null): mixed
676    {
677        if ($this->callable === null) {
678            throw new Exception('Error: The callable for this job was not set.');
679        }
680
681        if ($application !== null) {
682            if ($this->callable->hasParameters()) {
683                $parameters = $this->callable->getParameters();
684                array_unshift($parameters, $application);
685                $this->callable->setParameters($parameters);
686            } else {
687                $this->callable->addNamedParameter('application', $application);
688            }
689        }
690
691        $this->results = $this->callable->call();
692        return $this->results;
693    }
694
695    /**
696     * Run application command
697     *
698     * @param  Application $application
699     * @return mixed
700     */
701    protected function runCommand(Application $application): mixed
702    {
703        if (array_key_exists($this->command, $application->router()->getRouteMatch()->getRoutes())) {
704            ob_start();
705            $application->run(true, $this->command);
706            $output = ob_get_clean();
707
708            $this->results = array_filter(explode(PHP_EOL, $output));
709            return $this->results;
710        }
711
712        return false;
713    }
714
715    /**
716     * Run CLI executable command
717     *
718     * @return mixed
719     */
720    protected function runExec(): mixed
721    {
722        $output = [];
723        exec($this->exec, $output);
724        $this->results = $output;
725        return $this->results;
726    }
727
728    /**
729     * Sleep magic method
730     *
731     * @return array
732     */
733    public function __sleep(): array
734    {
735        if (!empty($this->callable) && ($this->callable->getCallable() instanceof \Closure)) {
736            $serializedClosure       = new SerializableClosure($this->callable->getCallable());
737            $this->serializedClosure = serialize($serializedClosure);
738            if ($this->callable->hasParameters()) {
739                $this->serializedParameters = $this->callable->getParameters();
740            }
741            $this->callable = null;
742        }
743
744        return array_keys(get_object_vars($this));
745    }
746
747    /**
748     * Wakeup magic method
749     *
750     * @return void
751     */
752    public function __wakeup(): void
753    {
754        if (!empty($this->serializedClosure)) {
755            $serializedClosure          = unserialize($this->serializedClosure);
756            $callable                   = $serializedClosure->getClosure();
757            $this->callable             = new CallableObject($callable, $this->serializedParameters);
758            $this->serializedClosure    = null;
759            $this->serializedParameters = null;
760        }
761    }
762
763}