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 (#154)
by joseph
25:46
created

getRelativePathInProjectFromOverridePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1.0019

Importance

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