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 (#194)
by joseph
26:34
created

FileOverrider::setPathToProjectRoot()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.008

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 5
cp 0.8
crap 1.008
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PostProcessor;
4
5
/**
6
 * This class provides the necessary functionality to allow you to maintain a set of file overrides and to safely apply
7
 * them as part of a post process to your main build process
8
 */
9
class FileOverrider
10
{
11
    /**
12
     * The default path to the overrides folder, relative to the project root
13
     */
14
    public const OVERRIDES_PATH = '/build/overrides';
15
16
    private const EXTENSION_LENGTH_NO_HASH_IN_PROJECT     = 4;
17
    private const EXTENSION_LENGTH_WITH_HASH_IN_OVERRIDES = 46;
18
    private const OVERRIDE_EXTENSION                      = 'override';
19
20
    /**
21
     * @var string
22
     */
23
    private $pathToProjectRoot;
24
25
    /**
26
     * @var string
27
     */
28
    private $pathToOverridesDirectory;
29
30 5
    public function __construct(
31
        string $pathToProjectRoot = null,
32
        string $relativePathToOverridesDirectory = self::OVERRIDES_PATH
33
    ) {
34 5
        if (null !== $pathToProjectRoot) {
35 5
            $this->setPathToProjectRoot($pathToProjectRoot);
36 5
            $this->setPathToOverridesDirectory($this->pathToProjectRoot . '/' . $relativePathToOverridesDirectory);
37
        }
38 5
    }
39
40
    /**
41
     * @param string $pathToProjectRoot
42
     *
43
     * @return $this
44
     * @throws \RuntimeException
45
     */
46 5
    public function setPathToProjectRoot(string $pathToProjectRoot): self
47
    {
48 5
        $this->pathToProjectRoot = $this->getRealPath($pathToProjectRoot);
49 5
        $this->setPathToOverridesDirectory($this->pathToProjectRoot . self::OVERRIDES_PATH);
50
51 5
        return $this;
52
    }
53
54 5
    private function getRealPath(string $path): string
55
    {
56 5
        $realPath = \realpath($path);
57 5
        if (false === $realPath) {
58
            if (!mkdir($path, 0777, true) && !is_dir($path)) {
59
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
60
            }
61
            $realPath = realpath($path);
62
        }
63
64 5
        return $realPath;
65
    }
66
67
    /**
68
     * Create a new Override File by copying the file from the project into the project's overrides directory
69
     *
70
     * @param string $pathToFileInProject
71
     *
72
     * @return string
73
     */
74 2
    public function createNewOverride(string $pathToFileInProject): string
75
    {
76 2
        $relativePathToFileInProject = $this->getRelativePathToFile($pathToFileInProject);
77 2
        if (null !== $this->getOverrideForPath($relativePathToFileInProject)) {
78 1
            throw new \RuntimeException('Override already exists for path ' . $relativePathToFileInProject);
79
        }
80
        $overridePath        =
81 1
            $this->getOverrideDirectoryForFile($relativePathToFileInProject) .
82 1
            '/' . $this->getFileNameNoExtensionForPathInProject($relativePathToFileInProject) .
83 1
            '.' . $this->getProjectFileHash($relativePathToFileInProject) .
84 1
            '.php.override';
85 1
        $pathToFileInProject = $this->pathToProjectRoot . '/' . $relativePathToFileInProject;
86 1
        if (false === is_file($pathToFileInProject)) {
87
            throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
88
        }
89 1
        copy($pathToFileInProject, $overridePath);
90
91 1
        return $this->getRelativePathToFile($overridePath);
92
    }
93
94 5
    private function getRelativePathToFile(string $pathToFileInProject): string
95
    {
96 5
        return str_replace($this->pathToProjectRoot, '', $this->getRealPath($pathToFileInProject));
97
    }
98
99 2
    private function getOverrideForPath(string $relativePathToFileInProject): ?string
100
    {
101 2
        $fileDirectory       = $this->getOverrideDirectoryForFile($relativePathToFileInProject);
102 2
        $fileNameNoExtension = $this->getFileNameNoExtensionForPathInProject($relativePathToFileInProject);
103 2
        $filesInDirectory    = glob("$fileDirectory/$fileNameNoExtension*" . self::OVERRIDE_EXTENSION);
104 2
        if ([] === $filesInDirectory) {
105 1
            return null;
106
        }
107 1
        if (1 === count($filesInDirectory)) {
108 1
            return $fileDirectory . '/' . current($filesInDirectory);
109
        }
110
        throw new \RuntimeException(
111
            'Found more than one override in path ' . $fileDirectory . ': '
112
            . print_r($filesInDirectory, true)
113
        );
114
    }
115
116 2
    private function getOverrideDirectoryForFile(string $relativePathToFileInProject): string
117
    {
118 2
        $path = $this->getPathToOverridesDirectory() . \dirname($relativePathToFileInProject);
119 2
        if (!is_dir($path) && !(mkdir($path, 0777, true) && is_dir($path))) {
120
            throw new \RuntimeException('Failed making override directory path ' . $path);
121
        }
122
123 2
        return $this->getRealPath($path);
124
    }
125
126
    /**
127
     * @return string
128
     */
129 5
    public function getPathToOverridesDirectory(): string
130
    {
131 5
        return $this->getRealPath($this->pathToOverridesDirectory);
132
    }
133
134
    /**
135
     * @param string $pathToOverridesDirectory
136
     *
137
     * @return FileOverrider
138
     */
139 5
    public function setPathToOverridesDirectory(string $pathToOverridesDirectory): FileOverrider
140
    {
141 5
        $this->pathToOverridesDirectory = $this->getRealPath($pathToOverridesDirectory);
142
143 5
        return $this;
144
    }
145
146 2
    private function getFileNameNoExtensionForPathInProject(string $relativePathToFileInProject): string
147
    {
148 2
        $fileName = basename($relativePathToFileInProject);
149
150 2
        return substr($fileName, 0, -self::EXTENSION_LENGTH_NO_HASH_IN_PROJECT);
151
    }
152
153 3
    private function getProjectFileHash(string $relativePathToFileInProject): string
154
    {
155 3
        return $this->getFileHash($this->pathToProjectRoot . '/' . $relativePathToFileInProject);
156
    }
157
158 4
    private function getFileHash(string $path): string
159
    {
160 4
        $contents = \ts\file_get_contents($path);
161
162 4
        return md5($contents);
163
    }
164
165
    /**
166
     * Loop over all the override files and update with the file contents from the project
167
     *
168
     * @return array[] the file paths that have been updated
169
     */
170 1
    public function updateOverrideFiles(): array
171
    {
172 1
        $filesUpdated = [];
173 1
        $fileSame     = [];
174 1
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
175 1
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
176 1
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
177
                $fileSame[] = $relativePathToFileInProject;
178
                continue;
179
            }
180 1
            $pathToFileInProject = $this->pathToProjectRoot . $relativePathToFileInProject;
181 1
            if (false === is_file($pathToFileInProject)) {
182
                throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
183
            }
184 1
            copy($pathToFileInProject, $pathToFileInOverrides);
185 1
            $filesUpdated[] = $relativePathToFileInProject;
186
        }
187
188 1
        return [$this->sortFiles($filesUpdated), $this->sortFiles($fileSame)];
189
    }
190
191
    /**
192
     * Yield file paths in the override folder
193
     *
194
     * @return \Generator|string[]
195
     */
196 3
    private function getOverridesIterator(): \Generator
197
    {
198
        try {
199 3
            $recursiveIterator = new \RecursiveIteratorIterator(
200 3
                new \RecursiveDirectoryIterator(
201 3
                    $this->getPathToOverridesDirectory(),
202 3
                    \RecursiveDirectoryIterator::SKIP_DOTS
203
                ),
204 3
                \RecursiveIteratorIterator::SELF_FIRST
205
            );
206 3
            foreach ($recursiveIterator as $fileInfo) {
207
                /**
208
                 * @var \SplFileInfo $fileInfo
209
                 */
210 3
                if ($fileInfo->isFile()) {
211 3
                    if (self::OVERRIDE_EXTENSION !== substr(
212 3
                        $fileInfo->getFilename(),
213 3
                        -strlen(self::OVERRIDE_EXTENSION)
214
                    )
215
                    ) {
216
                        continue;
217
                    }
218 3
                    yield $fileInfo->getPathname();
219
                }
220
            }
221 3
        } finally {
222 3
            $recursiveIterator = null;
223 3
            unset($recursiveIterator);
224
        }
225 3
    }
226
227 3
    private function getRelativePathInProjectFromOverridePath(string $pathToFileInOverrides): string
228
    {
229 3
        $relativePath = substr($pathToFileInOverrides, strlen($this->getPathToOverridesDirectory()));
230 3
        $relativeDir  = dirname($relativePath);
231 3
        $filename     = basename($pathToFileInOverrides);
232 3
        $filename     = substr($filename, 0, -self::EXTENSION_LENGTH_WITH_HASH_IN_OVERRIDES) . '.php';
233
234 3
        return $this->getRelativePathToFile(
235 3
            $this->getRealPath($this->pathToProjectRoot . '/' . $relativeDir . '/' . $filename)
236
        );
237
    }
238
239
    /**
240
     * Is the file in the project the same as the override file already?
241
     *
242
     * @param string $pathToFileInOverrides
243
     *
244
     * @return bool
245
     */
246 2
    private function projectFileIsSameAsOverride(string $pathToFileInOverrides): bool
247
    {
248 2
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
249
250 2
        return $this->getFileHash($this->pathToProjectRoot . '/' . $relativePathToFileInProject) ===
251 2
               $this->getFileHash($pathToFileInOverrides);
252
    }
253
254 2
    private function sortFiles(array $files): array
255
    {
256 2
        sort($files, SORT_STRING);
257
258 2
        return $files;
259
    }
260
261
    /**
262
     * Loop over all the override files and copy into the project
263
     *
264
     * @return array[] the file paths that have been updated
265
     */
266 2
    public function applyOverrides(): array
267
    {
268 2
        $filesUpdated = [];
269 2
        $filesSame    = [];
270 2
        $errors       = [];
271 2
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
272 2
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
273 2
            if ($this->overrideFileHashIsCorrect($pathToFileInOverrides)) {
274 1
                if (false === is_file($pathToFileInOverrides)) {
275
                    throw new \RuntimeException('path ' . $pathToFileInOverrides . ' is not a file');
276
                }
277 1
                copy($pathToFileInOverrides, $this->pathToProjectRoot . $relativePathToFileInProject);
278 1
                $filesUpdated[] = $relativePathToFileInProject;
279 1
                continue;
280
            }
281 1
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
282
                $filesSame[] = $relativePathToFileInProject;
283
                continue;
284
            }
285 1
            $errors[$pathToFileInOverrides] = $this->getProjectFileHash($relativePathToFileInProject);
286
        }
287 2
        if ([] !== $errors) {
288 1
            throw new \RuntimeException('These file hashes were not up to date:' . print_r($errors, true));
289
        }
290
291 1
        return [$this->sortFiles($filesUpdated), $this->sortFiles($filesSame)];
292
    }
293
294 2
    private function overrideFileHashIsCorrect(string $pathToFileInOverrides): bool
295
    {
296 2
        $filenameParts = explode('.', basename($pathToFileInOverrides));
297 2
        if (4 !== count($filenameParts)) {
298
            throw new \RuntimeException('Invalid override filename ' . $pathToFileInOverrides);
299
        }
300 2
        $hash                        = $filenameParts[1];
301 2
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
302
303 2
        return $hash === $this->getProjectFileHash($relativePathToFileInProject);
304
    }
305
}
306