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 (#199)
by joseph
21:49
created

FileOverrider::overrideFileHashIsCorrect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 6
cts 9
cp 0.6667
crap 2.1481
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PostProcessor;
4
5
use SebastianBergmann\Diff\Differ;
6
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
7
8
/**
9
 * This class provides the necessary functionality to allow you to maintain a set of file overrides and to safely apply
10
 * them as part of a post process to your main build process
11
 */
12
class FileOverrider
13
{
14
    /**
15
     * The default path to the overrides folder, relative to the project root
16
     */
17
    public const OVERRIDES_PATH = '/build/overrides';
18
19
    private const EXTENSION_LENGTH_NO_HASH_IN_PROJECT     = 4;
20
    private const EXTENSION_LENGTH_WITH_HASH_IN_OVERRIDES = 46;
21
    private const OVERRIDE_EXTENSION                      = 'override';
22
23
    /**
24
     * @var string
25
     */
26
    private $pathToProjectRoot;
27
28
    /**
29
     * @var string
30
     */
31
    private $pathToOverridesDirectory;
32
33
    /**
34
     * @var Differ
35
     */
36
    private $differ;
37
38 5
    public function __construct(
39
        string $pathToProjectRoot = null,
40
        string $relativePathToOverridesDirectory = self::OVERRIDES_PATH
41
    ) {
42 5
        if (null !== $pathToProjectRoot) {
43 5
            $this->setPathToProjectRoot($pathToProjectRoot);
44 5
            $this->setPathToOverridesDirectory($this->pathToProjectRoot . '/' . $relativePathToOverridesDirectory);
45
        }
46 5
        $builder      = new DiffOnlyOutputBuilder(
47 5
            "--- Original\n+++ New\n"
48
        );
49 5
        $this->differ = new Differ($builder);
50 5
    }
51
52
    /**
53
     * @param string $pathToProjectRoot
54
     *
55
     * @return $this
56
     * @throws \RuntimeException
57
     */
58 5
    public function setPathToProjectRoot(string $pathToProjectRoot): self
59
    {
60 5
        $this->pathToProjectRoot = $this->getRealPath($pathToProjectRoot);
61 5
        $this->setPathToOverridesDirectory($this->pathToProjectRoot . self::OVERRIDES_PATH);
62
63 5
        return $this;
64
    }
65
66 5
    private function getRealPath(string $path): string
67
    {
68 5
        $realPath = \realpath($path);
69 5
        if (false === $realPath) {
70
            if (!mkdir($path, 0777, true) && !is_dir($path)) {
71
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
72
            }
73
            $realPath = realpath($path);
74
        }
75
76 5
        return $realPath;
77
    }
78
79
    public function recreateOverride(string $relativePathToFileInOverrides): array
80
    {
81
        $overridePath = $this->cleanPath($this->pathToProjectRoot . '/' . $relativePathToFileInOverrides);
82
83
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($overridePath);
84
85
        $old = $relativePathToFileInOverrides . '-old';
86
        rename($overridePath, $overridePath . '-old');
87
88
        $new = $this->createNewOverride($this->pathToProjectRoot . '/' . $relativePathToFileInProject);
89
90
        return [$old, $new];
91
    }
92
93 3
    private function cleanPath(string $path): string
94
    {
95 3
        return preg_replace('%/{2,}%', '/', $path);
96
    }
97
98 3
    private function getRelativePathInProjectFromOverridePath(string $pathToFileInOverrides): string
99
    {
100 3
        $pathToFileInOverrides = $this->cleanPath($pathToFileInOverrides);
101 3
        $relativePath          = substr($pathToFileInOverrides, strlen($this->getPathToOverridesDirectory()));
102 3
        $relativeDir           = dirname($relativePath);
103 3
        $filename              = basename($pathToFileInOverrides);
104 3
        $filename              = substr($filename, 0, -self::EXTENSION_LENGTH_WITH_HASH_IN_OVERRIDES) . '.php';
105
106 3
        return $this->getRelativePathToFile(
107 3
            $this->getRealPath($this->pathToProjectRoot . '/' . $relativeDir . '/' . $filename)
108
        );
109
    }
110
111
    /**
112
     * @return string
113
     */
114 5
    public function getPathToOverridesDirectory(): string
115
    {
116 5
        return $this->getRealPath($this->pathToOverridesDirectory);
117
    }
118
119
    /**
120
     * @param string $pathToOverridesDirectory
121
     *
122
     * @return FileOverrider
123
     */
124 5
    public function setPathToOverridesDirectory(string $pathToOverridesDirectory): FileOverrider
125
    {
126 5
        $this->pathToOverridesDirectory = $this->getRealPath($pathToOverridesDirectory);
127
128 5
        return $this;
129
    }
130
131 5
    private function getRelativePathToFile(string $pathToFileInProject): string
132
    {
133 5
        return str_replace($this->pathToProjectRoot, '', $this->getRealPath($pathToFileInProject));
134
    }
135
136
    /**
137
     * Create a new Override File by copying the file from the project into the project's overrides directory
138
     *
139
     * @param string $pathToFileInProject
140
     *
141
     * @return string
142
     */
143 2
    public function createNewOverride(string $pathToFileInProject): string
144
    {
145 2
        $relativePathToFileInProject = $this->getRelativePathToFile($pathToFileInProject);
146 2
        if (null !== $this->getOverrideForPath($relativePathToFileInProject)) {
147 1
            throw new \RuntimeException('Override already exists for path ' . $relativePathToFileInProject);
148
        }
149
        $overridePath        =
150 1
            $this->getOverrideDirectoryForFile($relativePathToFileInProject) .
151 1
            '/' . $this->getFileNameNoExtensionForPathInProject($relativePathToFileInProject) .
152 1
            '.' . $this->getProjectFileHash($relativePathToFileInProject) .
153 1
            '.php.override';
154 1
        $pathToFileInProject = $this->pathToProjectRoot . '/' . $relativePathToFileInProject;
155 1
        if (false === is_file($pathToFileInProject)) {
156
            throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
157
        }
158 1
        copy($pathToFileInProject, $overridePath);
159
160 1
        return $this->getRelativePathToFile($overridePath);
161
    }
162
163 2
    private function getOverrideForPath(string $relativePathToFileInProject): ?string
164
    {
165 2
        $fileDirectory       = $this->getOverrideDirectoryForFile($relativePathToFileInProject);
166 2
        $fileNameNoExtension = $this->getFileNameNoExtensionForPathInProject($relativePathToFileInProject);
167 2
        $filesInDirectory    = glob("$fileDirectory/$fileNameNoExtension*" . self::OVERRIDE_EXTENSION);
168 2
        if ([] === $filesInDirectory) {
169 1
            return null;
170
        }
171 1
        if (1 === count($filesInDirectory)) {
0 ignored issues
show
Bug introduced by
It seems like $filesInDirectory can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
        if (1 === count(/** @scrutinizer ignore-type */ $filesInDirectory)) {
Loading history...
172 1
            return $fileDirectory . '/' . current($filesInDirectory);
0 ignored issues
show
Bug introduced by
It seems like $filesInDirectory can also be of type false; however, parameter $array of current() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
            return $fileDirectory . '/' . current(/** @scrutinizer ignore-type */ $filesInDirectory);
Loading history...
173
        }
174
        throw new \RuntimeException(
175
            'Found more than one override in path ' . $fileDirectory . ': '
176
            . print_r($filesInDirectory, true)
177
        );
178
    }
179
180 2
    private function getOverrideDirectoryForFile(string $relativePathToFileInProject): string
181
    {
182 2
        $path = $this->getPathToOverridesDirectory() . \dirname($relativePathToFileInProject);
183 2
        if (!is_dir($path) && !(mkdir($path, 0777, true) && is_dir($path))) {
184
            throw new \RuntimeException('Failed making override directory path ' . $path);
185
        }
186
187 2
        return $this->getRealPath($path);
188
    }
189
190 2
    private function getFileNameNoExtensionForPathInProject(string $relativePathToFileInProject): string
191
    {
192 2
        $fileName = basename($relativePathToFileInProject);
193
194 2
        return substr($fileName, 0, -self::EXTENSION_LENGTH_NO_HASH_IN_PROJECT);
195
    }
196
197 3
    private function getProjectFileHash(string $relativePathToFileInProject): string
198
    {
199 3
        return $this->getFileHash($this->pathToProjectRoot . '/' . $relativePathToFileInProject);
200
    }
201
202 4
    private function getFileHash(string $path): string
203
    {
204 4
        $contents = \ts\file_get_contents($path);
205
206 4
        return md5($contents);
207
    }
208
209
    /**
210
     * Loop over all the override files and update with the file contents from the project
211
     *
212
     * @return array[] the file paths that have been updated
213
     */
214 1
    public function updateOverrideFiles(): array
215
    {
216 1
        $filesUpdated = [];
217 1
        $fileSame     = [];
218 1
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
219 1
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
220 1
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
221
                $fileSame[] = $relativePathToFileInProject;
222
                continue;
223
            }
224 1
            $pathToFileInProject = $this->pathToProjectRoot . $relativePathToFileInProject;
225 1
            if (false === is_file($pathToFileInProject)) {
226
                throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
227
            }
228 1
            copy($pathToFileInProject, $pathToFileInOverrides);
229 1
            $filesUpdated[] = $relativePathToFileInProject;
230
        }
231
232 1
        return [$this->sortFiles($filesUpdated), $this->sortFiles($fileSame)];
233
    }
234
235
    /**
236
     * Yield file paths in the override folder
237
     *
238
     * @return \Generator|string[]
239
     */
240 3
    private function getOverridesIterator(): \Generator
241
    {
242
        try {
243 3
            $recursiveIterator = new \RecursiveIteratorIterator(
244 3
                new \RecursiveDirectoryIterator(
245 3
                    $this->getPathToOverridesDirectory(),
246 3
                    \RecursiveDirectoryIterator::SKIP_DOTS
247
                ),
248 3
                \RecursiveIteratorIterator::SELF_FIRST
249
            );
250 3
            foreach ($recursiveIterator as $fileInfo) {
251
                /**
252
                 * @var \SplFileInfo $fileInfo
253
                 */
254 3
                if ($fileInfo->isFile()) {
255 3
                    if (self::OVERRIDE_EXTENSION !== substr(
256 3
                            $fileInfo->getFilename(),
257 3
                            -strlen(self::OVERRIDE_EXTENSION)
258
                        )
259
                    ) {
260
                        continue;
261
                    }
262 3
                    yield $fileInfo->getPathname();
263
                }
264
            }
265 3
        } finally {
266 3
            $recursiveIterator = null;
267 3
            unset($recursiveIterator);
268
        }
269 3
    }
270
271
    /**
272
     * Is the file in the project the same as the override file already?
273
     *
274
     * @param string $pathToFileInOverrides
275
     *
276
     * @return bool
277
     */
278 2
    private function projectFileIsSameAsOverride(string $pathToFileInOverrides): bool
279
    {
280 2
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
281
282 2
        return $this->getFileHash($this->pathToProjectRoot . '/' . $relativePathToFileInProject) ===
283 2
               $this->getFileHash($pathToFileInOverrides);
284
    }
285
286 2
    private function sortFiles(array $files): array
287
    {
288 2
        sort($files, SORT_STRING);
289
290 2
        return $files;
291
    }
292
293
    /**
294
     * Before applying overrides, we can check for errors and then return useful information
295
     *
296
     * @return array
297
     */
298
    public function getInvalidOverrides(): array
299
    {
300
        $errors = [];
301
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
302
            if ($this->overrideFileHashIsCorrect($pathToFileInOverrides)) {
303
                continue;
304
            }
305
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
306
                continue;
307
            }
308
            $relativePathToOverride      = $this->getRelativePathToFile($pathToFileInOverrides);
309
            $relativePathToFileInProject =
310
                $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
311
312
            $errors[$relativePathToOverride]['overridePath'] = $relativePathToOverride;
313
            $errors[$relativePathToOverride]['projectPath']  = $relativePathToFileInProject;
314
            $errors[$relativePathToOverride]['diff']         = $this->differ->diff(
315
                \ts\file_get_contents($this->pathToProjectRoot . $relativePathToFileInProject),
316
                \ts\file_get_contents($pathToFileInOverrides)
317
            );
318
            $errors[$relativePathToOverride]['new md5']      =
319
                $this->getProjectFileHash($relativePathToFileInProject);
320
        }
321
322
        return $errors;
323
    }
324
325 2
    private function overrideFileHashIsCorrect(string $pathToFileInOverrides): bool
326
    {
327 2
        $filenameParts = explode('.', basename($pathToFileInOverrides));
328 2
        if (4 !== count($filenameParts)) {
329
            throw new \RuntimeException('Invalid override filename ' . $pathToFileInOverrides);
330
        }
331 2
        $hash                        = $filenameParts[1];
332 2
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
333
334 2
        return $hash === $this->getProjectFileHash($relativePathToFileInProject);
335
    }
336
337
    /**
338
     * Loop over all the override files and copy into the project
339
     *
340
     * @return array[] the file paths that have been updated
341
     */
342 2
    public function applyOverrides(): array
343
    {
344 2
        $filesUpdated = [];
345 2
        $filesSame    = [];
346 2
        $errors       = [];
347 2
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
348 2
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
349 2
            if ($this->overrideFileHashIsCorrect($pathToFileInOverrides)) {
350 1
                if (false === is_file($pathToFileInOverrides)) {
351
                    throw new \RuntimeException('path ' . $pathToFileInOverrides . ' is not a file');
352
                }
353 1
                copy($pathToFileInOverrides, $this->pathToProjectRoot . $relativePathToFileInProject);
354 1
                $filesUpdated[] = $relativePathToFileInProject;
355 1
                continue;
356
            }
357 1
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
358
                $filesSame[] = $relativePathToFileInProject;
359
                continue;
360
            }
361 1
            $errors[$pathToFileInOverrides]['diff']    = $this->differ->diff(
362 1
                \ts\file_get_contents($this->pathToProjectRoot . $relativePathToFileInProject),
363 1
                \ts\file_get_contents($pathToFileInOverrides)
364
            );
365 1
            $errors[$pathToFileInOverrides]['new md5'] = $this->getProjectFileHash($relativePathToFileInProject);
366
        }
367 2
        if ([] !== $errors) {
368 1
            throw new \RuntimeException('These file hashes were not up to date:' . print_r($errors, true));
369
        }
370
371 1
        return [$this->sortFiles($filesUpdated), $this->sortFiles($filesSame)];
372
    }
373
}
374