Passed
Push — master ( bace86...bb4cad )
by Dmitriy
05:30 queued 02:57
created

HttpStreamProxy   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 107
dl 0
loc 264
rs 8.96
c 0
b 0
f 0
wmc 43

30 Methods

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

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Collector\Stream;
6
7
use Yiisoft\Yii\Debug\Helper\BacktraceIgnoreMatcher;
8
use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapper;
9
use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapperInterface;
10
11
use const SEEK_SET;
12
13
class HttpStreamProxy implements StreamWrapperInterface
14
{
15
    public static bool $registered = false;
16
    public static array $ignoredPathPatterns = [];
17
    public static array $ignoredClasses = [];
18
    public static array $ignoredUrls = [];
19
    /**
20
     * @var resource|null
21
     */
22
    public $context;
23
    public StreamWrapperInterface $decorated;
24
    public bool $ignored = false;
25
26
    public static ?HttpStreamCollector $collector = null;
27
    public array $operations = [];
28
29
    public function __construct()
30
    {
31
        $this->decorated = new StreamWrapper();
32
        $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...
33
    }
34
35
    public function __call(string $name, array $arguments)
36
    {
37
        try {
38
            self::unregister();
39
            return $this->decorated->{$name}(...$arguments);
40
        } finally {
41
            self::register();
42
        }
43
    }
44
45
    public function __destruct()
46
    {
47
        foreach ($this->operations as $name => $operation) {
48
            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

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

83
        /** @scrutinizer ignore-unhandled */ @stream_wrapper_restore('http');

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