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 (#102)
by joseph
02:44
created

Psr4Validator::getDirectoryIterator()

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 1 Features 1
Metric Value
eloc 10
c 7
b 1
f 1
nc 1
nop 1
dl 0
loc 26
ccs 6
cts 6
cp 1

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Psr4Validator.php$0 ➔ __construct() 0 4 2
A Psr4Validator.php$0 ➔ compare() 0 3 1
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 [$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
     * @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
            public function __construct(RecursiveIteratorIterator $iterator)
175
            {
176 3
                foreach ($iterator as $item) {
177 3
                    $this->insert($item);
178
                }
179 3
            }
180
181
            /**
182
             * @param SplFileInfo|mixed $item1
183
             * @param SplFileInfo|mixed $item2
184
             */
185 3
            protected function compare($item1, $item2): int
186
            {
187 3
                return strcmp((string)$item2->getRealPath(), (string)$item1->getRealPath());
188
            }
189
        };
190
    }
191
192 3
    private function check(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): void
193
    {
194 3
        $actualNamespace = $this->getActualNamespace($fileInfo);
195 3
        if ($actualNamespace === '') {
196 1
            return;
197
        }
198 3
        $expectedNamespace = $this->expectedFileNamespace($absPathRoot, $namespaceRoot, $fileInfo);
199 3
        if ($actualNamespace !== $expectedNamespace) {
200 1
            $this->psr4Errors[$namespaceRoot][] =
201
                [
202 1
                    'fileInfo'          => $fileInfo->getRealPath(),
203 1
                    'expectedNamespace' => $expectedNamespace,
204 1
                    'actualNamespace'   => $actualNamespace,
205
                ];
206
        }
207 3
    }
208
209 3
    private function getActualNamespace(SplFileInfo $fileInfo): string
210
    {
211 3
        $contents = \file_get_contents($fileInfo->getPathname());
212 3
        if ($contents === false) {
213
            throw new RuntimeException('Failed getting file contents for ' . $fileInfo->getPathname());
214
        }
215 3
        $matches = null;
216 3
        \preg_match('%namespace\s+?([^;]+)%', $contents, $matches);
217 3
        if ([] === $matches) {
218 1
            $this->parseErrors[] = (string)$fileInfo->getRealPath();
219
220 1
            return '';
221
        }
222
223 3
        return $matches[1];
224
    }
225
226 3
    private function expectedFileNamespace(string $absPathRoot, string $namespaceRoot, SplFileInfo $fileInfo): string
227
    {
228 3
        $relativePath = \substr($fileInfo->getPathname(), \strlen($absPathRoot));
229 3
        $relativeDir  = \dirname($relativePath);
230 3
        $relativeNs   = '';
231 3
        if ($relativeDir !== '.') {
232 3
            $relativeNs = \str_replace(
233 3
                '/',
234 3
                '\\',
235 3
                \ltrim($relativeDir, '/')
236
            );
237
        }
238
239 3
        return rtrim($namespaceRoot . $relativeNs, '\\');
240
    }
241
}
242