Passed
Pull Request — master (#186)
by Alexander
02:41
created

FilesystemStreamProxy   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 45
eloc 101
c 1
b 0
f 0
dl 0
loc 266
rs 8.8

30 Methods

Rating   Name   Duplication   Size   Complexity  
A stream_seek() 0 3 1
A __construct() 0 4 1
A dir_closedir() 0 3 1
A __call() 0 7 1
A stream_stat() 0 3 1
A rename() 0 11 2
A __get() 0 3 1
A dir_readdir() 0 9 2
A stream_eof() 0 3 1
A stream_metadata() 0 3 1
A stream_write() 0 10 2
A stream_open() 0 4 1
A dir_opendir() 0 3 1
A stream_cast() 0 3 1
A rmdir() 0 11 2
A stream_tell() 0 3 1
A stream_flush() 0 3 1
A stream_truncate() 0 3 1
A register() 0 12 2
A setIgnored() 0 27 6
A __destruct() 0 7 2
A url_stat() 0 3 1
A stream_read() 0 9 2
A unlink() 0 9 2
A stream_set_option() 0 3 1
A stream_lock() 0 3 1
A stream_close() 0 3 1
A unregister() 0 7 2
A mkdir() 0 12 2
A dir_rewinddir() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like FilesystemStreamProxy 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 FilesystemStreamProxy, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Collector;
6
7
use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapper;
8
use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapperInterface;
9
10
use const SEEK_SET;
11
12
class FilesystemStreamProxy implements StreamWrapperInterface
13
{
14
    public static bool $registered = false;
15
    /**
16
     * @var resource|null
17
     */
18
    public $context;
19
    public StreamWrapperInterface $decorated;
20
    public bool $ignored = false;
21
22
    public static ?FilesystemStreamCollector $collector = null;
23
    public static array $ignoredPathPatterns = [];
24
    public static array $ignoredClasses = [];
25
    public array $operations = [];
26
27
    public function __construct()
28
    {
29
        $this->decorated = new StreamWrapper();
30
        $this->decorated->context = $this->context;
0 ignored issues
show
Bug introduced by
Accessing context on the interface Yiisoft\Yii\Debug\Helper...\StreamWrapperInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
31
    }
32
33
    public function __call(string $name, array $arguments)
34
    {
35
        try {
36
            self::unregister();
37
            return $this->decorated->{$name}(...$arguments);
38
        } finally {
39
            self::register();
40
        }
41
    }
42
43
    public function __destruct()
44
    {
45
        foreach ($this->operations as $name => $operation) {
46
            self::$collector->collect(
0 ignored issues
show
Bug introduced by
The method collect() does not exist on null. ( Ignorable by Annotation )

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

46
            self::$collector->/** @scrutinizer ignore-call */ 
47
                              collect(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
47
                operation: $name,
48
                path: $operation['path'],
49
                args: $operation['args'],
50
            );
51
        }
52
    }
53
54
    public function __get(string $name)
55
    {
56
        return $this->decorated->{$name};
57
    }
58
59
    public static function register(): void
60
    {
61
        if (self::$registered) {
62
            return;
63
        }
64
        /**
65
         * It's important to trigger autoloader before unregistering the file stream handler
66
         */
67
        class_exists(StreamWrapper::class);
68
        stream_wrapper_unregister('file');
69
        stream_wrapper_register('file', self::class, STREAM_IS_URL);
70
        self::$registered = true;
71
    }
72
73
    public static function unregister(): void
74
    {
75
        if (!self::$registered) {
76
            return;
77
        }
78
        @stream_wrapper_restore('file');
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

78
        /** @scrutinizer ignore-unhandled */ @stream_wrapper_restore('file');

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...
79
        self::$registered = false;
80
    }
81
82
    /**
83
     * TODO: optimise the check. Maybe a hashmap?
84
     */
85
    private function setIgnored(): void
86
    {
87
        $backtrace = debug_backtrace();
88
        /**
89
         * 0 – Called method
90
         * 1 – Proxy
91
         * 2 – Real using place / Composer\ClassLoader include function
92
         * 3 – Whatever / Composer\ClassLoader
93
         */
94
        if (isset($backtrace[3]['class']) && in_array($backtrace[3]['class'], self::$ignoredClasses, true)) {
95
            $this->ignored = true;
96
            return;
97
        }
98
99
        if (!isset($backtrace[2])) {
100
            return;
101
        }
102
        $path = $backtrace[2]['file'];
103
104
        $result = false;
105
        foreach (self::$ignoredPathPatterns as $ignoredPathPattern) {
106
            if (preg_match($ignoredPathPattern, $path) > 0) {
107
                $result = true;
108
                break;
109
            }
110
        }
111
        $this->ignored = $result;
112
    }
113
114
    public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_open" is not in camel caps format
Loading history...
115
    {
116
        $this->setIgnored();
117
        return $this->__call(__FUNCTION__, func_get_args());
118
    }
119
120
    public function stream_read(int $count): string|false
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_read" is not in camel caps format
Loading history...
121
    {
122
        if (!$this->ignored) {
123
            $this->operations['read'] = [
124
                'path' => $this->decorated->filename,
0 ignored issues
show
Bug introduced by
Accessing filename on the interface Yiisoft\Yii\Debug\Helper...\StreamWrapperInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
125
                'args' => [],
126
            ];
127
        }
128
        return $this->__call(__FUNCTION__, func_get_args());
129
    }
130
131
    public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_set_option" is not in camel caps format
Loading history...
132
    {
133
        return $this->__call(__FUNCTION__, func_get_args());
134
    }
135
136
    public function stream_tell(): int
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_tell" is not in camel caps format
Loading history...
137
    {
138
        return $this->__call(__FUNCTION__, func_get_args());
139
    }
140
141
    public function stream_eof(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_eof" is not in camel caps format
Loading history...
142
    {
143
        return $this->__call(__FUNCTION__, func_get_args());
144
    }
145
146
    public function stream_seek(int $offset, int $whence = SEEK_SET): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_seek" is not in camel caps format
Loading history...
147
    {
148
        return $this->__call(__FUNCTION__, func_get_args());
149
    }
150
151
    public function stream_cast(int $castAs)
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_cast" is not in camel caps format
Loading history...
152
    {
153
        return $this->__call(__FUNCTION__, func_get_args());
154
    }
155
156
    public function stream_stat(): array|false
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_stat" is not in camel caps format
Loading history...
157
    {
158
        return $this->__call(__FUNCTION__, func_get_args());
159
    }
160
161
    public function dir_closedir(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::dir_closedir" is not in camel caps format
Loading history...
162
    {
163
        return $this->__call(__FUNCTION__, func_get_args());
164
    }
165
166
    public function dir_opendir(string $path, int $options): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::dir_opendir" is not in camel caps format
Loading history...
167
    {
168
        return $this->__call(__FUNCTION__, func_get_args());
169
    }
170
171
    public function dir_readdir(): false|string
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::dir_readdir" is not in camel caps format
Loading history...
172
    {
173
        if (!$this->ignored) {
174
            $this->operations['readdir'] = [
175
                'path' => $this->decorated->filename,
0 ignored issues
show
Bug introduced by
Accessing filename on the interface Yiisoft\Yii\Debug\Helper...\StreamWrapperInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
176
                'args' => [],
177
            ];
178
        }
179
        return $this->__call(__FUNCTION__, func_get_args());
180
    }
181
182
    public function dir_rewinddir(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::dir_rewinddir" is not in camel caps format
Loading history...
183
    {
184
        return $this->__call(__FUNCTION__, func_get_args());
185
    }
186
187
    public function mkdir(string $path, int $mode, int $options): bool
188
    {
189
        if (!$this->ignored) {
190
            $this->operations[__FUNCTION__] = [
191
                'path' => $path,
192
                'args' => [
193
                    'mode' => $mode,
194
                    'options' => $options,
195
                ],
196
            ];
197
        }
198
        return $this->__call(__FUNCTION__, func_get_args());
199
    }
200
201
    public function rename(string $path_from, string $path_to): bool
202
    {
203
        if (!$this->ignored) {
204
            $this->operations[__FUNCTION__] = [
205
                'path' => $path_from,
206
                'args' => [
207
                    'path_to' => $path_to,
208
                ],
209
            ];
210
        }
211
        return $this->__call(__FUNCTION__, func_get_args());
212
    }
213
214
    public function rmdir(string $path, int $options): bool
215
    {
216
        if (!$this->ignored) {
217
            $this->operations[__FUNCTION__] = [
218
                'path' => $path,
219
                'args' => [
220
                    'options' => $options,
221
                ],
222
            ];
223
        }
224
        return $this->__call(__FUNCTION__, func_get_args());
225
    }
226
227
    public function stream_close(): void
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_close" is not in camel caps format
Loading history...
228
    {
229
        $this->__call(__FUNCTION__, func_get_args());
230
    }
231
232
    public function stream_flush(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_flush" is not in camel caps format
Loading history...
233
    {
234
        return $this->__call(__FUNCTION__, func_get_args());
235
    }
236
237
    public function stream_lock(int $operation): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_lock" is not in camel caps format
Loading history...
238
    {
239
        return $this->__call(__FUNCTION__, func_get_args());
240
    }
241
242
    public function stream_metadata(string $path, int $option, mixed $value): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_metadata" is not in camel caps format
Loading history...
243
    {
244
        return $this->__call(__FUNCTION__, func_get_args());
245
    }
246
247
    public function stream_truncate(int $new_size): bool
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_truncate" is not in camel caps format
Loading history...
248
    {
249
        return $this->__call(__FUNCTION__, func_get_args());
250
    }
251
252
    public function stream_write(string $data): int
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::stream_write" is not in camel caps format
Loading history...
253
    {
254
        if (!$this->ignored) {
255
            $this->operations['write'] = [
256
                'path' => $this->decorated->filename,
0 ignored issues
show
Bug introduced by
Accessing filename on the interface Yiisoft\Yii\Debug\Helper...\StreamWrapperInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
257
                'args' => [],
258
            ];
259
        }
260
261
        return $this->__call(__FUNCTION__, func_get_args());
262
    }
263
264
    public function unlink(string $path): bool
265
    {
266
        if (!$this->ignored) {
267
            $this->operations[__FUNCTION__] = [
268
                'path' => $path,
269
                'args' => [],
270
            ];
271
        }
272
        return $this->__call(__FUNCTION__, func_get_args());
273
    }
274
275
    public function url_stat(string $path, int $flags): array|false
0 ignored issues
show
Coding Style introduced by
Method name "FilesystemStreamProxy::url_stat" is not in camel caps format
Loading history...
276
    {
277
        return $this->__call(__FUNCTION__, func_get_args());
278
    }
279
}
280