Passed
Push — master ( 6c656c...64e06b )
by Dmitriy
10:49 queued 07:42
created

InspectController::files()   C

Complexity

Conditions 12
Paths 22

Size

Total Lines 75
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 41
c 5
b 1
f 0
dl 0
loc 75
ccs 0
cts 46
cp 0
rs 6.9666
cc 12
nc 22
nop 1
crap 156

How to fix   Long Method    Complexity   

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\DataResponse;
22
use Yiisoft\DataResponse\DataResponseFactoryInterface;
23
use Yiisoft\Router\CurrentRoute;
24
use Yiisoft\Router\RouteCollectionInterface;
25
use Yiisoft\Translator\CategorySource;
26
use Yiisoft\VarDumper\VarDumper;
27
use Yiisoft\Yii\Debug\Api\Inspector\ApplicationState;
28
use Yiisoft\Yii\Debug\Api\Inspector\Database\SchemaProviderInterface;
29
use Yiisoft\Yii\Debug\Api\Repository\CollectorRepositoryInterface;
30
use Yiisoft\Yii\Debug\Collector\RequestCollector;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Yii\Debug\Collector\RequestCollector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
32
class InspectController
33
{
34
    public function __construct(
35
        private DataResponseFactoryInterface $responseFactory,
36
        private Aliases $aliases,
37
    ) {
38
    }
39
40
    public function config(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
41
    {
42
        $config = $container->get(ConfigInterface::class);
43
44
        $request = $request->getQueryParams();
45
        $group = $request['group'] ?? 'di';
46
47
        $data = $config->get($group);
48
        ksort($data);
49
50
        $response = VarDumper::create($data)->asJson(false, 255);
51
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
52
    }
53
54
    public function getTranslations(ContainerInterface $container): ResponseInterface
55
    {
56
        /**
57
         * @var $categorySources CategorySource[]
58
         */
59
        $categorySources = $container->get('[email protected]');
60
61
        $params = ApplicationState::$params;
62
63
        $locales = array_keys($params['locale']['locales']);
64
        if ($locales === []) {
65
            throw new RuntimeException(
66
                'Unable to determine list of available locales. ' .
67
                'Make sure that "$params[\'locale\'][\'locales\']" contains all available locales.'
68
            );
69
        }
70
        $messages = [];
71
        foreach ($categorySources as $categorySource) {
72
            $messages[$categorySource->getName()] = [];
73
74
            try {
75
                foreach ($locales as $locale) {
76
                    $messages[$categorySource->getName()][$locale] = $categorySource->getMessages($locale);
77
                }
78
            } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
79
            }
80
        }
81
82
        $response = VarDumper::create($messages)->asPrimitives(255);
83
        return $this->responseFactory->createResponse($response);
84
    }
85
86
    public function putTranslation(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
87
    {
88
        /**
89
         * @var $categorySources CategorySource[]
90
         */
91
        $categorySources = $container->get('[email protected]');
92
93
        $body = $request->getParsedBody();
94
        $categoryName = $body['category'] ?? '';
95
        $locale = $body['locale'] ?? '';
96
        $translationId = $body['translation'] ?? '';
97
        $newMessage = $body['message'] ?? '';
98
99
        $categorySource = null;
100
        foreach ($categorySources as $possibleCategorySource) {
101
            if ($possibleCategorySource->getName() === $categoryName) {
102
                $categorySource = $possibleCategorySource;
103
            }
104
        }
105
        if ($categorySource === null) {
106
            throw new InvalidArgumentException(
107
                sprintf(
108
                    'Invalid category name "%s". Only the following categories are available: "%s"',
109
                    $categoryName,
110
                    implode(
111
                        '", "',
112
                        array_map(fn (CategorySource $categorySource) => $categorySource->getName(), $categorySources)
113
                    )
114
                )
115
            );
116
        }
117
        $messages = $categorySource->getMessages($locale);
118
        $messages = array_replace_recursive($messages, [
119
            $translationId => [
120
                'message' => $newMessage,
121
            ],
122
        ]);
123
        $categorySource->write($locale, $messages);
124
125
        $result = [$locale => $messages];
126
        $response = VarDumper::create($result)->asPrimitives(255);
127
        return $this->responseFactory->createResponse($response);
128
    }
129
130
    public function params(): ResponseInterface
131
    {
132
        $params = ApplicationState::$params;
133
        ksort($params);
134
135
        return $this->responseFactory->createResponse($params);
136
    }
137
138
    public function files(ServerRequestInterface $request): ResponseInterface
139
    {
140
        $request = $request->getQueryParams();
141
        $class = $request['class'] ?? '';
142
143
        if (!empty($class) && class_exists($class)) {
144
            $reflection = new ReflectionClass($class);
145
            $destination = $reflection->getFileName();
146
            if ($destination === false) {
147
                return $this->responseFactory->createResponse([
148
                    'message' => sprintf('Cannot find source of class "%s".', $class),
149
                ], 404);
150
            }
151
            return $this->readFile($destination);
152
        }
153
154
        $path = $request['path'] ?? '';
155
156
        $rootPath = $this->aliases->get('@root');
157
158
        $destination = $this->removeBasePath($rootPath, $path);
159
160
        if (!str_starts_with($destination, '/')) {
161
            $destination = '/' . $destination;
162
        }
163
164
        $destination = realpath($rootPath . $destination);
165
166
        if ($destination === false) {
167
            return $this->responseFactory->createResponse([
168
                'message' => sprintf('Destination "%s" does not exist', $path),
169
            ], 404);
170
        }
171
172
        if (!is_dir($destination)) {
173
            return $this->readFile($destination);
174
        }
175
176
        /**
177
         * @var $directoryIterator SplFileInfo[]
178
         */
179
        $directoryIterator = new RecursiveDirectoryIterator(
180
            $destination,
181
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
182
        );
183
184
        $files = [];
185
        foreach ($directoryIterator as $file) {
186
            if ($file->getBasename() === '.') {
187
                continue;
188
            }
189
190
            $path = $file->getPathName();
191
            if ($file->isDir()) {
192
                if ($file->getBasename() === '..') {
193
                    $path = realpath($path);
194
                }
195
                $path .= '/';
196
            }
197
            /**
198
             * Check if path is inside the application directory
199
             */
200
            if (!str_starts_with($path, $rootPath)) {
201
                continue;
202
            }
203
            $path = $this->removeBasePath($rootPath, $path);
204
            $files[] = array_merge(
205
                [
206
                    'path' => $path,
207
                ],
208
                $this->serializeFileInfo($file)
209
            );
210
        }
211
212
        return $this->responseFactory->createResponse($files);
213
    }
214
215
    public function classes(): ResponseInterface
216
    {
217
        // TODO: how to get params for console or other param groups?
218
        $classes = [];
219
220
        $inspected = [...get_declared_classes(), ...get_declared_interfaces()];
221
        // TODO: think how to ignore heavy objects
222
        $patterns = [
223
            fn (string $class) => !str_starts_with($class, 'ComposerAutoloaderInit'),
224
            fn (string $class) => !str_starts_with($class, 'Composer\\'),
225
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\Yii\\Debug\\'),
226
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\ErrorHandler\\ErrorHandler'),
227
            fn (string $class) => !str_contains($class, '@anonymous'),
228
            fn (string $class) => !is_subclass_of($class, Throwable::class),
229
        ];
230
        foreach ($patterns as $patternFunction) {
231
            $inspected = array_filter($inspected, $patternFunction);
232
        }
233
234
        foreach ($inspected as $className) {
235
            $class = new ReflectionClass($className);
236
237
            if ($class->isInternal() || $class->isAbstract() || $class->isAnonymous()) {
238
                continue;
239
            }
240
241
            $classes[] = $className;
242
        }
243
        sort($classes);
244
245
        return $this->responseFactory->createResponse($classes);
246
    }
247
248
    public function object(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
249
    {
250
        $queryParams = $request->getQueryParams();
251
        $className = $queryParams['classname'];
252
253
        $reflection = new ReflectionClass($className);
254
255
        if ($reflection->isInternal()) {
256
            throw new InvalidArgumentException('Inspector cannot initialize internal classes.');
257
        }
258
        if ($reflection->implementsInterface(Throwable::class)) {
259
            throw new InvalidArgumentException('Inspector cannot initialize exceptions.');
260
        }
261
262
        $variable = $container->get($className);
263
        $result = VarDumper::create($variable)->asJson(false, 3);
264
265
        return $this->responseFactory->createResponse([
266
            'object' => json_decode($result, null, 512, JSON_THROW_ON_ERROR),
267
            'path' => $reflection->getFileName(),
268
        ]);
269
    }
270
271
    public function phpinfo(): ResponseInterface
272
    {
273
        ob_start();
274
        phpinfo();
275
        $phpinfo = ob_get_contents();
276
        ob_get_clean();
277
278
        return $this->responseFactory->createResponse($phpinfo);
279
    }
280
281
    public function routes(RouteCollectionInterface $routeCollection): ResponseInterface
282
    {
283
        $routes = [];
284
        foreach ($routeCollection->getRoutes() as $route) {
285
            $data = $route->__debugInfo();
286
            $routes[] = [
287
                'name' => $data['name'],
288
                'hosts' => $data['hosts'],
289
                'pattern' => $data['pattern'],
290
                'methods' => $data['methods'],
291
                'defaults' => $data['defaults'],
292
                'override' => $data['override'],
293
                'middlewares' => $data['middlewareDefinitions'],
294
            ];
295
        }
296
        $response = VarDumper::create($routes)->asJson(false, 5);
297
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
298
    }
299
300
    public function getTables(SchemaProviderInterface $schemaProvider): ResponseInterface
301
    {
302
        return $this->responseFactory->createResponse($schemaProvider->getTables());
303
    }
304
305
    public function getTable(SchemaProviderInterface $schemaProvider, CurrentRoute $currentRoute): ResponseInterface
306
    {
307
        $tableName = $currentRoute->getArgument('name');
308
309
        return $this->responseFactory->createResponse($schemaProvider->getTable($tableName));
0 ignored issues
show
Bug introduced by
It seems like $tableName can also be of type null; however, parameter $tableName of Yiisoft\Yii\Debug\Api\In...erInterface::getTable() 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

309
        return $this->responseFactory->createResponse($schemaProvider->getTable(/** @scrutinizer ignore-type */ $tableName));
Loading history...
310
    }
311
312
    public function request(
313
        ServerRequestInterface $request,
314
        CollectorRepositoryInterface $collectorRepository
315
    ): ResponseInterface {
316
        $request = $request->getQueryParams();
317
        $debugEntryId = $request['debugEntryId'] ?? null;
318
319
        $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

319
        $data = $collectorRepository->getDetail(/** @scrutinizer ignore-type */ $debugEntryId);
Loading history...
320
        $rawRequest = $data[RequestCollector::class]['requestRaw'];
321
322
        $request = Message::parseRequest($rawRequest);
323
324
        $client = new Client();
325
        $response = $client->send($request);
326
327
        $result = VarDumper::create($response)->asPrimitives();
328
329
        return $this->responseFactory->createResponse($result);
330
    }
331
332
    private function removeBasePath(string $rootPath, string $path): string|array|null
333
    {
334
        return preg_replace(
335
            '/^' . preg_quote($rootPath, '/') . '/',
336
            '',
337
            $path,
338
            1
339
        );
340
    }
341
342
    private function getUserOwner(int $uid): array
343
    {
344
        if ($uid === 0 || !function_exists('posix_getpwuid') || false === ($info = posix_getpwuid($uid))) {
345
            return [
346
                'id' => $uid,
347
            ];
348
        }
349
        return [
350
            'uid' => $info['uid'],
351
            'gid' => $info['gid'],
352
            'name' => $info['name'],
353
        ];
354
    }
355
356
    private function getGroupOwner(int $gid): array
357
    {
358
        if ($gid === 0 || !function_exists('posix_getgrgid') || false === ($info = posix_getgrgid($gid))) {
359
            return [
360
                'id' => $gid,
361
            ];
362
        }
363
        return [
364
            'gid' => $info['gid'],
365
            'name' => $info['name'],
366
        ];
367
    }
368
369
    private function serializeFileInfo(SplFileInfo $file): array
370
    {
371
        return [
372
            'baseName' => $file->getBasename(),
373
            'extension' => $file->getExtension(),
374
            'user' => $this->getUserOwner((int) $file->getOwner()),
375
            'group' => $this->getGroupOwner((int) $file->getGroup()),
376
            'size' => $file->getSize(),
377
            'type' => $file->getType(),
378
            'permissions' => substr(sprintf('%o', $file->getPerms()), -4),
379
        ];
380
    }
381
382
    private function readFile(string $destination): DataResponse
383
    {
384
        $rootPath = $this->aliases->get('@root');
385
        $file = new SplFileInfo($destination);
386
        return $this->responseFactory->createResponse(
387
            array_merge(
388
                [
389
                    'directory' => $this->removeBasePath($rootPath, dirname($destination)),
390
                    'content' => file_get_contents($destination),
391
                    'path' => $this->removeBasePath($rootPath, $destination),
392
                    'absolutePath' => $destination,
393
                ],
394
                $this->serializeFileInfo($file)
395
            )
396
        );
397
    }
398
}
399