GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

PharStreamWrapper   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 496
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 188
c 5
b 0
f 0
dl 0
loc 496
rs 7.44
wmc 52

29 Methods

Rating   Name   Duplication   Size   Complexity  
A collectInvocation() 0 19 4
A stream_tell() 0 5 1
A resolveAssertable() 0 3 1
A dir_closedir() 0 11 2
A stream_eof() 0 5 1
A assert() 0 14 2
A invokeInternalStreamWrapper() 0 17 2
A stream_read() 0 6 1
A rmdir() 0 7 1
A stream_write() 0 6 1
A stream_flush() 0 5 1
A stream_cast() 0 5 1
B stream_metadata() 0 32 7
A dir_opendir() 0 9 1
A dir_rewinddir() 0 11 2
A unlink() 0 7 1
A url_stat() 0 5 2
A dir_readdir() 0 5 1
A rename() 0 9 1
A stream_seek() 0 8 1
A stream_truncate() 0 6 1
A stream_set_option() 0 25 4
A mkdir() 0 9 1
A stream_stat() 0 5 1
A stream_open() 0 28 4
A stream_close() 0 5 1
A stream_lock() 0 6 1
A registerStreamWrapper() 0 4 1
A restoreInternalSteamWrapper() 0 10 4

How to fix   Complexity   

Complex Class

Complex classes like PharStreamWrapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PharStreamWrapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
namespace TYPO3\PharStreamWrapper;
4
5
/*
6
 * This file is part of the TYPO3 project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under the terms
9
 * of the MIT License (MIT). For the full copyright and license information,
10
 * please read the LICENSE file that was distributed with this source code.
11
 *
12
 * The TYPO3 project - inspiring people to share!
13
 */
14
15
use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
16
17
class PharStreamWrapper
18
{
19
    /**
20
     * Internal stream constants that are not exposed to PHP, but used...
21
     * @see https://github.com/php/php-src/blob/e17fc0d73c611ad0207cac8a4a01ded38251a7dc/main/php_streams.h
22
     */
23
    const STREAM_OPEN_FOR_INCLUDE = 128;
24
25
    /**
26
     * @var resource
27
     */
28
    public $context;
29
30
    /**
31
     * @var resource
32
     */
33
    protected $internalResource;
34
35
    /**
36
     * @var PharInvocation
37
     */
38
    protected $invocation;
39
40
    /**
41
     * @return bool
42
     */
43
    public function dir_closedir(): bool
44
    {
45
        if (!is_resource($this->internalResource)) {
46
            return false;
47
        }
48
49
        $this->invokeInternalStreamWrapper(
50
            'closedir',
51
            $this->internalResource
52
        );
53
        return !is_resource($this->internalResource);
54
    }
55
56
    /**
57
     * @param string $path
58
     * @param int $options
59
     * @return bool
60
     */
61
    public function dir_opendir(string $path, int $options): bool
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

61
    public function dir_opendir(string $path, /** @scrutinizer ignore-unused */ int $options): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
62
    {
63
        $this->assert($path, Behavior::COMMAND_DIR_OPENDIR);
64
        $this->internalResource = $this->invokeInternalStreamWrapper(
65
            'opendir',
66
            $path,
67
            $this->context
68
        );
69
        return is_resource($this->internalResource);
70
    }
71
72
    /**
73
     * @return string|false
74
     */
75
    public function dir_readdir()
76
    {
77
        return $this->invokeInternalStreamWrapper(
78
            'readdir',
79
            $this->internalResource
80
        );
81
    }
82
83
    /**
84
     * @return bool
85
     */
86
    public function dir_rewinddir(): bool
87
    {
88
        if (!is_resource($this->internalResource)) {
89
            return false;
90
        }
91
92
        $this->invokeInternalStreamWrapper(
93
            'rewinddir',
94
            $this->internalResource
95
        );
96
        return is_resource($this->internalResource);
97
    }
98
99
    /**
100
     * @param string $path
101
     * @param int $mode
102
     * @param int $options
103
     * @return bool
104
     */
105
    public function mkdir(string $path, int $mode, int $options): bool
106
    {
107
        $this->assert($path, Behavior::COMMAND_MKDIR);
108
        return $this->invokeInternalStreamWrapper(
109
            'mkdir',
110
            $path,
111
            $mode,
112
            (bool) ($options & STREAM_MKDIR_RECURSIVE),
113
            $this->context
114
        );
115
    }
116
117
    /**
118
     * @param string $path_from
119
     * @param string $path_to
120
     * @return bool
121
     */
122
    public function rename(string $path_from, string $path_to): bool
123
    {
124
        $this->assert($path_from, Behavior::COMMAND_RENAME);
125
        $this->assert($path_to, Behavior::COMMAND_RENAME);
126
        return $this->invokeInternalStreamWrapper(
127
            'rename',
128
            $path_from,
129
            $path_to,
130
            $this->context
131
        );
132
    }
133
134
    /**
135
     * @param string $path
136
     * @param int $options
137
     * @return bool
138
     */
139
    public function rmdir(string $path, int $options): bool
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

139
    public function rmdir(string $path, /** @scrutinizer ignore-unused */ int $options): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
140
    {
141
        $this->assert($path, Behavior::COMMAND_RMDIR);
142
        return $this->invokeInternalStreamWrapper(
143
            'rmdir',
144
            $path,
145
            $this->context
146
        );
147
    }
148
149
    /**
150
     * @param int $cast_as
151
     */
152
    public function stream_cast(int $cast_as)
0 ignored issues
show
Unused Code introduced by
The parameter $cast_as is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

152
    public function stream_cast(/** @scrutinizer ignore-unused */ int $cast_as)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
153
    {
154
        throw new Exception(
155
            'Method stream_select() cannot be used',
156
            1530103999
157
        );
158
    }
159
160
    public function stream_close()
161
    {
162
        $this->invokeInternalStreamWrapper(
163
            'fclose',
164
            $this->internalResource
165
        );
166
    }
167
168
    /**
169
     * @return bool
170
     */
171
    public function stream_eof(): bool
172
    {
173
        return $this->invokeInternalStreamWrapper(
174
            'feof',
175
            $this->internalResource
176
        );
177
    }
178
179
    /**
180
     * @return bool
181
     */
182
    public function stream_flush(): bool
183
    {
184
        return $this->invokeInternalStreamWrapper(
185
            'fflush',
186
            $this->internalResource
187
        );
188
    }
189
190
    /**
191
     * @param int $operation
192
     * @return bool
193
     */
194
    public function stream_lock(int $operation): bool
195
    {
196
        return $this->invokeInternalStreamWrapper(
197
            'flock',
198
            $this->internalResource,
199
            $operation
200
        );
201
    }
202
203
    /**
204
     * @param string $path
205
     * @param int $option
206
     * @param string|int $value
207
     * @return bool
208
     */
209
    public function stream_metadata(string $path, int $option, $value): bool
210
    {
211
        $this->assert($path, Behavior::COMMAND_STEAM_METADATA);
212
        if ($option === STREAM_META_TOUCH) {
213
            return $this->invokeInternalStreamWrapper(
214
                'touch',
215
                $path,
216
                ...$value
217
            );
218
        }
219
        if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) {
220
            return $this->invokeInternalStreamWrapper(
221
                'chown',
222
                $path,
223
                $value
224
            );
225
        }
226
        if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) {
227
            return $this->invokeInternalStreamWrapper(
228
                'chgrp',
229
                $path,
230
                $value
231
            );
232
        }
233
        if ($option === STREAM_META_ACCESS) {
234
            return $this->invokeInternalStreamWrapper(
235
                'chmod',
236
                $path,
237
                $value
238
            );
239
        }
240
        return false;
241
    }
242
243
    /**
244
     * @param string $path
245
     * @param string $mode
246
     * @param int $options
247
     * @param string|null $opened_path
248
     * @return bool
249
     */
250
    public function stream_open(
251
        string $path,
252
        string $mode,
253
        int $options,
254
        string &$opened_path = null
255
    ): bool {
256
        $this->assert($path, Behavior::COMMAND_STREAM_OPEN);
257
        $arguments = [$path, $mode, (bool) ($options & STREAM_USE_PATH)];
258
        // only add stream context for non include/require calls
259
        if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) {
260
            $arguments[] = $this->context;
261
        // work around https://bugs.php.net/bug.php?id=66569
262
        // for including files from Phar stream with OPcache enabled
263
        } else {
264
            Helper::resetOpCache();
265
        }
266
        $this->internalResource = $this->invokeInternalStreamWrapper(
267
            'fopen',
268
            ...$arguments
269
        );
270
        if (!is_resource($this->internalResource)) {
271
            return false;
272
        }
273
        if ($opened_path !== null) {
274
            $metaData = stream_get_meta_data($this->internalResource);
275
            $opened_path = $metaData['uri'];
276
        }
277
        return true;
278
    }
279
280
    /**
281
     * @param int $count
282
     * @return string
283
     */
284
    public function stream_read(int $count): string
285
    {
286
        return $this->invokeInternalStreamWrapper(
287
            'fread',
288
            $this->internalResource,
289
            $count
290
        );
291
    }
292
293
    /**
294
     * @param int $offset
295
     * @param int $whence
296
     * @return bool
297
     */
298
    public function stream_seek(int $offset, int $whence = SEEK_SET): bool
299
    {
300
        return $this->invokeInternalStreamWrapper(
301
            'fseek',
302
            $this->internalResource,
303
            $offset,
304
            $whence
305
        ) !== -1;
306
    }
307
308
    /**
309
     * @param int $option
310
     * @param int $arg1
311
     * @param int $arg2
312
     * @return bool
313
     */
314
    public function stream_set_option(int $option, int $arg1, int $arg2): bool
315
    {
316
        if ($option === STREAM_OPTION_BLOCKING) {
317
            return $this->invokeInternalStreamWrapper(
318
                'stream_set_blocking',
319
                $this->internalResource,
320
                $arg1
321
            );
322
        }
323
        if ($option === STREAM_OPTION_READ_TIMEOUT) {
324
            return $this->invokeInternalStreamWrapper(
325
                'stream_set_timeout',
326
                $this->internalResource,
327
                $arg1,
328
                $arg2
329
            );
330
        }
331
        if ($option === STREAM_OPTION_WRITE_BUFFER) {
332
            return $this->invokeInternalStreamWrapper(
333
                'stream_set_write_buffer',
334
                $this->internalResource,
335
                $arg2
336
            ) === 0;
337
        }
338
        return false;
339
    }
340
341
    /**
342
     * @return array
343
     */
344
    public function stream_stat(): array
345
    {
346
        return $this->invokeInternalStreamWrapper(
347
            'fstat',
348
            $this->internalResource
349
        );
350
    }
351
352
    /**
353
     * @return int
354
     */
355
    public function stream_tell(): int
356
    {
357
        return $this->invokeInternalStreamWrapper(
358
            'ftell',
359
            $this->internalResource
360
        );
361
    }
362
363
    /**
364
     * @param int $new_size
365
     * @return bool
366
     */
367
    public function stream_truncate(int $new_size): bool
368
    {
369
        return $this->invokeInternalStreamWrapper(
370
            'ftruncate',
371
            $this->internalResource,
372
            $new_size
373
        );
374
    }
375
376
    /**
377
     * @param string $data
378
     * @return int
379
     */
380
    public function stream_write(string $data): int
381
    {
382
        return $this->invokeInternalStreamWrapper(
383
            'fwrite',
384
            $this->internalResource,
385
            $data
386
        );
387
    }
388
389
    /**
390
     * @param string $path
391
     * @return bool
392
     */
393
    public function unlink(string $path): bool
394
    {
395
        $this->assert($path, Behavior::COMMAND_UNLINK);
396
        return $this->invokeInternalStreamWrapper(
397
            'unlink',
398
            $path,
399
            $this->context
400
        );
401
    }
402
403
    /**
404
     * @param string $path
405
     * @param int $flags
406
     * @return array|false
407
     */
408
    public function url_stat(string $path, int $flags)
409
    {
410
        $this->assert($path, Behavior::COMMAND_URL_STAT);
411
        $functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat';
412
        return $this->invokeInternalStreamWrapper($functionName, $path);
413
    }
414
415
    /**
416
     * @param string $path
417
     * @param string $command
418
     */
419
    protected function assert(string $path, string $command)
420
    {
421
        if (Manager::instance()->assert($path, $command) === true) {
422
            $this->collectInvocation($path);
423
            return;
424
        }
425
426
        throw new Exception(
427
            sprintf(
428
                'Denied invocation of "%s" for command "%s"',
429
                $path,
430
                $command
431
            ),
432
            1535189880
433
        );
434
    }
435
436
    /**
437
     * @param string $path
438
     */
439
    protected function collectInvocation(string $path)
440
    {
441
        if (isset($this->invocation)) {
442
            return;
443
        }
444
445
        $manager = Manager::instance();
446
        $this->invocation = $manager->resolve($path);
447
        if ($this->invocation === null) {
448
            throw new Exception(
449
                'Expected invocation could not be resolved',
450
                1556389591
451
            );
452
        }
453
        // confirm, previous interceptor(s) validated invocation
454
        $this->invocation->confirm();
455
        $collection = $manager->getCollection();
456
        if (!$collection->has($this->invocation)) {
457
            $collection->collect($this->invocation);
458
        }
459
    }
460
461
    /**
462
     * @return Manager|Assertable
463
     * @deprecated Use Manager::instance() directly
464
     */
465
    protected function resolveAssertable(): Assertable
466
    {
467
        return Manager::instance();
468
    }
469
470
    /**
471
     * Invokes commands on the native PHP Phar stream wrapper.
472
     *
473
     * @param string $functionName
474
     * @param mixed ...$arguments
475
     * @return mixed
476
     */
477
    private function invokeInternalStreamWrapper(string $functionName, ...$arguments)
478
    {
479
        $silentExecution = $functionName[0] === '@';
480
        $functionName = ltrim($functionName, '@');
481
        $this->restoreInternalSteamWrapper();
482
483
        try {
484
            if ($silentExecution) {
485
                $result = @call_user_func_array($functionName, $arguments);
486
            } else {
487
                $result = call_user_func_array($functionName, $arguments);
488
            }
489
        } finally {
490
            $this->registerStreamWrapper();
491
        }
492
493
        return $result;
494
    }
495
496
    private function restoreInternalSteamWrapper()
497
    {
498
        if (PHP_VERSION_ID < 70324
499
            || PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 70412) {
500
            stream_wrapper_restore('phar');
501
        } else {
502
            // with https://github.com/php/php-src/pull/6183 (PHP #76943) the
503
            // behavior of `stream_wrapper_restore()` did change for
504
            // PHP 8.0-RC1, 7.4.12 and 7.3.24
505
            @stream_wrapper_restore('phar');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for stream_wrapper_restore(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

505
            /** @scrutinizer ignore-unhandled */ @stream_wrapper_restore('phar');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
506
        }
507
    }
508
509
    private function registerStreamWrapper()
510
    {
511
        stream_wrapper_unregister('phar');
512
        stream_wrapper_register('phar', static::class);
513
    }
514
}
515