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

HttpStreamProxy::stream_seek()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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