Passed
Pull Request — master (#186)
by Dmitriy
05:22 queued 02:51
created

FileStreamProxy::__call()   A

Complexity

Conditions 1
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 1
nc 3
nop 2
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 FileStreamProxy 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 ?FileStreamCollector $collector = null;
23
    public static array $ignoredPathPatterns = [];
24
    public static array $ignoredClasses = [];
25
26
    public function __construct()
27
    {
28
        $this->decorated = new StreamWrapper();
29
        $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...
30
    }
31
32
    public function __call(string $name, array $arguments)
33
    {
34
        try {
35
            self::unregister();
36
            return $this->decorated->{$name}(...$arguments);
37
        } finally {
38
            self::register();
39
        }
40
    }
41
42
    public function __get(string $name)
43
    {
44
        return $this->decorated->{$name};
45
    }
46
47
    public static function register(): void
48
    {
49
        if (self::$registered) {
50
            return;
51
        }
52
        /**
53
         * It's important to trigger autoloader before unregistering the file stream handler
54
         */
55
        class_exists(StreamWrapper::class);
56
        stream_wrapper_unregister('file');
57
        stream_wrapper_register('file', self::class, STREAM_IS_URL);
58
        self::$registered = true;
59
    }
60
61
    public static function unregister(): void
62
    {
63
        if (!self::$registered) {
64
            return;
65
        }
66
        @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

66
        /** @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...
67
        self::$registered = false;
68
    }
69
70
    /**
71
     * TODO: optimise the check. Maybe a hashmap?
72
     */
73
    private function setIgnored(): void
74
    {
75
        $backtrace = debug_backtrace();
76
        /**
77
         * 0 – Called method
78
         * 1 – Proxy
79
         * 2 – Real using place / Composer\ClassLoader include function
80
         * 3 – Whatever / Composer\ClassLoader
81
         */
82
        if (isset($backtrace[3]['class']) && in_array($backtrace[3]['class'], self::$ignoredClasses, true)) {
83
            $this->ignored = true;
84
            return;
85
        }
86
87
        if (!isset($backtrace[2])) {
88
            return;
89
        }
90
        $path = $backtrace[2]['file'];
91
92
        $result = false;
93
        foreach (self::$ignoredPathPatterns as $ignoredPathPattern) {
94
            if (preg_match($ignoredPathPattern, $path) > 0) {
95
                $result = true;
96
                break;
97
            }
98
        }
99
        $this->ignored = $result;
100
    }
101
102
    public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_open" is not in camel caps format
Loading history...
103
    {
104
        $this->setIgnored();
105
        return $this->__call(__FUNCTION__, func_get_args());
106
    }
107
108
    public function stream_read(int $count): string|false
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_read" is not in camel caps format
Loading history...
109
    {
110
        $a = debug_backtrace();
111
        if (!$this->ignored) {
112
            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

112
            self::$collector->/** @scrutinizer ignore-call */ 
113
                              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...
113
                operation: 'read',
114
                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...
115
                args: [],
116
            );
117
        }
118
        return $this->__call(__FUNCTION__, func_get_args());
119
    }
120
121
    public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_set_option" is not in camel caps format
Loading history...
122
    {
123
        return $this->__call(__FUNCTION__, func_get_args());
124
    }
125
126
    public function stream_tell(): int
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_tell" is not in camel caps format
Loading history...
127
    {
128
        return $this->__call(__FUNCTION__, func_get_args());
129
    }
130
131
    public function stream_eof(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_eof" is not in camel caps format
Loading history...
132
    {
133
        return $this->__call(__FUNCTION__, func_get_args());
134
    }
135
136
    public function stream_seek(int $offset, int $whence = SEEK_SET): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_seek" is not in camel caps format
Loading history...
137
    {
138
        return $this->__call(__FUNCTION__, func_get_args());
139
    }
140
141
    public function stream_cast(int $castAs)
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_cast" is not in camel caps format
Loading history...
142
    {
143
        return $this->__call(__FUNCTION__, func_get_args());
144
    }
145
146
    public function stream_stat(): array
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_stat" is not in camel caps format
Loading history...
147
    {
148
        return $this->__call(__FUNCTION__, func_get_args());
149
    }
150
151
    public function dir_closedir(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::dir_closedir" is not in camel caps format
Loading history...
152
    {
153
        return $this->__call(__FUNCTION__, func_get_args());
154
    }
155
156
    public function dir_opendir(string $path, int $options): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::dir_opendir" is not in camel caps format
Loading history...
157
    {
158
        return $this->__call(__FUNCTION__, func_get_args());
159
    }
160
161
    public function dir_readdir(): string
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::dir_readdir" is not in camel caps format
Loading history...
162
    {
163
        if (!$this->ignored) {
164
            self::$collector->collect(
165
                operation: __FUNCTION__,
166
                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...
167
                args: [],
168
            );
169
        }
170
        return $this->__call(__FUNCTION__, func_get_args());
171
    }
172
173
    public function dir_rewinddir(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::dir_rewinddir" is not in camel caps format
Loading history...
174
    {
175
        return $this->__call(__FUNCTION__, func_get_args());
176
    }
177
178
    public function mkdir(string $path, int $mode, int $options): bool
179
    {
180
        if (!$this->ignored) {
181
            self::$collector->collect(
182
                operation: __FUNCTION__,
183
                path: $path,
184
                args: [
185
                    'mode' => $mode,
186
                    'options' => $options,
187
                ]
188
            );
189
        }
190
        return $this->__call(__FUNCTION__, func_get_args());
191
    }
192
193
    public function rename(string $path_from, string $path_to): bool
194
    {
195
        if (!$this->ignored) {
196
            self::$collector->collect(
197
                operation: __FUNCTION__,
198
                path: $path_from,
199
                args: [
200
                    'path_to' => $path_to,
201
                ],
202
            );
203
        }
204
        return $this->__call(__FUNCTION__, func_get_args());
205
    }
206
207
    public function rmdir(string $path, int $options): bool
208
    {
209
        if (!$this->ignored) {
210
            self::$collector->collect(
211
                operation: __FUNCTION__,
212
                path: $path,
213
                args: [
214
                    'options' => $options,
215
                ],
216
            );
217
        }
218
        return $this->__call(__FUNCTION__, func_get_args());
219
    }
220
221
    public function stream_close(): void
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_close" is not in camel caps format
Loading history...
222
    {
223
        $this->__call(__FUNCTION__, func_get_args());
224
    }
225
226
    public function stream_flush(): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_flush" is not in camel caps format
Loading history...
227
    {
228
        return $this->__call(__FUNCTION__, func_get_args());
229
    }
230
231
    public function stream_lock(int $operation): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_lock" is not in camel caps format
Loading history...
232
    {
233
        return $this->__call(__FUNCTION__, func_get_args());
234
    }
235
236
    public function stream_metadata(string $path, int $option, mixed $value): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_metadata" is not in camel caps format
Loading history...
237
    {
238
        return $this->__call(__FUNCTION__, func_get_args());
239
    }
240
241
    public function stream_truncate(int $new_size): bool
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_truncate" is not in camel caps format
Loading history...
242
    {
243
        return $this->__call(__FUNCTION__, func_get_args());
244
    }
245
246
    public function stream_write(string $data): int
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::stream_write" is not in camel caps format
Loading history...
247
    {
248
        if (!$this->ignored) {
249
            self::$collector->collect(
250
                operation: 'write',
251
                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...
252
                args: [],
253
            );
254
        }
255
256
        return $this->__call(__FUNCTION__, func_get_args());
257
    }
258
259
    public function unlink(string $path): bool
260
    {
261
        if (!$this->ignored) {
262
            self::$collector->collect(
263
                operation: __FUNCTION__,
264
                path: $path,
265
                args: [],
266
            );
267
        }
268
        return $this->__call(__FUNCTION__, func_get_args());
269
    }
270
271
    public function url_stat(string $path, int $flags): array|false
0 ignored issues
show
Coding Style introduced by
Method name "FileStreamProxy::url_stat" is not in camel caps format
Loading history...
272
    {
273
        return $this->__call(__FUNCTION__, func_get_args());
274
    }
275
}
276