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:13
created

Psr4Validator.php$0 ➔ getActualNamespace()   A

Complexity

Conditions 3

Size

Total Lines 14

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 14
ccs 10
cts 10
cp 1
crap 3
rs 9.7998
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 3
    private $missingPaths = [];
45
46 3
    /**
47 3
     * Psr4Validator constructor.
48 3
     *
49 3
     * @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 3
        $this->ignoreRegexPatterns = $ignoreRegexPatterns;
55
        $this->pathToProjectRoot   = $pathToProjectRoot;
56 3
        $this->decodedComposerJson = $decodedComposerJson;
57 3
    }
58
59 3
    /**
60 1
     * @throws Exception
61
     *
62 3
     * @return array[]
63 1
     */
64
    public function main(): array
65 3
    {
66 1
        $this->loop();
67
        $errors = [];
68 3
        //Actual Errors
69 2
        if ([] !== $this->psr4Errors) {
70
            $errors['PSR-4 Errors:'] = $this->psr4Errors;
71
        }
72 1
        if ([] !== $this->parseErrors) {
73 1
            $errors['Parse Errors:'] = $this->parseErrors;
74
        }
75
        if ([] !== $this->missingPaths) {
76 1
            $errors['Missing Paths:'] = $this->missingPaths;
77
        }
78
        if ([] === $errors) {
79
            return $errors;
80
        }
81
        //Debug Info
82 3
        if ([] !== $this->ignoredFiles) {
83
            $errors['Ignored Files:'] = $this->ignoredFiles;
84 3
        }
85 3
86
        return $errors;
87 3
    }
88
89 3
    /**
90
     * @throws Exception
91 3
     */
92 3
    private function loop(): void
93 1
    {
94
        foreach ($this->yieldPhpFilesToCheck() as list($absPathRoot, $namespaceRoot, $fileInfo)) {
95 3
            $this->check($absPathRoot, $namespaceRoot, $fileInfo);
96 3
        }
97 1
    }
98
99 1
    /**
100 1
     * @throws Exception
101 1
     *
102
     * @return Generator|mixed[]
103
     * @SuppressWarnings(PHPMD.StaticAccess)
104 3
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
105
     */
106 3
    private function yieldPhpFilesToCheck(): Generator
107
    {
108 3
        $json = $this->decodedComposerJson;
109 3
        foreach (['autoload', 'autoload-dev'] as $autoload) {
110 3
            if (!isset($json[$autoload]['psr-4'])) {
111 3
                continue;
112 3
            }
113 3
            $psr4 = $json[$autoload]['psr-4'];
114 3
            foreach ($psr4 as $namespaceRoot => $paths) {
115 3
                if (!\is_array($paths)) {
116
                    $paths = [$paths];
117
                }
118
                foreach ($paths as $path) {
119 3
                    $absPathRoot     = $this->pathToProjectRoot . '/' . $path;
120
                    $realAbsPathRoot = \realpath($absPathRoot);
121
                    if ($realAbsPathRoot === false) {
122 3
                        $this->addMissingPathError($path, $namespaceRoot, $absPathRoot);
123
                        continue;
124 3
                    }
125 3
                    $iterator = $this->getDirectoryIterator($absPathRoot);
126
                    foreach ($iterator as $fileInfo) {
127
                        if ($fileInfo->getExtension() !== 'php') {
128 3
                            continue;
129 3
                        }
130 1
                        foreach ($this->ignoreRegexPatterns as $pattern) {
131
                            $path = (string)$fileInfo->getRealPath();
132 1
                            if (\preg_match($pattern, $path) === 1) {
133
                                $this->ignoredFiles[] = $path;
134
                                continue 2;
135 3
                            }
136
                        }
137
                        yield [
138
                            $absPathRoot,
139
                            $namespaceRoot,
140
                            $fileInfo,
141
                        ];
142
                    }
143 3
                }
144
            }
145 3
        }
146 3
    }
147 3
148
    private function addMissingPathError(string $path, string $namespaceRoot, string $absPathRoot): void
149 3
    {
150 3
        $invalidPathMessage = "Namespace root '{$namespaceRoot}'\ncontains a path '{$path}'\nwhich doesn't exist\n";
151 3
        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 3
    }
157
158 3
    /**
159 3
     * @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 3
            RecursiveIteratorIterator::SELF_FIRST
170
        );
171 3
172
        return new class ($iterator) extends SplHeap {
173
            public function __construct(RecursiveIteratorIterator $iterator)
174
            {
175
                foreach ($iterator as $item) {
176 1
                    $this->insert($item);
177
                }
178 1
            }
179 1
180
            /**
181 1
             * @param mixed $item1
182
             * @param mixed $item2
183 1
             */
184 1
            protected function compare($item1, $item2): int
185
            {
186
                return strcmp((string)$item2->getRealPath(), (string)$item1->getRealPath());
187
            }
188
        };
189
    }
190
191
    private function check(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): void
192 3
    {
193
        $actualNamespace = $this->getActualNamespace($fileInfo);
194 3
        if ($actualNamespace === '') {
195 3
            return;
196 3
        }
197 1
        $expectedNamespace = $this->expectedFileNamespace($absPathRoot, $namespaceRoot, $fileInfo);
198
        if ($actualNamespace !== $expectedNamespace) {
199 3
            $this->psr4Errors[$namespaceRoot][] =
200 3
                [
201 3
                    'fileInfo'          => $fileInfo->getRealPath(),
202 1
                    'expectedNamespace' => $expectedNamespace,
203
                    'actualNamespace'   => $actualNamespace,
204 3
                ];
205 3
        }
206 3
    }
207 3
208 1
    private function getActualNamespace(SplFileInfo $fileInfo): string
209 1
    {
210
        $contents = \file_get_contents($fileInfo->getPathname());
211 3
        if ($contents === false) {
212 3
            throw new RuntimeException('Failed getting file contents for ' . $fileInfo->getPathname());
213 3
        }
214 3
        \preg_match('%namespace\s+?([^;]+)%', $contents, $matches);
215
        if ([] === $matches) {
216 3
            $this->parseErrors[] = (string)$fileInfo->getRealPath();
217 1
218 1
            return '';
219 1
        }
220
221
        return $matches[1];
222
    }
223 3
224 3
    private function expectedFileNamespace(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): string
225 3
    {
226
        $relativePath = \substr($fileInfo->getPathname(), \strlen($absPathRoot));
227
        $relativeDir  = \dirname($relativePath);
228
        $relativeNs   = '';
229
        if ($relativeDir !== '.') {
230
            $relativeNs = \str_replace(
231 3
                '/',
232
                '\\',
233
                \ltrim($relativeDir, '/')
234
            );
235
        }
236
237
        return rtrim($namespaceRoot . $relativeNs, '\\');
238
    }
239
}
240