Completed
Branch master (339a46)
by Damian
04:14
created

FilesystemQueue::element()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Initx\Querabilis\Driver;
4
5
use Initx\Querabilis\Envelope;
6
use Initx\Querabilis\Exception\IllegalStateException;
7
use Initx\Querabilis\Exception\NoSuchElementException;
8
use Initx\Querabilis\Queue;
9
use JMS\Serializer\SerializerInterface;
10
11
final class FilesystemQueue implements Queue
12
{
13
    use HasFallbackSerializer;
14
    use HasDefaultRemoveAndElement;
15
16
    /**
17
     * @var string
18
     */
19
    private $path;
20
21
    /**
22
     * @var SerializerInterface
23
     */
24
    private $serializer;
25
26 11
    public function __construct(string $path, ?SerializerInterface $serializer = null)
27
    {
28 11
        $this->serializer = $this->fallbackSerializer($serializer);
29 11
        $this->path = $path;
30 11
    }
31
32 5
    public function add(Envelope $envelope): bool
33
    {
34 5
        if (!$this->offer($envelope)) {
35
            $this->throwItIsNotWriteable();
36
        }
37
38 4
        return true;
39
    }
40
41 7
    public function offer(Envelope $envelope): bool
42
    {
43 7
        $content = $this->serializer->serialize($envelope, 'json').PHP_EOL;
44
45 7
        $result = (bool)@file_put_contents(
46 7
            $this->path,
47 7
            $content,
48 7
            FILE_APPEND
49
        );
50
51 7
        if (!$result) {
52 2
            $this->throwItIsNotWriteable();
53
        }
54
55 5
        return true;
56
    }
57
58 3
    public function poll(): ?Envelope
59
    {
60 3
        $firstLine = $this->removeFirstLine();
61
62 2
        if (!$firstLine) {
63 1
            return null;
64
        }
65
66 1
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
67
    }
68
69 4
    public function peek(): ?Envelope
70
    {
71 4
        $firstLine = $this->readFirstLine();
72
73 4
        if (!$firstLine) {
74 2
            return null;
75
        }
76
77 2
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
78
    }
79
80 3
    private function removeFirstLine(): ?string
81
    {
82 3
        if (!file_exists($this->path)) {
83 1
            $this->throwItNotExists();
84
        }
85 2
        $firstLine = null;
86 2
        if ($handle = fopen($this->path, 'cb+')) {
87 2
            if (!flock($handle, LOCK_EX)) {
88
                fclose($handle);
89
            }
90 2
            $offset = 0;
91 2
            $len = filesize($this->path);
92 2
            while (($line = fgets($handle, 4096)) !== false) {
93 1
                if (!$firstLine) {
94 1
                    $firstLine = $line;
95 1
                    $offset = strlen($firstLine);
96 1
                    continue;
97
                }
98 1
                $pos = ftell($handle);
99 1
                fseek($handle, $pos - strlen($line) - $offset);
100 1
                fwrite($handle, $line);
101 1
                fseek($handle, $pos);
102
            }
103 2
            fflush($handle);
104 2
            ftruncate($handle, $len - $offset);
105 2
            flock($handle, LOCK_UN);
106 2
            fclose($handle);
107
        }
108
109 2
        return $firstLine;
110
    }
111
112 4
    private function readFirstLine(): ?string
113
    {
114 4
        if (!file_exists($this->path)) {
115
            $this->throwItNotExists();
116
        }
117
118 4
        $firstLine = fgets(fopen($this->path, 'rb'));
0 ignored issues
show
Bug introduced by
It seems like fopen($this->path, 'rb') can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
        $firstLine = fgets(/** @scrutinizer ignore-type */ fopen($this->path, 'rb'));
Loading history...
119
120 4
        return $firstLine ?: null;
121
    }
122
123 2
    private function throwItIsNotWriteable(): void
124
    {
125 2
        throw new IllegalStateException("Could not write to file: {$this->path}");
126
    }
127
128 1
    private function throwItNotExists(): void
129
    {
130 1
        throw new IllegalStateException("File $this->path not exists");
131
    }
132
}
133