1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace hanneskod\yaysondb\Engine; |
6
|
|
|
|
7
|
|
|
use hanneskod\yaysondb\Exception\SourceModifiedException; |
8
|
|
|
use League\Flysystem\FilesystemInterface; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Engine based on a flysystem |
12
|
|
|
*/ |
13
|
|
|
class FlysystemEngine implements EngineInterface |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* @var string Name of source file |
17
|
|
|
*/ |
18
|
|
|
private $fname; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var FilesystemInterface Filesystem where source is found |
22
|
|
|
*/ |
23
|
|
|
private $fs; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var DecoderInterface Decoder used to encode and deconde content |
27
|
|
|
*/ |
28
|
|
|
private $decoder; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array Loaded documents |
32
|
|
|
*/ |
33
|
|
|
private $docs; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var string Hash of source at last reset |
37
|
|
|
*/ |
38
|
|
|
private $hash; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var bool Flag if there are un-commited changes |
42
|
|
|
*/ |
43
|
|
|
private $inTransaction; |
44
|
|
|
|
45
|
|
|
public function __construct(string $fname, FilesystemInterface $fs, DecoderInterface $decoder = null) |
46
|
|
|
{ |
47
|
|
|
$this->fname = $fname; |
48
|
|
|
$this->fs = $fs; |
49
|
|
|
$this->decoder = $decoder ?: $this->guessDecoder(); |
50
|
|
|
$this->reset(); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function getId(): string |
54
|
|
|
{ |
55
|
|
|
return $this->fname; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
public function reset() |
59
|
|
|
{ |
60
|
|
|
$this->inTransaction = false; |
61
|
|
|
$raw = $this->fs->read($this->fname); |
62
|
|
|
$this->hash = md5($raw); |
63
|
|
|
$this->docs = $this->decoder->decode($raw); |
|
|
|
|
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
public function getIterator(): \Generator |
67
|
|
|
{ |
68
|
|
|
foreach ($this->docs as $id => $doc) { |
69
|
|
|
yield $id => $doc; |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public function has(string $id): bool |
74
|
|
|
{ |
75
|
|
|
return isset($this->docs[$id]); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function read(string $id): array |
79
|
|
|
{ |
80
|
|
|
if ($this->has($id)) { |
81
|
|
|
return (array)$this->docs[$id]; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return []; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
public function write(string $id, array $doc): string |
88
|
|
|
{ |
89
|
|
|
$this->inTransaction = true; |
90
|
|
|
|
91
|
|
|
if ('' !== $id) { |
92
|
|
|
$this->docs[$id] = $doc; |
93
|
|
|
return $id; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$this->docs[] = $doc; |
97
|
|
|
end($this->docs); |
98
|
|
|
|
99
|
|
|
return (string)key($this->docs); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function delete(string $id): bool |
103
|
|
|
{ |
104
|
|
|
if ($this->has($id)) { |
105
|
|
|
$this->inTransaction = true; |
106
|
|
|
unset($this->docs[$id]); |
107
|
|
|
|
108
|
|
|
return true; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
return false; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function clear() |
115
|
|
|
{ |
116
|
|
|
if ($this->docs) { |
|
|
|
|
117
|
|
|
$this->inTransaction = true; |
118
|
|
|
$this->docs = []; |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
public function inTransaction(): bool |
123
|
|
|
{ |
124
|
|
|
return $this->inTransaction; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @throws SourceModifiedException If source is out of date |
129
|
|
|
*/ |
130
|
|
|
public function commit() |
131
|
|
|
{ |
132
|
|
|
if (md5($this->fs->read($this->fname)) != $this->hash) { |
133
|
|
|
throw new SourceModifiedException('Unable to commit changes: source data has changed'); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$raw = $this->decoder->encode($this->docs); |
137
|
|
|
$this->fs->update($this->fname, $raw); |
138
|
|
|
$this->hash = md5($raw); |
139
|
|
|
$this->inTransaction = false; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Create a decoder based of source file mime-type |
144
|
|
|
*/ |
145
|
|
|
private function guessDecoder(): DecoderInterface |
146
|
|
|
{ |
147
|
|
|
switch ($this->fs->getMimetype($this->fname)) { |
148
|
|
|
case 'application/x-httpd-php': |
149
|
|
|
return new PhpDecoder; |
150
|
|
|
case 'text/plain': |
151
|
|
|
case 'application/json': |
152
|
|
|
default: |
153
|
|
|
return new JsonDecoder; |
154
|
|
|
} |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.