Completed
Push — master ( 6d1294...e04fa8 )
by Damian
03:48
created

FilesystemQueue   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Test Coverage

Coverage 96.97%

Importance

Changes 0
Metric Value
wmc 22
eloc 60
dl 0
loc 131
c 0
b 0
f 0
ccs 64
cts 66
cp 0.9697
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A add() 0 4 2
A __construct() 0 5 1
A poll() 0 9 2
A peek() 0 9 2
A offer() 0 16 2
B removeFirstLine() 0 30 6
A element() 0 9 2
A readFirstLine() 0 9 3
A remove() 0 9 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
use Throwable;
11
12
final class FilesystemQueue implements Queue
13
{
14
    use HasFallbackSerializer;
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 = $serializer;
29 11
        $this->path = $path;
30 11
        $this->fallbackSerializer();
31 11
    }
32
33 5
    public function add(Envelope $envelope): void
34
    {
35 5
        if (!$this->offer($envelope)) {
36 1
            throw new IllegalStateException("Could not write to file: {$this->path}");
37
        }
38 4
    }
39
40 7
    public function offer(Envelope $envelope): bool
41
    {
42 7
        $content = $this->serializer->serialize($envelope, 'json').PHP_EOL;
43
        try {
44 7
            $result = (bool)file_put_contents(
45 7
                $this->path,
46 7
                $content,
47 7
                FILE_APPEND
48
            );
49 2
        } catch (Throwable $exception) {
50
            // There is a contract need to not throw exceptions from offer(e) method,
51
            // but return false on failure.
52 2
            $result = false;
53
        }
54
55 7
        return $result;
56
    }
57
58 3
    public function remove(): Envelope
59
    {
60 3
        $envelope = $this->poll();
61
62 2
        if (!$envelope) {
63 1
            throw new NoSuchElementException();
64
        }
65
66 1
        return $envelope;
67
    }
68
69 3
    public function poll(): ?Envelope
70
    {
71 3
        $firstLine = $this->removeFirstLine();
72
73 2
        if (!$firstLine) {
74 1
            return null;
75
        }
76
77 1
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
78
    }
79
80 2
    public function element(): Envelope
81
    {
82 2
        $envelope = $this->peek();
83
84 2
        if (!$envelope) {
85 1
            throw new NoSuchElementException();
86
        }
87
88 1
        return $envelope;
89
    }
90
91 4
    public function peek(): ?Envelope
92
    {
93 4
        $firstLine = $this->readFirstLine();
94
95 4
        if (!$firstLine) {
96 2
            return null;
97
        }
98
99 2
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
100
    }
101
102 3
    private function removeFirstLine(): ?string
103
    {
104 3
        if (!file_exists($this->path)) {
105 1
            throw new IllegalStateException("File $this->path not exists");
106
        }
107 2
        $firstLine = null;
108 2
        if ($handle = fopen($this->path, 'cb+')) {
109 2
            if (!flock($handle, LOCK_EX)) {
110
                fclose($handle);
111
            }
112 2
            $offset = 0;
113 2
            $len = filesize($this->path);
114 2
            while (($line = fgets($handle, 4096)) !== false) {
115 1
                if (!$firstLine) {
116 1
                    $firstLine = $line;
117 1
                    $offset = strlen($firstLine);
118 1
                    continue;
119
                }
120 1
                $pos = ftell($handle);
121 1
                fseek($handle, $pos - strlen($line) - $offset);
122 1
                fwrite($handle, $line);
123 1
                fseek($handle, $pos);
124
            }
125 2
            fflush($handle);
126 2
            ftruncate($handle, $len - $offset);
127 2
            flock($handle, LOCK_UN);
128 2
            fclose($handle);
129
        }
130
131 2
        return $firstLine;
132
    }
133
134 4
    private function readFirstLine(): ?string
135
    {
136 4
        if (!file_exists($this->path)) {
137
            throw new IllegalStateException("File $this->path not exists");
138
        }
139
140 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

140
        $firstLine = fgets(/** @scrutinizer ignore-type */ fopen($this->path, 'rb'));
Loading history...
141
142 4
        return $firstLine ?: null;
143
    }
144
}
145