GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Psr4Validator::addMissingPathError()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\PHPQA;
6
7
use Exception;
8
use Generator;
9
use RecursiveDirectoryIterator;
10
use RecursiveIteratorIterator;
11
use RuntimeException;
12
use SplFileInfo;
13
use SplHeap;
14
15
final class Psr4Validator
16
{
17
    /**
18
     * @var string
19
     */
20
    private $pathToProjectRoot;
21
    /**
22
     * @var array|array[]
23
     */
24
    private $decodedComposerJson;
25
    /**
26
     * @var array|string[]
27
     */
28
    private $parseErrors = [];
29
    /**
30
     * @var array|array[]
31
     */
32
    private $psr4Errors = [];
33
    /**
34
     * @var array|string[]
35
     */
36
    private $ignoreRegexPatterns;
37
    /**
38
     * @var array|string[]
39
     */
40
    private $ignoredFiles = [];
41
    /**
42
     * @var array|string[]
43
     */
44
    private $missingPaths = [];
45
46
    /**
47
     * Psr4Validator constructor.
48
     *
49
     * @param array|string[] $ignoreRegexPatterns Set of regex patterns used to exclude files or directories
50
     * @param array|array[]  $decodedComposerJson
51
     */
52
    public function __construct(array $ignoreRegexPatterns, string $pathToProjectRoot, array $decodedComposerJson)
53
    {
54
        $this->ignoreRegexPatterns = $ignoreRegexPatterns;
55
        $this->pathToProjectRoot   = $pathToProjectRoot;
56
        $this->decodedComposerJson = $decodedComposerJson;
57
    }
58
59
    /**
60
     * @return array[]
61
     * @throws Exception
62
     *
63
     */
64
    public function main(): array
65
    {
66
        $this->loop();
67
        $errors = [];
68
        //Actual Errors
69
        if ([] !== $this->psr4Errors) {
70
            $errors['PSR-4 Errors:'] = $this->psr4Errors;
71
        }
72
        if ([] !== $this->parseErrors) {
73
            $errors['Parse Errors:'] = $this->parseErrors;
74
        }
75
        if ([] !== $this->missingPaths) {
76
            $errors['Missing Paths:'] = $this->missingPaths;
77
        }
78
        if ([] === $errors) {
79
            return $errors;
80
        }
81
        //Debug Info
82
        if ([] !== $this->ignoredFiles) {
83
            $errors['Ignored Files:'] = $this->ignoredFiles;
84
        }
85
86
        return $errors;
87
    }
88
89
    /**
90
     * @throws Exception
91
     */
92
    private function loop(): void
93
    {
94
        foreach ($this->yieldPhpFilesToCheck() as [$absPathRoot, $namespaceRoot, $fileInfo]) {
95
            $this->check($absPathRoot, $namespaceRoot, $fileInfo);
96
        }
97
    }
98
99
    /**
100
     * @return Generator|mixed[]
101
     * @SuppressWarnings(PHPMD.StaticAccess)
102
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
103
     * @throws Exception
104
     *
105
     */
106
    private function yieldPhpFilesToCheck(): Generator
107
    {
108
        $json = $this->decodedComposerJson;
109
        foreach (['autoload', 'autoload-dev'] as $autoload) {
110
            if (!isset($json[$autoload]['psr-4'])) {
111
                continue;
112
            }
113
            $psr4 = $json[$autoload]['psr-4'];
114
            foreach ($psr4 as $namespaceRoot => $paths) {
115
                if (!\is_array($paths)) {
116
                    $paths = [$paths];
117
                }
118
                foreach ($paths as $path) {
119
                    $absPathRoot     = $this->pathToProjectRoot . '/' . $path;
120
                    $realAbsPathRoot = \realpath($absPathRoot);
121
                    if ($realAbsPathRoot === false) {
122
                        $this->addMissingPathError($path, $namespaceRoot, $absPathRoot);
123
                        continue;
124
                    }
125
                    $iterator = $this->getDirectoryIterator($absPathRoot);
126
                    foreach ($iterator as $fileInfo) {
127
                        if ($fileInfo->getExtension() !== 'php') {
128
                            continue;
129
                        }
130
                        foreach ($this->ignoreRegexPatterns as $pattern) {
131
                            $path = (string)$fileInfo->getRealPath();
132
                            if (\preg_match($pattern, $path) === 1) {
133
                                $this->ignoredFiles[] = $path;
134
                                continue 2;
135
                            }
136
                        }
137
                        yield [
138
                            $absPathRoot,
139
                            $namespaceRoot,
140
                            $fileInfo,
141
                        ];
142
                    }
143
                }
144
            }
145
        }
146
    }
147
148
    private function addMissingPathError(string $path, string $namespaceRoot, string $absPathRoot): void
149
    {
150
        $invalidPathMessage = "Namespace root '{$namespaceRoot}'\ncontains a path '{$path}'\nwhich doesn't exist\n";
151
        if (stripos($absPathRoot, 'Magento') !== false) {
152
            $invalidPathMessage .= 'Magento\'s composer includes this by default, '
153
                                   . 'it should be removed from the psr-4 section';
154
        }
155
        $this->missingPaths[$path] = $invalidPathMessage;
156
    }
157
158
    /**
159
     * @return SplHeap|SplFileInfo[]
160
     * @SuppressWarnings(PHPMD.UndefinedVariable) - phpmd cant handle the anon class
161
     */
162 3
    private function getDirectoryIterator(string $realPath): SplHeap
163
    {
164
        $directoryIterator = new RecursiveDirectoryIterator(
165
            $realPath,
166
            RecursiveDirectoryIterator::SKIP_DOTS
167
        );
168
        $iterator          = new RecursiveIteratorIterator(
169
            $directoryIterator,
170
            RecursiveIteratorIterator::SELF_FIRST
171
        );
172
173
        return new class ($iterator) extends SplHeap {
174
            /**
175
             *  constructor.
176
             *
177
             * @param RecursiveIteratorIterator<RecursiveDirectoryIterator> $iterator
178
             */
179
            public function __construct(RecursiveIteratorIterator $iterator)
180
            {
181 3
                foreach ($iterator as $item) {
182 3
                    $this->insert($item);
183
                }
184 3
            }
185
186
            /**
187
             * @param SplFileInfo|mixed $item1
188
             * @param SplFileInfo|mixed $item2
189
             */
190 3
            protected function compare($item1, $item2): int
191
            {
192 3
                return strcmp((string)$item2->getRealPath(), (string)$item1->getRealPath());
193
            }
194
        };
195
    }
196
197 3
    private function check(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): void
198
    {
199 3
        $actualNamespace = $this->getActualNamespace($fileInfo);
200 3
        if ($actualNamespace === '') {
201 1
            return;
202
        }
203 3
        $expectedNamespace = $this->expectedFileNamespace($absPathRoot, $namespaceRoot, $fileInfo);
204 3
        if ($actualNamespace !== $expectedNamespace) {
205 1
            $this->psr4Errors[$namespaceRoot][] =
206
                [
207 1
                    'fileInfo'          => $fileInfo->getRealPath(),
208 1
                    'expectedNamespace' => $expectedNamespace,
209 1
                    'actualNamespace'   => $actualNamespace,
210
                ];
211
        }
212 3
    }
213
214 3
    private function getActualNamespace(SplFileInfo $fileInfo): string
215
    {
216 3
        $contents = \file_get_contents($fileInfo->getPathname());
217 3
        if ($contents === false) {
218
            throw new RuntimeException('Failed getting file contents for ' . $fileInfo->getPathname());
219
        }
220 3
        $matches = null;
221 3
        \preg_match('%namespace\s+?([^;]+)%', $contents, $matches);
222 3
        if ([] === $matches) {
223 1
            $this->parseErrors[] = (string)$fileInfo->getRealPath();
224
225 1
            return '';
226
        }
227
228 3
        return $matches[1];
229
    }
230
231 3
    private function expectedFileNamespace(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): string
232
    {
233 3
        $relativePath = \substr($fileInfo->getPathname(), \strlen($absPathRoot));
234 3
        $relativeDir  = \dirname($relativePath);
235 3
        $relativeNs   = '';
236 3
        if ($relativeDir !== '.') {
237 3
            $relativeNs = \str_replace(
238 3
                '/',
239 3
                '\\',
240 3
                \ltrim($relativeDir, '/')
241
            );
242
        }
243
244 3
        return rtrim($namespaceRoot . $relativeNs, '\\');
245
    }
246
}
247