Passed
Pull Request — master (#60)
by Alexander
05:28 queued 02:37
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
                    [
73
                        'directory' => $this->removeBasePath($rootPath, dirname($destination)),
74
                        'content' => file_get_contents($destination),
75
                        'path' => $this->removeBasePath($rootPath, $destination),
76
                        'absolutePath' => $destination,
77
                    ],
78
                    $this->serializeFileInfo($file)
79
                )
80
            );
81
        }
82
83
        /**
84
         * @var $directoryIterator SplFileInfo[]
85
         */
86
        $directoryIterator = new RecursiveDirectoryIterator(
87
            $destination,
88
            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
89
        );
90
91
        $files = [];
92
        foreach ($directoryIterator as $file) {
93
            if ($file->getBasename() === '.') {
94
                continue;
95
            }
96
97
            $path = $file->getPathName();
98
            if ($file->isDir()) {
99
                if ($file->getBasename() === '..') {
100
                    $path = realpath($path);
101
                }
102
                $path .= '/';
103
            }
104
            /**
105
             * Check if path is inside the application directory
106
             */
107
            if (!str_starts_with($path, $rootPath)) {
108
                continue;
109
            }
110
            $path = $this->removeBasePath($rootPath, $path);
111
            $files[] = array_merge(
112
                [
113
                    'path' => $path,
114
                ],
115
                $this->serializeFileInfo($file)
116
            );
117
        }
118
119
        return $this->responseFactory->createResponse($files);
120
    }
121
122
    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

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