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

HttpStreamProxy   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 40
eloc 97
c 2
b 0
f 0
dl 0
loc 248
rs 9.2

29 Methods

Rating   Name   Duplication   Size   Complexity  
A mkdir() 0 12 2
A stream_truncate() 0 3 1
A stream_seek() 0 3 1
A __get() 0 3 1
A stream_metadata() 0 3 1
A stream_lock() 0 3 1
A dir_opendir() 0 3 1
A stream_read() 0 23 3
A register() 0 14 2
A stream_open() 0 3 1
A unlink() 0 9 2
A __call() 0 7 1
A stream_close() 0 3 1
A rename() 0 11 2
A stream_eof() 0 3 1
A dir_closedir() 0 3 1
A stream_tell() 0 3 1
A stream_cast() 0 3 1
A rmdir() 0 11 2
A dir_readdir() 0 9 2
A stream_flush() 0 3 1
A dir_rewinddir() 0 3 1
A stream_stat() 0 3 1
A __destruct() 0 7 2
A stream_set_option() 0 3 1
A stream_write() 0 10 2
A __construct() 0 4 1
A url_stat() 0 3 1
A unregister() 0 8 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;
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 HttpStreamProxy 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 ?HttpStreamCollector $collector = null;
23
    public array $operations = [];
24
25
    public function __construct()
26
    {
27
        $this->decorated = new StreamWrapper();
28
        $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...
29
    }
30
31
    public function __call(string $name, array $arguments)
32
    {
33
        try {
34
            self::unregister();
35
            return $this->decorated->{$name}(...$arguments);
36
        } finally {
37
            self::register();
38
        }
39
    }
40
41
    public function __destruct()
42
    {
43
        foreach ($this->operations as $name => $operation) {
44
            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

44
            self::$collector->/** @scrutinizer ignore-call */ 
45
                              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...
45
                operation: $name,
46
                path: $operation['path'],
47
                args: $operation['args'],
48
            );
49
        }
50
    }
51
52
    public function __get(string $name)
53
    {
54
        return $this->decorated->{$name};
55
    }
56
57
    public static function register(): void
58
    {
59
        if (self::$registered) {
60
            return;
61
        }
62
        /**
63
         * It's important to trigger autoloader before unregistering the file stream handler
64
         */
65
        class_exists(StreamWrapper::class);
66
        stream_wrapper_unregister('http');
67
        stream_wrapper_register('http', self::class, STREAM_IS_URL);
68
        stream_wrapper_unregister('https');
69
        stream_wrapper_register('https', 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('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

78
        /** @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...
79
        @stream_wrapper_restore('https');
80
        self::$registered = false;
81
    }
82
83
    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...
84
    {
85
        return $this->__call(__FUNCTION__, func_get_args());
86
    }
87
88
    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...
89
    {
90
        if (!$this->ignored) {
91
            $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...
92
            $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...
93
                ? null
94
                : stream_context_get_options($this->decorated->context);
95
            /**
96
             * @link https://www.php.net/manual/en/context.http.php
97
             */
98
            $method = $context['http']['method'] ?? $context['https']['method'] ?? 'GET';
99
            $headers = (array) ($context['http']['header'] ?? $context['https']['header'] ?? []);
100
101
            $this->operations['read'] = [
102
                '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...
103
                'args' => [
104
                    'method' => $method,
105
                    'response_headers' => $metadata['wrapper_data'],
106
                    'request_headers' => $headers,
107
                ],
108
            ];
109
        }
110
        return $this->__call(__FUNCTION__, func_get_args());
111
    }
112
113
    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...
114
    {
115
        return $this->__call(__FUNCTION__, func_get_args());
116
    }
117
118
    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...
119
    {
120
        return $this->__call(__FUNCTION__, func_get_args());
121
    }
122
123
    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...
124
    {
125
        return $this->__call(__FUNCTION__, func_get_args());
126
    }
127
128
    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...
129
    {
130
        return $this->__call(__FUNCTION__, func_get_args());
131
    }
132
133
    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...
134
    {
135
        return $this->__call(__FUNCTION__, func_get_args());
136
    }
137
138
    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...
139
    {
140
        return $this->__call(__FUNCTION__, func_get_args());
141
    }
142
143
    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...
144
    {
145
        return $this->__call(__FUNCTION__, func_get_args());
146
    }
147
148
    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...
149
    {
150
        return $this->__call(__FUNCTION__, func_get_args());
151
    }
152
153
    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...
154
    {
155
        if (!$this->ignored) {
156
            $this->operations[__FUNCTION__] = [
157
                '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...
158
                'args' => [],
159
            ];
160
        }
161
        return $this->__call(__FUNCTION__, func_get_args());
162
    }
163
164
    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...
165
    {
166
        return $this->__call(__FUNCTION__, func_get_args());
167
    }
168
169
    public function mkdir(string $path, int $mode, int $options): bool
170
    {
171
        if (!$this->ignored) {
172
            $this->operations[__FUNCTION__] = [
173
                'path' => $path,
174
                'args' => [
175
                    'mode' => $mode,
176
                    'options' => $options,
177
                ],
178
            ];
179
        }
180
        return $this->__call(__FUNCTION__, func_get_args());
181
    }
182
183
    public function rename(string $path_from, string $path_to): bool
184
    {
185
        if (!$this->ignored) {
186
            $this->operations[__FUNCTION__] = [
187
                'path' => $path_from,
188
                'args' => [
189
                    'path_to' => $path_to,
190
                ],
191
            ];
192
        }
193
        return $this->__call(__FUNCTION__, func_get_args());
194
    }
195
196
    public function rmdir(string $path, int $options): bool
197
    {
198
        if (!$this->ignored) {
199
            $this->operations[__FUNCTION__] = [
200
                'path' => $path,
201
                'args' => [
202
                    'options' => $options,
203
                ],
204
            ];
205
        }
206
        return $this->__call(__FUNCTION__, func_get_args());
207
    }
208
209
    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...
210
    {
211
        $this->__call(__FUNCTION__, func_get_args());
212
    }
213
214
    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...
215
    {
216
        return $this->__call(__FUNCTION__, func_get_args());
217
    }
218
219
    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...
220
    {
221
        return $this->__call(__FUNCTION__, func_get_args());
222
    }
223
224
    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...
225
    {
226
        return $this->__call(__FUNCTION__, func_get_args());
227
    }
228
229
    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...
230
    {
231
        return $this->__call(__FUNCTION__, func_get_args());
232
    }
233
234
    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...
235
    {
236
        if (!$this->ignored) {
237
            $this->operations['write'] = [
238
                '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...
239
                'args' => [],
240
            ];
241
        }
242
243
        return $this->__call(__FUNCTION__, func_get_args());
244
    }
245
246
    public function unlink(string $path): bool
247
    {
248
        if (!$this->ignored) {
249
            $this->operations[__FUNCTION__] = [
250
                'path' => $path,
251
                'args' => [],
252
            ];
253
        }
254
        return $this->__call(__FUNCTION__, func_get_args());
255
    }
256
257
    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...
258
    {
259
        return $this->__call(__FUNCTION__, func_get_args());
260
    }
261
}
262