1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace drupol\phpvfs\StreamWrapper; |
6
|
|
|
|
7
|
|
|
use drupol\phpvfs\Filesystem\Filesystem; |
8
|
|
|
use drupol\phpvfs\Filesystem\FilesystemInterface; |
9
|
|
|
use drupol\phpvfs\Node\File; |
10
|
|
|
use drupol\phpvfs\Node\FileInterface; |
11
|
|
|
use drupol\phpvfs\Utils\Path; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Class PhpVfs. |
15
|
|
|
*/ |
16
|
|
|
class PhpVfs implements StreamWrapperInterface |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* The scheme. |
20
|
|
|
*/ |
21
|
|
|
protected const SCHEME = 'phpvfs'; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The stream context. |
25
|
|
|
* |
26
|
|
|
* @var array |
27
|
|
|
*/ |
28
|
|
|
public $context; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* {@inheritdoc} |
32
|
|
|
*/ |
33
|
|
|
public function dir_closedir(): bool // phpcs:ignore |
34
|
|
|
{ |
35
|
|
|
throw new \Exception('Not implemented yet.'); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* {@inheritdoc} |
40
|
|
|
*/ |
41
|
|
|
public function dir_opendir(string $path, int $options): bool // phpcs:ignore |
42
|
|
|
{ |
43
|
|
|
throw new \Exception('Not implemented yet.'); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* {@inheritdoc} |
48
|
|
|
*/ |
49
|
|
|
public function dir_readdir(): string // phpcs:ignore |
50
|
|
|
{ |
51
|
|
|
throw new \Exception('Not implemented yet.'); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* {@inheritdoc} |
56
|
|
|
*/ |
57
|
|
|
public function dir_rewinddir(): bool // phpcs:ignore |
58
|
|
|
{ |
59
|
|
|
throw new \Exception('Not implemented yet.'); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* {@inheritdoc} |
64
|
|
|
*/ |
65
|
10 |
|
public static function fs(): FilesystemInterface |
66
|
|
|
{ |
67
|
10 |
|
$options = \stream_context_get_options( |
68
|
10 |
|
\stream_context_get_default() |
69
|
|
|
); |
70
|
|
|
|
71
|
10 |
|
return $options[static::SCHEME]['filesystem']; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* {@inheritdoc} |
76
|
|
|
*/ |
77
|
|
|
public function mkdir(string $path, int $mode, int $options): bool |
78
|
|
|
{ |
79
|
|
|
throw new \Exception('Not implemented yet.'); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* {@inheritdoc} |
84
|
|
|
*/ |
85
|
11 |
|
public static function register(Filesystem $filesystem, array $options = []): void |
86
|
|
|
{ |
87
|
|
|
$options = [ |
88
|
11 |
|
static::SCHEME => [ |
89
|
11 |
|
'filesystem' => $filesystem, |
90
|
|
|
'currentFile' => null, |
91
|
11 |
|
] + $options, |
92
|
|
|
]; |
93
|
|
|
|
94
|
11 |
|
\stream_context_set_default($options); |
95
|
11 |
|
\stream_wrapper_register(self::SCHEME, __CLASS__); |
96
|
11 |
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* {@inheritdoc} |
100
|
|
|
*/ |
101
|
1 |
|
public function rename(string $from, string $to): bool |
102
|
|
|
{ |
103
|
1 |
|
if (!$this::fs()->getCwd()->exist($from)) { |
104
|
1 |
|
throw new \Exception('Source resource does not exist.'); |
105
|
|
|
} |
106
|
|
|
|
107
|
1 |
|
$from = $this::fs()->getCwd()->get($from); |
108
|
|
|
|
109
|
1 |
|
if ($this::fs()->getCwd()->exist($to)) { |
110
|
1 |
|
throw new \Exception('Destination already exist.'); |
111
|
|
|
} |
112
|
|
|
|
113
|
1 |
|
$toPath = Path::fromString($to); |
114
|
|
|
|
115
|
1 |
|
$this::fs() |
116
|
1 |
|
->getCwd() |
117
|
1 |
|
->mkdir($toPath->dirname()); |
118
|
|
|
|
119
|
1 |
|
if (null !== $parent = $from->getParent()) { |
120
|
1 |
|
$parent->delete($from); |
121
|
|
|
} |
122
|
|
|
|
123
|
1 |
|
$directory = $this::fs()->getCwd()->cd($toPath->dirname()); |
124
|
|
|
|
125
|
1 |
|
$from->setAttribute('id', $toPath->basename()); |
126
|
|
|
|
127
|
|
|
$directory |
128
|
1 |
|
->add($from); |
129
|
|
|
|
130
|
1 |
|
return true; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* {@inheritdoc} |
135
|
|
|
*/ |
136
|
1 |
|
public function rmdir(string $path, int $options): bool |
137
|
|
|
{ |
138
|
1 |
|
$cwd = $this::fs() |
139
|
1 |
|
->getCwd() |
140
|
1 |
|
->rmdir($path); |
141
|
|
|
|
142
|
1 |
|
$this::fs() |
143
|
1 |
|
->setCwd($cwd); |
144
|
|
|
|
145
|
1 |
|
return true; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* {@inheritdoc} |
150
|
|
|
*/ |
151
|
|
|
public function stream_cast(int $cast_as) // phpcs:ignore |
152
|
|
|
{ |
153
|
|
|
throw new \Exception('Not implemented yet.'); |
154
|
|
|
|
155
|
|
|
return false; |
|
|
|
|
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* {@inheritdoc} |
160
|
|
|
*/ |
161
|
8 |
|
public function stream_close(): void // phpcs:ignore |
162
|
|
|
{ |
163
|
8 |
|
$this->setCurrentFile(null); |
164
|
8 |
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* {@inheritdoc} |
168
|
|
|
*/ |
169
|
3 |
|
public function stream_eof(): bool // phpcs:ignore |
170
|
|
|
{ |
171
|
3 |
|
if (!(($file = $this->getCurrentFile()) instanceof Handler\FileInterface)) { |
172
|
|
|
throw new \Exception('The current file does not implement FileInterface.'); |
173
|
|
|
} |
174
|
|
|
|
175
|
3 |
|
return $file->getPosition() === $file->size(); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* {@inheritdoc} |
180
|
|
|
*/ |
181
|
6 |
|
public function stream_flush(): bool // phpcs:ignore |
182
|
|
|
{ |
183
|
6 |
|
\clearstatcache(); |
184
|
|
|
|
185
|
6 |
|
return true; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* {@inheritdoc} |
190
|
|
|
*/ |
191
|
|
|
public function stream_lock($operation): bool // phpcs:ignore |
192
|
|
|
{ |
193
|
|
|
throw new \Exception('Not implemented yet.'); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* {@inheritdoc} |
198
|
|
|
*/ |
199
|
8 |
|
public function stream_open(string $resource, string $mode, int $options, ?string &$openedPath): bool // phpcs:ignore |
200
|
|
|
{ |
201
|
8 |
|
$modeSplit = \str_split(\str_replace('b', '', $mode)); |
202
|
|
|
|
203
|
8 |
|
$appendMode = \in_array('a', $modeSplit, true); |
204
|
8 |
|
$readMode = \in_array('r', $modeSplit, true); |
205
|
8 |
|
$writeMode = \in_array('w', $modeSplit, true); |
206
|
8 |
|
$extended = \in_array('+', $modeSplit, true); |
|
|
|
|
207
|
|
|
|
208
|
8 |
|
$resourcePath = Path::fromString($resource) |
209
|
8 |
|
->withScheme(null); |
210
|
|
|
|
211
|
8 |
|
$resourceExist = $this::fs()->getCwd()->exist($resource); |
212
|
8 |
|
$resourceDirnameExist = $this::fs()->getCwd()->exist($resourcePath->dirname()); |
|
|
|
|
213
|
|
|
|
214
|
8 |
|
if (false === $resourceExist) { |
215
|
|
|
if (true === $readMode) { |
216
|
|
|
if (0 !== ($options & \STREAM_REPORT_ERRORS)) { |
217
|
|
|
\trigger_error( |
218
|
|
|
\sprintf( |
219
|
|
|
'%s: failed to open stream: Unknown resource.', |
220
|
|
|
$resourcePath |
221
|
|
|
), |
222
|
|
|
\E_USER_WARNING |
223
|
|
|
); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return false; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$this::fs() |
230
|
|
|
->getCwd() |
231
|
|
|
->add(File::create($resource)->root()); |
232
|
|
|
} |
233
|
|
|
|
234
|
8 |
|
$file = $this::fs()->getCwd()->get($resource); |
235
|
|
|
|
236
|
8 |
|
if (!($file instanceof FileInterface)) { |
237
|
|
|
if (0 !== ($options & \STREAM_REPORT_ERRORS)) { |
238
|
|
|
\trigger_error(\sprintf('fopen(%s): failed to open stream: Not a file.', $resource), \E_USER_WARNING); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
return false; |
242
|
|
|
} |
243
|
|
|
|
244
|
8 |
|
$fileHandler = new Handler\File($file, $mode); |
245
|
|
|
|
246
|
8 |
|
if (true === $appendMode) { |
247
|
|
|
$fileHandler->seekToEnd(); |
248
|
8 |
|
} elseif (true === $writeMode) { |
249
|
5 |
|
$fileHandler->truncate(); |
250
|
5 |
|
\clearstatcache(); |
251
|
|
|
} |
252
|
|
|
|
253
|
8 |
|
$this->setCurrentFile($fileHandler); |
254
|
|
|
|
255
|
8 |
|
$openedPath = (string) $file->getPath(); |
256
|
|
|
|
257
|
8 |
|
return true; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* {@inheritdoc} |
262
|
|
|
*/ |
263
|
3 |
|
public function stream_read(int $bytes) // phpcs:ignore |
264
|
|
|
{ |
265
|
3 |
|
if ((null === $file = $this->getCurrentFile()) || (false === $file->isReadable())) { |
266
|
|
|
return false; |
267
|
|
|
} |
268
|
|
|
|
269
|
3 |
|
return $file->read($bytes); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* {@inheritdoc} |
274
|
|
|
*/ |
275
|
2 |
|
public function stream_seek(int $offset, int $whence = \SEEK_SET): bool // phpcs:ignore |
276
|
|
|
{ |
277
|
2 |
|
if (($file = $this->getCurrentFile()) instanceof Handler\File) { |
278
|
2 |
|
$file->setPosition($offset); |
279
|
|
|
} |
280
|
|
|
|
281
|
2 |
|
return true; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* {@inheritdoc} |
286
|
|
|
*/ |
287
|
|
|
public function stream_set_option(int $option, int $arg1, int $arg2): bool // phpcs:ignore |
288
|
|
|
{ |
289
|
|
|
throw new \Exception('Not implemented yet.'); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* {@inheritdoc} |
294
|
|
|
*/ |
295
|
1 |
|
public function stream_stat(): array // phpcs:ignore |
296
|
|
|
{ |
297
|
1 |
|
if (null === $file = $this->getCurrentFile()) { |
298
|
1 |
|
return []; |
299
|
|
|
} |
300
|
|
|
|
301
|
1 |
|
return (array) $file->getFile()->getAttributes(); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* {@inheritdoc} |
306
|
|
|
*/ |
307
|
1 |
|
public function stream_tell(): int // phpcs:ignore |
308
|
|
|
{ |
309
|
1 |
|
if (($file = $this->getCurrentFile()) instanceof Handler\File) { |
310
|
1 |
|
return $file->getPosition(); |
311
|
|
|
} |
|
|
|
|
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* {@inheritdoc} |
316
|
|
|
*/ |
317
|
1 |
|
public function stream_truncate(int $bytes): bool // phpcs:ignore |
318
|
|
|
{ |
319
|
1 |
|
if (($file = $this->getCurrentFile()) instanceof Handler\File) { |
320
|
1 |
|
$file->truncate($bytes); |
321
|
1 |
|
\clearstatcache(); |
322
|
|
|
} |
323
|
|
|
|
324
|
1 |
|
return true; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* {@inheritdoc} |
329
|
|
|
*/ |
330
|
5 |
|
public function stream_write(string $data): int // phpcs:ignore |
331
|
|
|
{ |
332
|
5 |
|
if ((null === $file = $this->getCurrentFile()) || (false === $file->isWritable())) { |
333
|
1 |
|
return 0; |
334
|
|
|
} |
335
|
|
|
|
336
|
5 |
|
return $file->write($data); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* {@inheritdoc} |
341
|
|
|
*/ |
342
|
1 |
|
public function unlink(string $path): bool |
343
|
|
|
{ |
344
|
1 |
|
if (true === $this::fs()->getCwd()->exist($path)) { |
345
|
1 |
|
$file = $this::fs()->getCwd()->get($path); |
346
|
|
|
|
347
|
1 |
|
if (null !== $parent = $file->getParent()) { |
348
|
1 |
|
$parent->delete($file); |
349
|
|
|
} |
350
|
|
|
} |
351
|
|
|
|
352
|
1 |
|
return true; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* {@inheritdoc} |
357
|
|
|
*/ |
358
|
11 |
|
public static function unregister(): void |
359
|
|
|
{ |
360
|
11 |
|
\stream_wrapper_unregister(self::SCHEME); |
361
|
11 |
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* {@inheritdoc} |
365
|
|
|
*/ |
366
|
|
|
public function url_stat(string $path, int $flags): array // phpcs:ignore |
367
|
|
|
{ |
368
|
|
|
throw new \Exception('Not implemented yet.'); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* @return null|\drupol\phpvfs\StreamWrapper\Handler\FileInterface |
373
|
|
|
*/ |
374
|
8 |
|
private function getCurrentFile(): ?Handler\FileInterface |
375
|
|
|
{ |
376
|
8 |
|
$options = \stream_context_get_options( |
377
|
8 |
|
\stream_context_get_default() |
378
|
|
|
); |
379
|
|
|
|
380
|
8 |
|
return $options[static::SCHEME]['currentFile']; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @param null|\drupol\phpvfs\StreamWrapper\Handler\FileInterface $file |
385
|
|
|
*/ |
386
|
8 |
|
private function setCurrentFile(?Handler\FileInterface $file): void |
387
|
|
|
{ |
388
|
8 |
|
$options = \stream_context_get_options( |
389
|
8 |
|
\stream_context_get_default() |
390
|
|
|
); |
391
|
|
|
|
392
|
8 |
|
$options[static::SCHEME]['currentFile'] = $file; |
393
|
|
|
|
394
|
8 |
|
\stream_context_set_default($options); |
395
|
8 |
|
} |
396
|
|
|
} |
397
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.