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

Psr4Validator   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Test Coverage

Coverage 98.98%

Importance

Changes 0
Metric Value
eloc 93
dl 0
loc 221
ccs 97
cts 98
cp 0.9898
c 0
b 0
f 0
rs 10
wmc 17

14 Methods

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