Passed
Pull Request — master (#60)
by Dmitriy
02:48
created

InspectController::getUserOwner()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 11
ccs 0
cts 6
cp 0
rs 10
cc 4
nc 2
nop 1
crap 20
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
                array_merge([
72
                    'directory' => $this->removeBasePath($rootPath, dirname($destination)),
73
                    'content' => file_get_contents($destination),
74
                    'path' => $this->removeBasePath($rootPath, $destination),
75
                    'absolutePath' => $destination,
76
                ],
77
                    $this->serializeFileInfo($file)
78
                )
79
            );
80
        }
81
82
        /**
83
         * @var $directoryIterator SplFileInfo[]
84
         */
85
        $directoryIterator = new RecursiveDirectoryIterator(
86
            $destination,
87
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
88
        );
89
90
        $files = [];
91
        foreach ($directoryIterator as $file) {
92
            if ($file->getBasename() === '.') {
93
                continue;
94
            }
95
96
            $path = $file->getPathName();
97
            if ($file->isDir()) {
98
                if ($file->getBasename() === '..') {
99
                    $path = realpath($path);
100
                }
101
                $path .= '/';
102
            }
103
            /**
104
             * Check if path is inside the application directory
105
             */
106
            if (!str_starts_with($path, $rootPath)) {
107
                continue;
108
            }
109
            $path = $this->removeBasePath($rootPath, $path);
110
            $files[] = array_merge([
111
                'path' => $path,
112
            ],
113
                $this->serializeFileInfo($file)
114
            );
115
        }
116
117
        return $this->responseFactory->createResponse($files);
118
    }
119
120
    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

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