Passed
Pull Request — master (#76)
by
unknown
08:22 queued 05:06
created

InspectController::files()   B

Complexity

Conditions 9
Paths 20

Size

Total Lines 71
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 37
c 5
b 1
f 0
dl 0
loc 71
ccs 0
cts 34
cp 0
rs 7.7724
cc 9
nc 20
nop 2
crap 90

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 GuzzleHttp\Client;
9
use GuzzleHttp\Psr7\Message;
10
use InvalidArgumentException;
11
use Psr\Container\ContainerInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use RecursiveDirectoryIterator;
15
use ReflectionClass;
16
use RuntimeException;
17
use SplFileInfo;
18
use Throwable;
19
use Yiisoft\Aliases\Aliases;
20
use Yiisoft\Config\ConfigInterface;
21
use Yiisoft\DataResponse\DataResponseFactoryInterface;
22
use Yiisoft\Router\RouteCollectionInterface;
23
use Yiisoft\Translator\CategorySource;
24
use Yiisoft\VarDumper\VarDumper;
25
use Yiisoft\Yii\Debug\Api\Inspector\ApplicationState;
26
use Yiisoft\Yii\Debug\Api\Inspector\CommandInterface;
27
use Yiisoft\Yii\Debug\Api\Repository\CollectorRepositoryInterface;
28
use Yiisoft\Yii\Debug\Collector\RequestCollector;
29
30
class InspectController
31
{
32
    public function __construct(
33
        private DataResponseFactoryInterface $responseFactory,
34
    ) {
35
    }
36
37
    public function config(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
38
    {
39
        $config = $container->get(ConfigInterface::class);
40
41
        $request = $request->getQueryParams();
42
        $group = $request['group'] ?? 'web';
43
44
        $data = $config->get($group);
45
        ksort($data);
46
47
        $response = VarDumper::create($data)->asJson(false, 255);
48
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
49
    }
50
51
    public function getTranslations(ContainerInterface $container): ResponseInterface
52
    {
53
        /**
54
         * @var $categorySources CategorySource[]
55
         */
56
        $categorySources = $container->get('[email protected]');
57
58
        $params = ApplicationState::$params;
59
60
        $locales = array_keys($params['locale']['locales']);
61
        if ($locales === []) {
62
            throw new RuntimeException(
63
                'Unable to determine list of available locales. ' .
64
                'Make sure that "$params[\'locale\'][\'locales\']" contains all available locales.'
65
            );
66
        }
67
        $messages = [];
68
        foreach ($categorySources as $categorySource) {
69
            $messages[$categorySource->getName()] = [];
70
71
            try {
72
                foreach ($locales as $locale) {
73
                    $messages[$categorySource->getName()][$locale] = $categorySource->getMessages($locale);
74
                }
75
            } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
76
            }
77
        }
78
79
        $response = VarDumper::create($messages)->asPrimitives(255);
80
        return $this->responseFactory->createResponse($response);
81
    }
82
83
    public function putTranslation(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
84
    {
85
        /**
86
         * @var $categorySources CategorySource[]
87
         */
88
        $categorySources = $container->get('[email protected]');
89
90
        $body = $request->getParsedBody();
91
        $categoryName = $body['category'] ?? '';
92
        $locale = $body['locale'] ?? '';
93
        $translationId = $body['translation'] ?? '';
94
        $newMessage = $body['message'] ?? '';
95
96
        $categorySource = null;
97
        foreach ($categorySources as $possibleCategorySource) {
98
            if ($possibleCategorySource->getName() === $categoryName) {
99
                $categorySource = $possibleCategorySource;
100
            }
101
        }
102
        if ($categorySource === null) {
103
            throw new InvalidArgumentException(
104
                sprintf(
105
                    'Invalid category name "%s". Only the following categories are available: "%s"',
106
                    $categoryName,
107
                    implode(
108
                        '", "',
109
                        array_map(fn (CategorySource $categorySource) => $categorySource->getName(), $categorySources)
110
                    )
111
                )
112
            );
113
        }
114
        $messages = $categorySource->getMessages($locale);
115
        $messages = array_replace_recursive($messages, [
116
            $translationId => [
117
                'message' => $newMessage,
118
            ],
119
        ]);
120
        $categorySource->write($locale, $messages);
121
122
        $result = [$locale => $messages];
123
        $response = VarDumper::create($result)->asPrimitives(255);
124
        return $this->responseFactory->createResponse($response);
125
    }
126
127
    public function params(): ResponseInterface
128
    {
129
        $params = ApplicationState::$params;
130
        ksort($params);
131
132
        return $this->responseFactory->createResponse($params);
133
    }
134
135
    public function files(Aliases $aliases, ServerRequestInterface $request): ResponseInterface
136
    {
137
        $request = $request->getQueryParams();
138
        $path = $request['path'] ?? '';
139
140
        $rootPath = $aliases->get('@root');
141
142
        $destination = $this->removeBasePath($rootPath, $path);
143
144
        if (!str_starts_with('/', $destination)) {
145
            $destination = '/' . $destination;
146
        }
147
148
        $destination = realpath($rootPath . $destination);
149
150
        if (!file_exists($destination)) {
151
            throw new InvalidArgumentException(sprintf('Destination "%s" does not exist', $destination));
152
        }
153
154
        if (!is_dir($destination)) {
155
            $file = new SplFileInfo($destination);
156
            return $this->responseFactory->createResponse(
157
                array_merge(
158
                    [
159
                        'directory' => $this->removeBasePath($rootPath, dirname($destination)),
160
                        'content' => file_get_contents($destination),
161
                        'path' => $this->removeBasePath($rootPath, $destination),
162
                        'absolutePath' => $destination,
163
                    ],
164
                    $this->serializeFileInfo($file)
165
                )
166
            );
167
        }
168
169
        /**
170
         * @var $directoryIterator SplFileInfo[]
171
         */
172
        $directoryIterator = new RecursiveDirectoryIterator(
173
            $destination,
174
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
175
        );
176
177
        $files = [];
178
        foreach ($directoryIterator as $file) {
179
            if ($file->getBasename() === '.') {
180
                continue;
181
            }
182
183
            $path = $file->getPathName();
184
            if ($file->isDir()) {
185
                if ($file->getBasename() === '..') {
186
                    $path = realpath($path);
187
                }
188
                $path .= '/';
189
            }
190
            /**
191
             * Check if path is inside the application directory
192
             */
193
            if (!str_starts_with($path, $rootPath)) {
194
                continue;
195
            }
196
            $path = $this->removeBasePath($rootPath, $path);
197
            $files[] = array_merge(
198
                [
199
                    'path' => $path,
200
                ],
201
                $this->serializeFileInfo($file)
202
            );
203
        }
204
205
        return $this->responseFactory->createResponse($files);
206
    }
207
208
    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

208
    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...
209
    {
210
        // TODO: how to get params for console or other param groups?
211
        $classes = [];
212
213
        $inspected = [...get_declared_classes(), ...get_declared_interfaces()];
214
        // TODO: think how to ignore heavy objects
215
        $patterns = [
216
            fn (string $class) => !str_starts_with($class, 'ComposerAutoloaderInit'),
217
            fn (string $class) => !str_starts_with($class, 'Composer\\'),
218
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\Yii\\Debug\\'),
219
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\ErrorHandler\\ErrorHandler'),
220
            fn (string $class) => !str_contains($class, '@anonymous'),
221
            fn (string $class) => !is_subclass_of($class, Throwable::class),
222
        ];
223
        foreach ($patterns as $patternFunction) {
224
            $inspected = array_filter($inspected, $patternFunction);
225
        }
226
227
        foreach ($inspected as $className) {
228
            $class = new ReflectionClass($className);
229
230
            if ($class->isInternal()) {
231
                continue;
232
            }
233
234
            $classes[] = $className;
235
        }
236
        sort($classes);
237
238
        return $this->responseFactory->createResponse($classes);
239
    }
240
241
    public function object(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
242
    {
243
        $queryParams = $request->getQueryParams();
244
        $className = $queryParams['classname'];
245
246
        $reflection = new ReflectionClass($className);
247
248
        if ($reflection->isInternal()) {
249
            throw new InvalidArgumentException('Inspector cannot initialize internal classes.');
250
        }
251
        if ($reflection->implementsInterface(Throwable::class)) {
252
            throw new InvalidArgumentException('Inspector cannot initialize exceptions.');
253
        }
254
255
        $variable = $container->get($className);
256
        $result = VarDumper::create($variable)->asJson(false, 3);
257
258
        return $this->responseFactory->createResponse([
259
            'object' => json_decode($result, null, 512, JSON_THROW_ON_ERROR),
260
            'path' => $reflection->getFileName(),
261
        ]);
262
    }
263
264
    public function getCommands(ConfigInterface $config): ResponseInterface
265
    {
266
        $params = $config->get('params');
267
        $commandMap = $params['yiisoft/yii-debug-api']['inspector']['commandMap'] ?? [];
268
269
        $result = [];
270
        foreach ($commandMap as $groupName => $commands) {
271
            foreach ($commands as $name => $command) {
272
                if (!is_subclass_of($command, CommandInterface::class)) {
273
                    continue;
274
                }
275
                $result[] = [
276
                    'name' => $name,
277
                    'title' => $command::getTitle(),
278
                    'group' => $groupName,
279
                    'description' => $command::getDescription(),
280
                ];
281
            }
282
        }
283
284
        return $this->responseFactory->createResponse($result);
285
    }
286
287
    public function routes(RouteCollectionInterface $routeCollection): ResponseInterface
288
    {
289
        $routes = [];
290
        foreach ($routeCollection->getRoutes() as $route) {
291
            $data = $route->__debugInfo();
292
            $routes[] = [
293
                'name' => $data['name'],
294
                'hosts' => $data['hosts'],
295
                'pattern' => $data['pattern'],
296
                'methods' => $data['methods'],
297
                'defaults' => $data['defaults'],
298
                'override' => $data['override'],
299
                'middlewares' => $data['middlewareDefinitions'],
300
            ];
301
        }
302
        $response = VarDumper::create($routes)->asJson(false, 5);
303
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
304
    }
305
306
    public function runCommand(
307
        ServerRequestInterface $request,
308
        ContainerInterface $container,
309
        ConfigInterface $config
310
    ): ResponseInterface {
311
        $params = $config->get('params');
312
        $commandMap = $params['yiisoft/yii-debug-api']['inspector']['commandMap'] ?? [];
313
314
        /**
315
         * @var array<string, class-string<CommandInterface>> $commandList
316
         */
317
        $commandList = [];
318
        foreach ($commandMap as $commands) {
319
            foreach ($commands as $name => $command) {
320
                if (!is_subclass_of($command, CommandInterface::class)) {
321
                    continue;
322
                }
323
                $commandList[$name] = $command;
324
            }
325
        }
326
327
        $request = $request->getQueryParams();
328
        $commandName = $request['command'] ?? null;
329
330
        if ($commandName === null) {
331
            throw new InvalidArgumentException(
332
                sprintf(
333
                    'Command must not be null. Available commands: "%s".',
334
                    implode('", "', $commandList)
335
                )
336
            );
337
        }
338
339
        if (!array_key_exists($commandName, $commandList)) {
340
            throw new InvalidArgumentException(
341
                sprintf(
342
                    'Unknown command "%s". Available commands: "%s".',
343
                    $commandName,
344
                    implode('", "', $commandList)
345
                )
346
            );
347
        }
348
349
        $commandClass = $commandList[$commandName];
350
        /**
351
         * @var $command CommandInterface
352
         */
353
        $command = $container->get($commandClass);
354
355
        $result = $command->run();
356
357
        return $this->responseFactory->createResponse([
358
            'status' => $result->getStatus(),
359
            'result' => $result->getResult(),
360
            'error' => $result->getErrors(),
361
        ]);
362
    }
363
364
    public function request(ServerRequestInterface $request, CollectorRepositoryInterface $collectorRepository): ResponseInterface
365
    {
366
        $request = $request->getQueryParams();
367
        $debugEntryId = $request['debugEntryId'] ?? null;
368
369
        $data = $collectorRepository->getDetail($debugEntryId);
0 ignored issues
show
Bug introduced by
It seems like $debugEntryId can also be of type null; however, parameter $id of Yiisoft\Yii\Debug\Api\Re...yInterface::getDetail() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

369
        $data = $collectorRepository->getDetail(/** @scrutinizer ignore-type */ $debugEntryId);
Loading history...
370
        $rawRequest = $data[RequestCollector::class]['requestRaw'];
371
372
        $request = Message::parseRequest($rawRequest);
373
374
        $client = new Client();
375
        $response = $client->send($request);
376
377
        $result = VarDumper::create($response)->asPrimitives();
378
379
        return $this->responseFactory->createResponse($result);
380
    }
381
382
    private function removeBasePath(string $rootPath, string $path): string|array|null
383
    {
384
        return preg_replace(
385
            '/^' . preg_quote($rootPath, '/') . '/',
386
            '',
387
            $path,
388
            1
389
        );
390
    }
391
392
    private function getUserOwner(int $uid): array
393
    {
394
        if ($uid === 0 || !function_exists('posix_getpwuid') || false === ($info = posix_getpwuid($uid))) {
395
            return [
396
                'id' => $uid,
397
            ];
398
        }
399
        return [
400
            'uid' => $info['uid'],
401
            'gid' => $info['gid'],
402
            'name' => $info['name'],
403
        ];
404
    }
405
406
    private function getGroupOwner(int $gid): array
407
    {
408
        if ($gid === 0 || !function_exists('posix_getgrgid') || false === ($info = posix_getgrgid($gid))) {
409
            return [
410
                'id' => $gid,
411
            ];
412
        }
413
        return [
414
            'gid' => $info['gid'],
415
            'name' => $info['name'],
416
        ];
417
    }
418
419
    private function serializeFileInfo(SplFileInfo $file): array
420
    {
421
        return [
422
            'baseName' => $file->getBasename(),
423
            'extension' => $file->getExtension(),
424
            'user' => $this->getUserOwner((int) $file->getOwner()),
425
            'group' => $this->getGroupOwner((int) $file->getGroup()),
426
            'size' => $file->getSize(),
427
            'type' => $file->getType(),
428
            'permissions' => substr(sprintf('%o', $file->getPerms()), -4),
429
        ];
430
    }
431
}
432