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.
Passed
Pull Request — master (#22)
by joseph
02:08
created

Psr4Validator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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