Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.50% covered (success)
96.50%
193 / 200
81.48% covered (success)
81.48%
22 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
96.50% covered (success)
96.50%
193 / 200
81.48% covered (success)
81.48%
22 / 27
119
0.00% covered (danger)
0.00%
0 / 1
 __construct
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 hasJob
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getJob
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 updateJob
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
9.02
 hasJobs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getJobs
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
8
 hasCompletedJob
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 hasCompletedJobs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getCompletedJob
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 getCompletedJobs
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 hasFailedJob
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getFailedJob
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 hasFailedJobs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getFailedJobs
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 push
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 failed
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 pop
75.00% covered (success)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 clear
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
7
 clearFailed
80.00% covered (success)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
5.20
 flush
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 flushFailed
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 flushAll
83.33% covered (success)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 folder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initFolders
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 clearFolder
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 removeQueueFolder
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getFiles
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
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-2023 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\Adapter;
15
16use Pop\Queue\Queue;
17use Pop\Queue\Processor\Jobs;
18
19/**
20 * File queue adapter class
21 *
22 * @category   Pop
23 * @package    Pop\Queue
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    1.2.0
28 */
29class File extends AbstractAdapter
30{
31
32    /**
33     * Queue folder
34     * @var string
35     */
36    protected $folder = null;
37
38    /**
39     * Constructor
40     *
41     * Instantiate the file queue object
42     *
43     * @param string $folder
44     */
45    public function __construct($folder)
46    {
47        if (!file_exists($folder)) {
48            throw new Exception("Error: The queue folder '" . $folder . "' does not exist.");
49        }
50        if (!is_writable($folder)) {
51            throw new Exception("Error: The queue folder '" . $folder . "' is not writable.");
52        }
53
54        $this->folder = $folder;
55    }
56
57    /**
58     * Check if queue stack has job
59     *
60     * @param  mixed $jobId
61     * @return boolean
62     */
63    public function hasJob($jobId)
64    {
65        $queueFolders = $this->getFiles($this->folder);
66        $hasJob       = false;
67
68        foreach ($queueFolders as $queueFolder) {
69            if (file_exists($this->folder . '/' . $queueFolder . '/' . $jobId)) {
70                $hasJob = true;
71                break;
72            }
73        }
74
75        return $hasJob;
76    }
77
78    /**
79     * Get job from queue stack by job ID
80     *
81     * @param  mixed   $jobId
82     * @param  boolean $unserialize
83     * @return array|boolean
84     */
85    public function getJob($jobId, $unserialize = true)
86    {
87        $queueFolders = $this->getFiles($this->folder);
88        $job          = false;
89
90        foreach ($queueFolders as $queueFolder) {
91            if (file_exists($this->folder . '/' . $queueFolder . '/' . $jobId)) {
92                $job = unserialize(file_get_contents($this->folder . '/' . $queueFolder . '/' . $jobId));
93                if (file_exists($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload')) {
94                    $jobPayload = file_get_contents($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload');
95                    if ($jobPayload !== false) {
96                        $job['payload'] = ($unserialize) ? unserialize($jobPayload) : $jobPayload;
97                    }
98                }
99                break;
100            }
101        }
102
103        return $job;
104    }
105
106    /**
107     * Update job from queue stack by job ID
108     *
109     * @param  mixed $jobId
110     * @param  mixed $completed
111     * @param  mixed $increment
112     * @return void
113     */
114    public function updateJob($jobId, $completed = false, $increment = false)
115    {
116        $jobData = $this->getJob($jobId);
117
118        if ($jobData !== false) {
119            $queueName = $jobData['queue'];
120            if (isset($jobData['payload'])) {
121                unset($jobData['payload']);
122            }
123            if ($increment !== false) {
124                if (($increment === true) && isset($jobData['attempts'])) {
125                    $jobData['attempts']++;
126                } else {
127                    $jobData['attempts'] = (int)$increment;
128                }
129            }
130            if ($completed !== false) {
131                $jobData['completed'] = ($completed === true) ? date('Y-m-d H:i:s') : $completed;
132
133                file_put_contents($this->folder . '/' . $queueName . '/completed/' . $jobId, serialize($jobData));
134                if (file_exists($this->folder . '/' . $queueName . '/' . $jobId)) {
135                    unlink($this->folder . '/' . $queueName . '/' . $jobId);
136                }
137            } else {
138                file_put_contents($this->folder . '/' . $queueName . '/' . $jobId, serialize($jobData));
139            }
140        }
141    }
142
143    /**
144     * Check if queue has jobs
145     *
146     * @param  mixed $queue
147     * @return boolean
148     */
149    public function hasJobs($queue)
150    {
151        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
152        return (count($this->getFiles($this->folder . '/' . $queueName)) > 0);
153    }
154
155    /**
156     * Get queue jobs
157     *
158     * @param  mixed   $queue
159     * @param  boolean $unserialize
160     * @return array
161     */
162    public function getJobs($queue, $unserialize = true)
163    {
164        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
165        $queueJobs = $this->getFiles($this->folder . '/' . $queueName);
166        $jobs      = [];
167
168        if (count($queueJobs) > 0) {
169            foreach ($queueJobs as $jobId) {
170                if ((strpos($jobId, '-payload') === false) && file_exists($this->folder . '/' . $queueName . '/' . $jobId)) {
171                    $job = unserialize(file_get_contents($this->folder . '/' . $queueName . '/' . $jobId));
172                    if (file_exists($this->folder . '/' . $queueName . '/' . $jobId . '-payload')) {
173                        $jobPayload = file_get_contents($this->folder . '/' . $queueName . '/' . $jobId . '-payload');
174                        if ($unserialize) {
175                            $jobPayload = unserialize($jobPayload);
176                        }
177
178                        $job['payload'] = $jobPayload;
179                    }
180
181                    $jobs[$jobId] = $job;
182                }
183            }
184        }
185
186        return $jobs;
187    }
188
189    /**
190     * Check if queue stack has completed job
191     *
192     * @param  mixed $jobId
193     * @return boolean
194     */
195    public function hasCompletedJob($jobId)
196    {
197        $queueFolders = $this->getFiles($this->folder);
198        $hasJob       = false;
199
200        foreach ($queueFolders as $queueFolder) {
201            if (file_exists($this->folder . '/' . $queueFolder . '/completed/' . $jobId)) {
202                $hasJob = true;
203                break;
204            }
205        }
206
207        return $hasJob;
208    }
209
210    /**
211     * Check if queue has completed jobs
212     *
213     * @param  mixed $queue
214     * @return boolean
215     */
216    public function hasCompletedJobs($queue)
217    {
218        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
219        return (count($this->getFiles($this->folder . '/' . $queueName . '/completed')) > 0);
220    }
221
222    /**
223     * Get queue completed job
224     *
225     * @param  mixed   $jobId
226     * @param  boolean $unserialize
227     * @return array
228     */
229    public function getCompletedJob($jobId, $unserialize = true)
230    {
231        $queueFolders = $this->getFiles($this->folder);
232        $job          = false;
233
234        foreach ($queueFolders as $queueFolder) {
235            if (file_exists($this->folder . '/' . $queueFolder . '/completed/' . $jobId)) {
236                $job = unserialize(file_get_contents($this->folder . '/' . $queueFolder . '/completed/' . $jobId));
237                if (file_exists($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload')) {
238                    $jobPayload = file_get_contents($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload');
239                    if ($jobPayload !== false) {
240                        $job['payload'] = ($unserialize) ? unserialize($jobPayload) : $jobPayload;
241                    }
242                }
243                break;
244            }
245        }
246
247        return $job;
248    }
249
250    /**
251     * Get queue completed jobs
252     *
253     * @param  mixed   $queue
254     * @param  boolean $unserialize
255     * @return array
256     */
257    public function getCompletedJobs($queue, $unserialize = true)
258    {
259        $queueName          = ($queue instanceof Queue) ? $queue->getName() : $queue;
260        $queueCompletedJobs = $this->getFiles($this->folder . '/' . $queueName . '/completed');
261        $completedJobs      = [];
262
263        if (count($queueCompletedJobs) > 0) {
264            foreach ($queueCompletedJobs as $jobId) {
265                if (file_exists($this->folder . '/' . $queueName . '/completed/' . $jobId)) {
266                    $completedJob = unserialize(file_get_contents($this->folder . '/' . $queueName . '/completed/' . $jobId));
267                    if (file_exists($this->folder . '/' . $queueName . '/' . $jobId . '-payload')) {
268                        $jobPayload = file_get_contents($this->folder . '/' . $queueName . '/' . $jobId . '-payload');
269                        if ($unserialize) {
270                            $jobPayload = unserialize($jobPayload);
271                        }
272
273                        $completedJob['payload'] = $jobPayload;
274                    }
275
276                    $completedJobs[$jobId] = $completedJob;
277                }
278            }
279        }
280
281        return $completedJobs;
282    }
283
284    /**
285     * Check if queue stack has failed job
286     *
287     * @param  mixed $jobId
288     * @return boolean
289     */
290    public function hasFailedJob($jobId)
291    {
292        $queueFolders = $this->getFiles($this->folder);
293        $hasJob       = false;
294
295        foreach ($queueFolders as $queueFolder) {
296            if (file_exists($this->folder . '/' . $queueFolder . '/failed/' . $jobId)) {
297                $hasJob = true;
298                break;
299            }
300        }
301
302        return $hasJob;
303    }
304
305    /**
306     * Get failed job from queue stack by job ID
307     *
308     * @param  mixed   $jobId
309     * @param  boolean $unserialize
310     * @return array
311     */
312    public function getFailedJob($jobId, $unserialize = true)
313    {
314        $queueFolders = $this->getFiles($this->folder);
315        $job          = false;
316
317        foreach ($queueFolders as $queueFolder) {
318            if (file_exists($this->folder . '/' . $queueFolder . '/failed/' . $jobId)) {
319                $job = unserialize(file_get_contents($this->folder . '/' . $queueFolder . '/failed/' . $jobId));
320                if (file_exists($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload')) {
321                    $jobPayload = file_get_contents($this->folder . '/' . $queueFolder . '/' . $jobId . '-payload');
322                    if ($jobPayload !== false) {
323                        $job['payload'] = ($unserialize) ? unserialize($jobPayload) : $jobPayload;
324                    }
325                }
326                break;
327            }
328        }
329
330        return $job;
331    }
332
333    /**
334     * Check if queue adapter has failed jobs
335     *
336     * @param  mixed $queue
337     * @return boolean
338     */
339    public function hasFailedJobs($queue)
340    {
341        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
342        return (count($this->getFiles($this->folder . '/' . $queueName . '/failed')) > 0);
343    }
344
345    /**
346     * Get queue jobs
347     *
348     * @param  mixed   $queue
349     * @param  boolean $unserialize
350     * @return array
351     */
352    public function getFailedJobs($queue, $unserialize = true)
353    {
354        $queueName       = ($queue instanceof Queue) ? $queue->getName() : $queue;
355        $queueFailedJobs = $this->getFiles($this->folder . '/' . $queueName . '/failed');
356        $failedJobs      = [];
357
358        if (count($queueFailedJobs) > 0) {
359            foreach ($queueFailedJobs as $jobId) {
360                if (file_exists($this->folder . '/' . $queueName . '/failed/' . $jobId)) {
361                    $failedJob = unserialize(file_get_contents($this->folder . '/' . $queueName . '/failed/' . $jobId));
362                    if (file_exists($this->folder . '/' . $queueName . '/' . $jobId . '-payload')) {
363                        $jobPayload = file_get_contents($this->folder . '/' . $queueName . '/' . $jobId . '-payload');
364                        if ($unserialize) {
365                            $jobPayload = unserialize($jobPayload);
366                        }
367
368                        $failedJob['payload'] = $jobPayload;
369                    }
370
371                    $failedJobs[$jobId] = $failedJob;
372                }
373            }
374        }
375
376        return $failedJobs;
377    }
378
379    /**
380     * Push job onto queue stack
381     *
382     * @param  mixed $queue
383     * @param  mixed $job
384     * @param  mixed $priority
385     * @return string
386     */
387    public function push($queue, $job, $priority = null)
388    {
389        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
390        $jobId     = null;
391
392        if ($job instanceof Jobs\Schedule) {
393            $jobId = ($job->getJob()->hasJobId()) ? $job->getJob()->getJobId() :$job->getJob()->generateJobId();
394        } else if ($job instanceof Jobs\Job) {
395            $jobId = ($job->hasJobId()) ? $job->getJobId() : $job->generateJobId();
396        }
397
398        $this->initFolders($queueName);
399
400        $jobData = [
401            'job_id'    => $jobId,
402            'queue'     => $queueName,
403            'priority'  => $priority,
404            'attempts'  => 0,
405            'completed' => null
406        ];
407
408        file_put_contents($this->folder . '/' . $queueName . '/' . $jobId, serialize($jobData));
409        file_put_contents($this->folder . '/' . $queueName . '/' . $jobId . '-payload', serialize(clone $job));
410
411        return $jobId;
412    }
413
414    /**
415     * Move failed job to failed queue stack
416     *
417     * @param  mixed      $queue
418     * @param  mixed      $jobId
419     * @param  \Exception $exception
420     * @return void
421     */
422    public function failed($queue, $jobId, \Exception $exception = null)
423    {
424        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
425
426        $this->initFolders($queueName);
427
428        $failedJobData = [
429            'job_id'    => $jobId,
430            'queue'     => $queueName,
431            'exception' => (null !== $exception) ? $exception->getMessage() : null,
432            'failed'    => date('Y-m-d H:i:s')
433        ];
434
435        file_put_contents($this->folder . '/' . $queueName . '/failed/' . $jobId, serialize($failedJobData));
436
437        if (!empty($jobId)) {
438            $this->pop($jobId, false);
439        }
440    }
441
442    /**
443     * Pop job off of queue stack
444     *
445     * @param  mixed   $jobId
446     * @param  boolean $payload
447     * @return void
448     */
449    public function pop($jobId, $payload = true)
450    {
451        $jobData   = $this->getJob($jobId);
452        $queueName = $jobData['queue'];
453
454        if (file_exists($this->folder . '/' . $queueName . '/' . $jobId)) {
455            unlink($this->folder . '/' . $queueName . '/' . $jobId);
456        }
457        if (($payload) && file_exists($this->folder . '/' . $queueName . '/' . $jobId . '-payload')) {
458            unlink($this->folder . '/' . $queueName . '/' . $jobId . '-payload');
459        }
460        if (file_exists($this->folder . '/' . $queueName . '/completed/' . $jobId)) {
461            unlink($this->folder . '/' . $queueName . '/completed/' . $jobId);
462        }
463    }
464
465    /**
466     * Clear jobs off of the queue stack
467     *
468     * @param  mixed   $queue
469     * @param  boolean $all
470     * @return void
471     */
472    public function clear($queue, $all = false)
473    {
474        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
475
476        if (file_exists($this->folder . '/' . $queueName)) {
477            $this->clearFolder($this->folder . '/' . $queueName);
478        }
479        if (($all) && file_exists($this->folder . '/' . $queueName . '/completed')) {
480            $this->clearFolder($this->folder . '/' . $queueName . '/completed');
481        }
482
483        if (is_dir($this->folder . '/' . $queueName) && count(scandir($this->folder . '/' . $queueName)) == 2) {
484            rmdir($this->folder . '/' . $queueName);
485        }
486    }
487
488    /**
489     * Clear failed jobs off of the queue stack
490     *
491     * @param  mixed $queue
492     * @return void
493     */
494    public function clearFailed($queue)
495    {
496        $queueName = ($queue instanceof Queue) ? $queue->getName() : $queue;
497
498        if (file_exists($this->folder . '/' . $queueName . '/failed')) {
499            $this->clearFolder($this->folder . '/' . $queueName . '/failed');
500        }
501
502        if (is_dir($this->folder . '/' . $queueName) && count(scandir($this->folder . '/' . $queueName)) == 2) {
503            rmdir($this->folder . '/' . $queueName);
504        }
505    }
506
507    /**
508     * Flush all jobs off of the queue stack
509     *
510     * @param  boolean $all
511     * @return void
512     */
513    public function flush($all = false)
514    {
515        $queueFolders = $this->getFiles($this->folder);
516
517        foreach ($queueFolders as $queueFolder) {
518            $this->clear($queueFolder, $all);
519        }
520    }
521
522    /**
523     * Flush all failed jobs off of the queue stack
524     *
525     * @return void
526     */
527    public function flushFailed()
528    {
529        $queueFolders = $this->getFiles($this->folder);
530
531        foreach ($queueFolders as $queueFolder) {
532            $this->clearFailed($queueFolder);
533        }
534    }
535
536    /**
537     * Flush all pop queue items
538     *
539     * @return void
540     */
541    public function flushAll()
542    {
543        $this->flushFailed();
544        $this->flush(true);
545
546        $queueFolders = $this->getFiles($this->folder);
547
548        foreach ($queueFolders as $queueFolder) {
549            if (is_dir($this->folder . '/' . $queueFolder) && count(scandir($this->folder . '/' . $queueFolder)) == 2) {
550                rmdir($this->folder . '/' . $queueFolder);
551            }
552        }
553    }
554
555    /**
556     * Get the queue folder
557     *
558     * @return string
559     */
560    public function folder()
561    {
562        return $this->folder;
563    }
564
565    /**
566     * Initialize queue folders
567     *
568     * @param  string $queueName
569     * @return File
570     */
571    public function initFolders($queueName)
572    {
573        if (!file_exists($this->folder . '/' . $queueName)) {
574            mkdir($this->folder . '/' . $queueName, 0777);
575        }
576        if (!file_exists($this->folder . '/' . $queueName . '/completed')) {
577            mkdir($this->folder . '/' . $queueName . '/completed', 0777);
578        }
579        if (!file_exists($this->folder . '/' . $queueName . '/failed')) {
580            mkdir($this->folder . '/' . $queueName . '/failed', 0777);
581        }
582
583        return $this;
584    }
585
586    /**
587     * Clear queue folder
588     *
589     * @param  string  $folder
590     * @throws Exception
591     * @return File
592     */
593    public function clearFolder($folder)
594    {
595        $files = $this->getFiles($folder);
596
597        foreach ($files as $file) {
598            if (file_exists($folder . '/' . $file)) {
599                unlink($folder . '/' . $file);
600            }
601        }
602
603        return $this;
604    }
605
606    /**
607     * Remove queue folder
608     *
609     * @param  string $queueName
610     * @return File
611     */
612    public function removeQueueFolder($queueName)
613    {
614        $this->clearFolder($this->folder . '/' . $queueName . '/completed');
615        $this->clearFolder($this->folder . '/' . $queueName . '/failed');
616        $this->clearFolder($this->folder . '/' . $queueName);
617
618
619        if (file_exists($this->folder . '/' . $queueName . '/completed')) {
620            rmdir($this->folder . '/' . $queueName . '/completed');
621        }
622        if (file_exists($this->folder . '/' . $queueName . '/failed')) {
623            rmdir($this->folder . '/' . $queueName . '/failed');
624        }
625        if (file_exists($this->folder . '/' . $queueName)) {
626            rmdir($this->folder . '/' . $queueName);
627        }
628
629        return $this;
630    }
631
632    /**
633     * Get files from folder
634     *
635     * @param  string $folder
636     * @return array
637     */
638    public function getFiles($folder)
639    {
640        if (is_dir($folder)) {
641            return array_values(array_filter(scandir($folder), function($value){
642                return (($value != '.') && ($value != '..') && ($value != 'completed') && ($value != 'failed'));
643            }));
644        } else {
645            return [];
646        }
647    }
648
649}