Passed
Push — master ( 0d68ae...2d2c36 )
by Damian
04:18
created

FilesystemQueue::poll()   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\SerializerInterface;
10
11
final class FilesystemQueue implements Queue
12
{
13
    use HasFallbackSerializer;
14
15
    /**
16
     * @var string
17
     */
18
    private $path;
19
20
    /**
21
     * @var SerializerInterface
22
     */
23
    private $serializer;
24
25 11
    public function __construct(string $path, ?SerializerInterface $serializer = null)
26
    {
27 11
        $this->serializer = $this->fallbackSerializer($serializer);
28 11
        $this->path = $path;
29 11
    }
30
31 5
    public function add(Envelope $envelope): void
32
    {
33 5
        if (!$this->offer($envelope)) {
34
            $this->throwItIsNotWriteable();
35
        }
36 4
    }
37
38 7
    public function offer(Envelope $envelope): bool
39
    {
40 7
        $content = $this->serializer->serialize($envelope, 'json').PHP_EOL;
41
42 7
        $result = (bool)@file_put_contents(
43 7
            $this->path,
44 7
            $content,
45 7
            FILE_APPEND
46
        );
47
48 7
        if (!$result) {
49 2
            $this->throwItIsNotWriteable();
50
        }
51
52 5
        return true;
53
    }
54
55 3
    public function remove(): Envelope
56
    {
57 3
        $envelope = $this->poll();
58
59 2
        if (!$envelope) {
60 1
            throw new NoSuchElementException();
61
        }
62
63 1
        return $envelope;
64
    }
65
66 3
    public function poll(): ?Envelope
67
    {
68 3
        $firstLine = $this->removeFirstLine();
69
70 2
        if (!$firstLine) {
71 1
            return null;
72
        }
73
74 1
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
75
    }
76
77 2
    public function element(): Envelope
78
    {
79 2
        $envelope = $this->peek();
80
81 2
        if (!$envelope) {
82 1
            throw new NoSuchElementException();
83
        }
84
85 1
        return $envelope;
86
    }
87
88 4
    public function peek(): ?Envelope
89
    {
90 4
        $firstLine = $this->readFirstLine();
91
92 4
        if (!$firstLine) {
93 2
            return null;
94
        }
95
96 2
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
97
    }
98
99 3
    private function removeFirstLine(): ?string
100
    {
101 3
        if (!file_exists($this->path)) {
102 1
            $this->throwItNotExists();
103
        }
104 2
        $firstLine = null;
105 2
        if ($handle = fopen($this->path, 'cb+')) {
106 2
            if (!flock($handle, LOCK_EX)) {
107
                fclose($handle);
108
            }
109 2
            $offset = 0;
110 2
            $len = filesize($this->path);
111 2
            while (($line = fgets($handle, 4096)) !== false) {
112 1
                if (!$firstLine) {
113 1
                    $firstLine = $line;
114 1
                    $offset = strlen($firstLine);
115 1
                    continue;
116
                }
117 1
                $pos = ftell($handle);
118 1
                fseek($handle, $pos - strlen($line) - $offset);
119 1
                fwrite($handle, $line);
120 1
                fseek($handle, $pos);
121
            }
122 2
            fflush($handle);
123 2
            ftruncate($handle, $len - $offset);
124 2
            flock($handle, LOCK_UN);
125 2
            fclose($handle);
126
        }
127
128 2
        return $firstLine;
129
    }
130
131 4
    private function readFirstLine(): ?string
132
    {
133 4
        if (!file_exists($this->path)) {
134
            $this->throwItNotExists();
135
        }
136
137 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

137
        $firstLine = fgets(/** @scrutinizer ignore-type */ fopen($this->path, 'rb'));
Loading history...
138
139 4
        return $firstLine ?: null;
140
    }
141
142 2
    private function throwItIsNotWriteable(): void
143
    {
144 2
        throw new IllegalStateException("Could not write to file: {$this->path}");
145
    }
146
147 1
    private function throwItNotExists(): void
148
    {
149 1
        throw new IllegalStateException("File $this->path not exists");
150
    }
151
}
152