Completed
Push — master ( 4b9f01...160a67 )
by Damian
02:21
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\SerializerBuilder;
10
use JMS\Serializer\SerializerInterface;
11
use Throwable;
12
13
final class FilesystemQueue implements Queue
14
{
15
    /**
16
     * @var string
17
     */
18
    private $path;
19
20
    /**
21
     * @var SerializerInterface
22
     */
23
    private $serializer;
24
25 7
    public function __construct(string $path, SerializerInterface $serializer = null)
26
    {
27 7
        if (!$serializer) {
28 7
            $serializer = SerializerBuilder::create()
29 7
                ->addMetadataDir(__DIR__ . '/../../config/jms', 'Initx')
30 7
                ->build();
31
        }
32 7
        $this->serializer = $serializer;
33 7
        $this->path = $path;
34 7
    }
35
36
    /**
37
     * Inserts an element if possible, otherwise throwing exception.
38
     *
39
     * @param Envelope $envelope
40
     * @return void
41
     * @throws IllegalStateException
42
     */
43 3
    public function add(Envelope $envelope): void
44
    {
45 3
        if (!$this->write($envelope)) {
46 1
            throw IllegalStateException::create("Could not write to {$this->path}");
47
        }
48 2
    }
49
50
    /**
51
     * Inserts an element if possible.
52
     *
53
     * @param Envelope $envelope
54
     * @return void
55
     */
56 2
    public function offer(Envelope $envelope): void
57
    {
58 2
        $this->write($envelope);
59 2
    }
60
61 5
    private function write(Envelope $envelope): bool
62
    {
63 5
        $content = $this->serializer->serialize($envelope, 'json');
64
        try {
65 5
            $result = (bool)file_put_contents(
66 5
                $this->path,
67 5
                $content,
68 5
                FILE_APPEND
69
            );
70 2
        } catch (Throwable $exception) {
71 2
            $result = false;
72
        }
73
74 5
        return $result;
75
    }
76
77
    /**
78
     * Remove and return head of queue, otherwise throwing exception.
79
     *
80
     * @return Envelope
81
     * @throws NoSuchElementException | IllegalStateException
82
     */
83 3
    public function remove(): Envelope
84
    {
85 3
        if (!$envelope = $this->poll()) {
86 1
            throw new NoSuchElementException('Queue empty');
87
        }
88
89 1
        return $envelope;
90
    }
91
92 3
    private function removeFirstLine(): ?string
93
    {
94 3
        if (!file_exists($this->path)) {
95 1
            throw new IllegalStateException("File $this->path not exists");
96
        }
97 2
        $firstLine = null;
98 2
        if ($handle = fopen($this->path, 'cb+')) {
99 2
            if (!flock($handle, LOCK_EX)) {
100
                fclose($handle);
101
            }
102 2
            $offset = 0;
103 2
            $len = filesize($this->path);
104 2
            while (($line = fgets($handle, 4096)) !== false) {
105 1
                if (!$firstLine) {
106 1
                    $firstLine = $line;
107 1
                    $offset = strlen($firstLine);
108 1
                    continue;
109
                }
110
                $pos = ftell($handle);
111
                fseek($handle, $pos - strlen($line) - $offset);
112
                fwrite($handle, $line);
113
                fseek($handle, $pos);
114
            }
115 2
            fflush($handle);
116 2
            ftruncate($handle, $len - $offset);
117 2
            flock($handle, LOCK_UN);
118 2
            fclose($handle);
119
        }
120
121 2
        return $firstLine;
122
    }
123
124
    /**
125
     * Remove and return head of queue, otherwise returning null.
126
     *
127
     * @return Envelope | null
128
     * @throws IllegalStateException
129
     */
130 3
    public function poll(): ?Envelope
131
    {
132 3
        $firstLine = $this->removeFirstLine();
133
134 2
        if (!$firstLine) {
135 1
            return null;
136
        }
137
138 1
        return $this->serializer->deserialize($firstLine, Envelope::class, 'json');
139
    }
140
141
    /**
142
     * Return but do not remove head of queue, otherwise throwing exception.
143
     *
144
     * @return Envelope
145
     * @throws NoSuchElementException
146
     */
147
    public function element(): Envelope
148
    {
149
        // TODO: Implement element() method.
150
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Initx\Envelope. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
151
152
    /**
153
     * Return but do not remove head of queue, otherwise returning null.
154
     *
155
     * @return Envelope | null
156
     */
157
    public function peek(): ?Envelope
158
    {
159
        // TODO: Implement peek() method.
160
    }
161
}
162