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.
Completed
Pull Request — master (#99)
by joseph
03:46
created

Psr4Validator.php$0 ➔ check()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 13
ccs 10
cts 10
cp 1
crap 3
rs 9.8333
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
     * @throws Exception
61
     *
62
     * @return array[]
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 list($absPathRoot, $namespaceRoot, $fileInfo)) {
95
            $this->check($absPathRoot, $namespaceRoot, $fileInfo);
96
        }
97
    }
98
99
    /**
100
     * @throws Exception
101
     *
102
     * @return Generator|mixed[]
103
     * @SuppressWarnings(PHPMD.StaticAccess)
104
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
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
     */
161 3
    private function getDirectoryIterator(string $realPath): SplHeap
162
    {
163
        $directoryIterator = new RecursiveDirectoryIterator(
164
            $realPath,
165
            RecursiveDirectoryIterator::SKIP_DOTS
166
        );
167
        $iterator          = new RecursiveIteratorIterator(
168
            $directoryIterator,
169
            RecursiveIteratorIterator::SELF_FIRST
170
        );
171
172
        return new class ($iterator) extends SplHeap {
173
            public function __construct(RecursiveIteratorIterator $iterator)
174
            {
175 3
                foreach ($iterator as $item) {
176 3
                    $this->insert($item);
177
                }
178 3
            }
179
180
            /**
181
             * @param mixed $item1
182
             * @param mixed $item2
183
             */
184 3
            protected function compare($item1, $item2): int
185
            {
186 3
                return strcmp((string)$item2->getRealPath(), (string)$item1->getRealPath());
187
            }
188
        };
189
    }
190
191 3
    private function check(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): void
192
    {
193 3
        $actualNamespace = $this->getActualNamespace($fileInfo);
194 3
        if ($actualNamespace === '') {
195 1
            return;
196
        }
197 3
        $expectedNamespace = $this->expectedFileNamespace($absPathRoot, $namespaceRoot, $fileInfo);
198 3
        if ($actualNamespace !== $expectedNamespace) {
199 1
            $this->psr4Errors[$namespaceRoot][] =
200
                [
201 1
                    'fileInfo'          => $fileInfo->getRealPath(),
202 1
                    'expectedNamespace' => $expectedNamespace,
203 1
                    'actualNamespace'   => $actualNamespace,
204
                ];
205
        }
206 3
    }
207
208 3
    private function getActualNamespace(SplFileInfo $fileInfo): string
209
    {
210 3
        $contents = \file_get_contents($fileInfo->getPathname());
211 3
        if ($contents === false) {
212
            throw new RuntimeException('Failed getting file contents for ' . $fileInfo->getPathname());
213
        }
214 3
        \preg_match('%namespace\s+?([^;]+)%', $contents, $matches);
215 3
        if ([] === $matches) {
216 1
            $this->parseErrors[] = (string)$fileInfo->getRealPath();
217
218 1
            return '';
219
        }
220
221 3
        return $matches[1];
222
    }
223
224 3
    private function expectedFileNamespace(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): string
225
    {
226 3
        $relativePath = \substr($fileInfo->getPathname(), \strlen($absPathRoot));
227 3
        $relativeDir  = \dirname($relativePath);
228 3
        $relativeNs   = '';
229 3
        if ($relativeDir !== '.') {
230 3
            $relativeNs = \str_replace(
231 3
                '/',
232 3
                '\\',
233 3
                \ltrim($relativeDir, '/')
234
            );
235
        }
236
237 3
        return rtrim($namespaceRoot . $relativeNs, '\\');
238
    }
239
}
240