Passed
Pull Request — master (#60)
by Dmitriy
39:45 queued 26:56
created

InspectController::serializeFileInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 8
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
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\Command\CodeceptionCommand;
22
use Yiisoft\Yii\Debug\Api\Inspector\Command\PHPUnitCommand;
23
use Yiisoft\Yii\Debug\Api\Inspector\Command\PsalmCommand;
24
25
class InspectController
26
{
27
    public function __construct(
28
        private DataResponseFactoryInterface $responseFactory,
29
    ) {
30
    }
31
32
    public function config(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
33
    {
34
        $config = $container->get(ConfigInterface::class);
35
36
        $request = $request->getQueryParams();
37
        $group = $request['group'] ?? 'web';
38
39
        $data = $config->get($group);
40
        ksort($data);
41
42
        $response = VarDumper::create($data)->asJson(false, 255);
43
        return $this->responseFactory->createResponse(json_decode($response, null, 512, JSON_THROW_ON_ERROR));
44
    }
45
46
    public function params(): ResponseInterface
47
    {
48
        $params = ApplicationState::$params;
49
        ksort($params);
50
51
        return $this->responseFactory->createResponse($params);
52
    }
53
54
    public function files(Aliases $aliases, ServerRequestInterface $request): ResponseInterface
55
    {
56
        $request = $request->getQueryParams();
57
        $path = $request['path'] ?? '';
58
59
        $rootPath = $aliases->get('@root');
60
61
        $destination = realpath($rootPath . $path);
62
63
        if (!file_exists($destination)) {
64
            throw new InvalidArgumentException(sprintf('Destination "%s" does not exist', $destination));
65
        }
66
67
68
        if (!is_dir($destination)) {
69
            $file = new SplFileInfo($destination);
70
            return $this->responseFactory->createResponse([
71
                'directory' => $this->removeBasePath($rootPath, dirname($destination)),
72
                'content' => file_get_contents($destination),
73
                'path' => $this->removeBasePath($rootPath, $destination),
74
                'absolutePath' => $destination,
75
                ...$this->serializeFileInfo($file),
76
            ]);
77
        }
78
79
        /**
80
         * @var $directoryIterator SplFileInfo[]
81
         */
82
        $directoryIterator = new RecursiveDirectoryIterator(
83
            $destination,
84
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
85
        );
86
87
        $files = [];
88
        foreach ($directoryIterator as $file) {
89
            if ($file->getBasename() === '.') {
90
                continue;
91
            }
92
93
            $path = $file->getPathName();
94
            if ($file->isDir()) {
95
                if ($file->getBasename() === '..') {
96
                    $path = realpath($path);
97
                }
98
                $path .= '/';
99
            }
100
            /**
101
             * Check if path is inside the application directory
102
             */
103
            if (!str_starts_with($path, $rootPath)) {
104
                continue;
105
            }
106
            $path = $this->removeBasePath($rootPath, $path);
107
            $files[] = [
108
                'path' => $path,
109
                ...$this->serializeFileInfo($file),
110
            ];
111
        }
112
113
        return $this->responseFactory->createResponse($files);
114
    }
115
116
    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

116
    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...
117
    {
118
        // TODO: how to get params for console or other param groups?
119
        $classes = [];
120
121
        $inspected = [...get_declared_classes(), ...get_declared_interfaces()];
122
        // TODO: think how to ignore heavy objects
123
        $patterns = [
124
            fn (string $class) => !str_starts_with($class, 'ComposerAutoloaderInit'),
125
            fn (string $class) => !str_starts_with($class, 'Composer\\'),
126
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\Yii\\Debug\\'),
127
            fn (string $class) => !str_starts_with($class, 'Yiisoft\\ErrorHandler\\ErrorHandler'),
128
            fn (string $class) => !str_contains($class, '@anonymous'),
129
            fn (string $class) => !is_subclass_of($class, Throwable::class),
130
        ];
131
        foreach ($patterns as $patternFunction) {
132
            $inspected = array_filter($inspected, $patternFunction);
133
        }
134
135
        foreach ($inspected as $className) {
136
            $class = new ReflectionClass($className);
137
138
            if ($class->isInternal()) {
139
                continue;
140
            }
141
142
            $classes[] = $className;
143
        }
144
        sort($classes);
145
146
        return $this->responseFactory->createResponse($classes);
147
    }
148
149
    public function object(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
150
    {
151
        $request = $request->getQueryParams();
152
        $className = $request['classname'];
153
154
        $class = new ReflectionClass($className);
155
156
        if ($class->isInternal()) {
157
            throw new InvalidArgumentException('error');
158
        }
159
160
        $variable = $container->get($className);
161
        $result = VarDumper::create($variable)->asJson();
162
163
        return $this->responseFactory->createResponse(json_decode($result, null, 512, JSON_THROW_ON_ERROR));
164
    }
165
166
    public function command(ServerRequestInterface $request, ContainerInterface $container): ResponseInterface
167
    {
168
        // TODO: would be great to recognise test engine automatically
169
        $map = [
170
            'test/phpunit' => PHPUnitCommand::class,
171
            'test/codeception' => CodeceptionCommand::class,
172
            'analyse/psalm' => PsalmCommand::class,
173
        ];
174
175
        $request = $request->getQueryParams();
176
        $commandName = $request['command'] ?? 'test/codeception';
177
178
        if (!array_key_exists($commandName, $map)) {
179
            throw new InvalidArgumentException('Unknown command');
180
        }
181
182
        $result = $container->get($map[$commandName])->run();
183
184
        return $this->responseFactory->createResponse($result);
185
    }
186
187
    private function removeBasePath(string $rootPath, string $path): string|array|null
188
    {
189
        return preg_replace(
190
            '/^' . preg_quote($rootPath, '/') . '/',
191
            '',
192
            $path,
193
            1
194
        );
195
    }
196
197
    private function getUserOwner(int $uid): array
198
    {
199
        if ($uid ===0 || !function_exists('posix_getpwuid') || false === ($info = posix_getpwuid($uid))) {
200
            return [
201
                'id' => $uid,
202
            ];
203
        }
204
        return [
205
            'uid' => $info['uid'],
206
            'gid' => $info['gid'],
207
            'name' => $info['name'],
208
        ];
209
    }
210
211
    private function getGroupOwner(int $gid): array
212
    {
213
        if ($gid ===0 || !function_exists('posix_getgrgid') || false === ($info = posix_getgrgid($gid))) {
214
            return [
215
                'id' => $gid,
216
            ];
217
        }
218
        return [
219
            'gid' => $info['gid'],
220
            'name' => $info['name'],
221
        ];
222
    }
223
224
    private function serializeFileInfo(SplFileInfo $file): array
225
    {
226
        return [
227
            'baseName' => $file->getBasename(),
228
            'extension' => $file->getExtension(),
229
            'user' => $this->getUserOwner((int)$file->getOwner()),
230
            'group' => $this->getGroupOwner((int)$file->getGroup()),
231
            'size' => $file->getSize(),
232
            'type' => $file->getType(),
233
            'permissions' => substr(sprintf('%o', $file->getPerms()), -4),
234
        ];
235
    }
236
}
237