Completed
Push — master ( e9000a...3a63a2 )
by Damian
03:43
created

FilesystemQueue::peek()   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
eloc 4
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Initx\Driver;
4
5
use Initx\Envelope;
6
use Initx\Exception\IllegalStateException;
7
use Initx\Exception\NoSuchElementException;
8
use Initx\Queue;
9
use JMS\Serializer\SerializerBuilder;
10
use JMS\Serializer\SerializerInterface;
11
use Throwable;
12
13
final class FilesystemQueue implements Queue
14
{
15
    use HasFallbackSerializer;
16
17
    /**
18
     * @var string
19
     */
20
    private $path;
21
22
    /**
23
     * @var SerializerInterface
24
     */
25
    private $serializer;
26
27 11
    public function __construct(string $path, SerializerInterface $serializer = null)
28
    {
29 11
        $this->serializer = $serializer;
30 11
        $this->path = $path;
31 11
        $this->fallbackSerializer();
32 11
    }
33
34
    /**
35
     * Inserts an element if possible, otherwise throwing exception.
36
     *
37
     * @param Envelope $envelope
38
     * @return void
39
     * @throws IllegalStateException
40
     */
41 5
    public function add(Envelope $envelope): void
42
    {
43 5
        if (!$this->offer($envelope)) {
44 1
            throw IllegalStateException::create("Could not write to {$this->path}");
45
        }
46 4
    }
47
48
    /**
49
     * Inserts an element if possible.
50
     *
51
     * @param Envelope $envelope
52
     * @return void
53
     */
54 7
    public function offer(Envelope $envelope): bool
55
    {
56 7
        $content = $this->serializer->serialize($envelope, 'json').PHP_EOL;
57
        try {
58 7
            $result = (bool)file_put_contents(
59 7
                $this->path,
60 7
                $content,
61 7
                FILE_APPEND
62
            );
63 2
        } catch (Throwable $exception) {
64 2
            $result = false;
65
        }
66
67 7
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type boolean which is incompatible with the documented return type void.
Loading history...
68
    }
69
70
    /**
71
     * Remove and return head of queue, otherwise throwing exception.
72
     *
73
     * @return Envelope
74
     * @throws NoSuchElementException | IllegalStateException
75
     */
76 3
    public function remove(): Envelope
77
    {
78 3
        if (!$envelope = $this->poll()) {
79 1
            throw new NoSuchElementException('Queue empty');
80
        }
81
82 1
        return $envelope;
83
    }
84
85 3
    private function removeFirstLine(): ?string
86
    {
87 3
        if (!file_exists($this->path)) {
88 1
            throw new IllegalStateException("File $this->path not exists");
89
        }
90 2
        $firstLine = null;
91 2
        if ($handle = fopen($this->path, 'cb+')) {
92 2
            if (!flock($handle, LOCK_EX)) {
93
                fclose($handle);
94
            }
95 2
            $offset = 0;
96 2
            $len = filesize($this->path);
97 2
            while (($line = fgets($handle, 4096)) !== false) {
98 1
                if (!$firstLine) {
99 1
                    $firstLine = $line;
100 1
                    $offset = strlen($firstLine);
101 1
                    continue;
102
                }
103 1
                $pos = ftell($handle);
104 1
                fseek($handle, $pos - strlen($line) - $offset);
105 1
                fwrite($handle, $line);
106 1
                fseek($handle, $pos);
107
            }
108 2
            fflush($handle);
109 2
            ftruncate($handle, $len - $offset);
110 2
            flock($handle, LOCK_UN);
111 2
            fclose($handle);
112
        }
113
114 2
        return $firstLine;
115
    }
116
117 4
    private function readFirstLine(): ?string
118
    {
119 4
        if (!file_exists($this->path)) {
120
            throw new IllegalStateException("File $this->path not exists");
121
        }
122
123 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

123
        $firstLine = fgets(/** @scrutinizer ignore-type */ fopen($this->path, 'rb'));
Loading history...
124
125 4
        return $firstLine ?: null;
126
    }
127
128
    /**
129
     * Remove and return head of queue, otherwise returning null.
130
     *
131
     * @return Envelope | null
132
     * @throws IllegalStateException
133
     */
134 3
    public function poll(): ?Envelope
135
    {
136 3
        $firstLine = $this->removeFirstLine();
137
138 2
        if (!$firstLine) {
139 1
            return null;
140
        }
141
142 1
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
143
    }
144
145
    /**
146
     * Return but do not remove head of queue, otherwise throwing exception.
147
     *
148
     * @return Envelope
149
     * @throws NoSuchElementException | IllegalStateException
150
     */
151 2
    public function element(): Envelope
152
    {
153 2
        if (!$envelope = $this->peek()) {
154 1
            throw new NoSuchElementException('Queue empty');
155
        }
156
157 1
        return $envelope;
158
    }
159
160
    /**
161
     * Return but do not remove head of queue, otherwise returning null.
162
     *
163
     * @return Envelope | null
164
     * @throws IllegalStateException
165
     */
166 4
    public function peek(): ?Envelope
167
    {
168 4
        $firstLine = $this->readFirstLine();
169
170 4
        if (!$firstLine) {
171 2
            return null;
172
        }
173
174 2
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
175
    }
176
}
177