Passed
Pull Request — master (#65)
by Dmitriy
03:03
created

InspectController::runCommand()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 55
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 55
ccs 0
cts 26
cp 0
rs 8.8497
cc 6
nc 12
nop 3
crap 42

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Api\Controller;
6
7
use FilesystemIterator;
8
use InvalidArgumentException;
9
use Psr\Container\ContainerInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use RecursiveDirectoryIterator;
13
use ReflectionClass;
14
use SplFileInfo;
15
use Throwable;
16
use Yiisoft\Aliases\Aliases;
17
use Yiisoft\Config\ConfigInterface;
18
use Yiisoft\DataResponse\DataResponseFactoryInterface;
19
use Yiisoft\VarDumper\VarDumper;
20
use Yiisoft\Yii\Debug\Api\Inspector\ApplicationState;
21
use Yiisoft\Yii\Debug\Api\Inspector\CommandInterface;
22
23
class InspectController
24
{
25
    public function __construct(
26
        private DataResponseFactoryInterface $responseFactory,
27
    ) {
28
    }
29
30
    public function config(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
31
    {
32
        $config = $container->get(ConfigInterface::class);
33
34
        $request = $request->getQueryParams();
35
        $group = $request['group'] ?? 'web';
36
37
        $data = $config->get($group);
38
        ksort($data);
39
40
        $response = VarDumper::create($data)->asJson(false, 255);
41
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
42
    }
43
44
    public function params(): ResponseInterface
45
    {
46
        $params = ApplicationState::$params;
47
        ksort($params);
48
49
        return $this->responseFactory->createResponse($params);
50
    }
51
52
    public function files(Aliases $aliases, ServerRequestInterface $request): ResponseInterface
53
    {
54
        $request = $request->getQueryParams();
55
        $path = $request['path'] ?? '';
56
57
        $rootPath = $aliases->get('@root');
58
59
        $destination = $this->removeBasePath($rootPath, $path);
60
61
        if (!str_starts_with('/', $destination)) {
62
            $destination = '/' . $destination;
63
        }
64
65
        $destination = realpath($rootPath . $destination);
66
67
        if (!file_exists($destination)) {
68
            throw new InvalidArgumentException(sprintf('Destination "%s" does not exist', $destination));
69
        }
70
71
        if (!is_dir($destination)) {
72
            $file = new SplFileInfo($destination);
73
            return $this->responseFactory->createResponse(
74
                array_merge(
75
                    [
76
                        'directory' => $this->removeBasePath($rootPath, dirname($destination)),
77
                        'content' => file_get_contents($destination),
78
                        'path' => $this->removeBasePath($rootPath, $destination),
79
                        'absolutePath' => $destination,
80
                    ],
81
                    $this->serializeFileInfo($file)
82
                )
83
            );
84
        }
85
86
        /**
87
         * @var $directoryIterator SplFileInfo[]
88
         */
89
        $directoryIterator = new RecursiveDirectoryIterator(
90
            $destination,
91
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
92
        );
93
94
        $files = [];
95
        foreach ($directoryIterator as $file) {
96
            if ($file->getBasename() === '.') {
97
                continue;
98
            }
99
100
            $path = $file->getPathName();
101
            if ($file->isDir()) {
102
                if ($file->getBasename() === '..') {
103
                    $path = realpath($path);
104
                }
105
                $path .= '/';
106
            }
107
            /**
108
             * Check if path is inside the application directory
109
             */
110
            if (!str_starts_with($path, $rootPath)) {
111
                continue;
112
            }
113
            $path = $this->removeBasePath($rootPath, $path);
114
            $files[] = array_merge(
115
                [
116
                    'path' => $path,
117
                ],
118
                $this->serializeFileInfo($file)
119
            );
120
        }
121
122
        return $this->responseFactory->createResponse($files);
123
    }
124
125
    public function classes(ContainerInterface $container): ResponseInterface
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed. ( Ignorable by Annotation )

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

125
    public function classes(/** @scrutinizer ignore-unused */ ContainerInterface $container): ResponseInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
126
    {
127
        // TODO: how to get params for console or other param groups?
128
        $classes = [];
129
130
        $inspected = [...get_declared_classes(), ...get_declared_interfaces()];
131
        // TODO: think how to ignore heavy objects
132
        $patterns = [
133
            fn (string $class) => !str_starts_with($class, 'ComposerAutoloaderInit'),
134
            fn (string $class) => !str_starts_with($class, 'Composer\\'),
135
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\Yii\\Debug\\'),
136
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\ErrorHandler\\ErrorHandler'),
137
            fn (string $class) => !str_contains($class, '@anonymous'),
138
            fn (string $class) => !is_subclass_of($class, Throwable::class),
139
        ];
140
        foreach ($patterns as $patternFunction) {
141
            $inspected = array_filter($inspected, $patternFunction);
142
        }
143
144
        foreach ($inspected as $className) {
145
            $class = new ReflectionClass($className);
146
147
            if ($class->isInternal()) {
148
                continue;
149
            }
150
151
            $classes[] = $className;
152
        }
153
        sort($classes);
154
155
        return $this->responseFactory->createResponse($classes);
156
    }
157
158
    public function object(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
159
    {
160
        $queryParams = $request->getQueryParams();
161
        $className = $queryParams['classname'];
162
163
        $reflection = new ReflectionClass($className);
164
165
        if ($reflection->isInternal()) {
166
            throw new InvalidArgumentException('Inspector cannot initialize internal classes.');
167
        }
168
        if ($reflection->implementsInterface(Throwable::class)) {
169
            throw new InvalidArgumentException('Inspector cannot initialize exceptions.');
170
        }
171
172
        $variable = $container->get($className);
173
        $result = VarDumper::create($variable)->asJson(false, 3);
174
175
        return $this->responseFactory->createResponse([
176
            'object' => json_decode($result, null, 512, JSON_THROW_ON_ERROR),
177
            'path' => $reflection->getFileName(),
178
        ]);
179
    }
180
181
    public function getCommands(ConfigInterface $config): ResponseInterface
182
    {
183
        $params = $config->get('params');
184
        $commandMap = $params['yiisoft/yii-debug-api']['inspector']['commandMap'] ?? [];
185
186
        $result = [];
187
        foreach ($commandMap as $groupName => $commands) {
188
            foreach ($commands as $name => $command) {
189
                if (!is_subclass_of($command, CommandInterface::class)) {
190
                    continue;
191
                }
192
                $result[] = [
193
                    'name' => $name,
194
                    'title' => $command::getTitle(),
195
                    'group' => $groupName,
196
                    'description' => $command::getDescription(),
197
                ];
198
            }
199
        }
200
201
        return $this->responseFactory->createResponse($result);
202
    }
203
204
    public function runCommand(
205
        ServerRequestInterface $request,
206
        ContainerInterface $container,
207
        ConfigInterface $config
208
    ): ResponseInterface {
209
        $params = $config->get('params');
210
        $commandMap = $params['yiisoft/yii-debug-api']['inspector']['commandMap'] ?? [];
211
212
        /**
213
         * @var array<string, class-string<CommandInterface>> $commandList
214
         */
215
        $commandList = [];
216
        foreach ($commandMap as $commands) {
217
            foreach ($commands as $name => $command) {
218
                if (!is_subclass_of($command, CommandInterface::class)) {
219
                    continue;
220
                }
221
                $commandList[$name] = $command;
222
            }
223
        }
224
225
        $request = $request->getQueryParams();
226
        $commandName = $request['command'] ?? null;
227
228
        if ($commandName === null) {
229
            throw new InvalidArgumentException(
230
                sprintf(
231
                    'Command must not be null. Available commands: "%s".',
232
                    implode('", "', $commandList)
233
                )
234
            );
235
        }
236
237
        if (!array_key_exists($commandName, $commandList)) {
238
            throw new InvalidArgumentException(
239
                sprintf(
240
                    'Unknown command "%s". Available commands: "%s".',
241
                    $commandName,
242
                    implode('", "', $commandList)
243
                )
244
            );
245
        }
246
247
        $commandClass = $commandList[$commandName];
248
        /**
249
         * @var $command CommandInterface
250
         */
251
        $command = $container->get($commandClass);
252
253
        $result = $command->run();
254
255
        return $this->responseFactory->createResponse([
256
            'status' => $result->getStatus(),
257
            'result' => $result->getResult(),
258
            'error' => $result->getErrors(),
259
        ]);
260
    }
261
262
    private function removeBasePath(string $rootPath, string $path): string|array|null
263
    {
264
        return preg_replace(
265
            '/^' . preg_quote($rootPath, '/') . '/',
266
            '',
267
            $path,
268
            1
269
        );
270
    }
271
272
    private function getUserOwner(int $uid): array
273
    {
274
        if ($uid === 0 || !function_exists('posix_getpwuid') || false === ($info = posix_getpwuid($uid))) {
275
            return [
276
                'id' => $uid,
277
            ];
278
        }
279
        return [
280
            'uid' => $info['uid'],
281
            'gid' => $info['gid'],
282
            'name' => $info['name'],
283
        ];
284
    }
285
286
    private function getGroupOwner(int $gid): array
287
    {
288
        if ($gid === 0 || !function_exists('posix_getgrgid') || false === ($info = posix_getgrgid($gid))) {
289
            return [
290
                'id' => $gid,
291
            ];
292
        }
293
        return [
294
            'gid' => $info['gid'],
295
            'name' => $info['name'],
296
        ];
297
    }
298
299
    private function serializeFileInfo(SplFileInfo $file): array
300
    {
301
        return [
302
            'baseName' => $file->getBasename(),
303
            'extension' => $file->getExtension(),
304
            'user' => $this->getUserOwner((int) $file->getOwner()),
305
            'group' => $this->getGroupOwner((int) $file->getGroup()),
306
            'size' => $file->getSize(),
307
            'type' => $file->getType(),
308
            'permissions' => substr(sprintf('%o', $file->getPerms()), -4),
309
        ];
310
    }
311
}
312