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

Psr4Validator::main()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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