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

FileOverrider   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Test Coverage

Coverage 58.64%

Importance

Changes 0
Metric Value
eloc 119
dl 0
loc 295
ccs 112
cts 191
cp 0.5864
rs 9.0399
c 0
b 0
f 0
wmc 42

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getOverrideDirectoryForFile() 0 8 4
A getRealPath() 0 11 4
A projectFileIsSameAsOverride() 0 6 1
A setPathToOverridesDirectory() 0 5 1
A setPathToProjectRoot() 0 6 1
A getRelativePathToFile() 0 3 1
A getRelativePathInProjectFromOverridePath() 0 9 1
A getOverrideForPath() 0 14 3
A overrideFileHashIsCorrect() 0 10 2
A updateOverrideFiles() 0 19 4
A __construct() 0 7 2
A getFileNameNoExtensionForPathInProject() 0 5 1
A getFileHash() 0 5 1
A getProjectFileHash() 0 3 1
A createNewOverride() 0 18 3
A sortFiles() 0 5 1
A getOverridesIterator() 0 28 4
A getPathToOverridesDirectory() 0 3 1
A applyOverrides() 0 26 6

How to fix   Complexity   

Complex Class

Complex classes like FileOverrider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileOverrider, and based on these observations, apply Extract Interface, too.

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 5
30
    public function __construct(
31
        string $pathToProjectRoot = null,
32
        string $relativePathToOverridesDirectory = self::OVERRIDES_PATH
33 5
    ) {
34 5
        if (null !== $pathToProjectRoot) {
35 5
            $this->setPathToProjectRoot($pathToProjectRoot);
36
            $this->setPathToOverridesDirectory($this->pathToProjectRoot . '/' . $relativePathToOverridesDirectory);
37 5
        }
38
    }
39
40
    /**
41
     * @param string $pathToProjectRoot
42
     *
43
     * @return $this
44
     * @throws \RuntimeException
45 5
     */
46
    public function setPathToProjectRoot(string $pathToProjectRoot): self
47 5
    {
48 5
        $this->pathToProjectRoot = $this->getRealPath($pathToProjectRoot);
49
        $this->setPathToOverridesDirectory($this->pathToProjectRoot . self::OVERRIDES_PATH);
50 5
51
        return $this;
52
    }
53 5
54
    private function getRealPath(string $path): string
55 5
    {
56 5
        $realPath = \realpath($path);
57
        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 5
64
        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 2
     */
74
    public function createNewOverride(string $pathToFileInProject): string
75 2
    {
76 2
        $relativePathToFileInProject = $this->getRelativePathToFile($pathToFileInProject);
77 1
        if (null !== $this->getOverrideForPath($relativePathToFileInProject)) {
78
            throw new \RuntimeException('Override already exists for path ' . $relativePathToFileInProject);
79
        }
80 1
        $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
        if (false === is_file($pathToFileInProject)) {
87
            throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
88 1
        }
89
        copy($pathToFileInProject, $overridePath);
90 1
91
        return $this->getRelativePathToFile($overridePath);
92
    }
93 5
94
    private function getRelativePathToFile(string $pathToFileInProject): string
95 5
    {
96
        return str_replace($this->pathToProjectRoot, '', $this->getRealPath($pathToFileInProject));
97
    }
98 2
99
    private function getOverrideForPath(string $relativePathToFileInProject): ?string
100 2
    {
101 2
        $fileDirectory       = $this->getOverrideDirectoryForFile($relativePathToFileInProject);
102 2
        $fileNameNoExtension = $this->getFileNameNoExtensionForPathInProject($relativePathToFileInProject);
103 2
        $filesInDirectory    = glob("$fileDirectory/$fileNameNoExtension*" . self::OVERRIDE_EXTENSION);
104 1
        if ([] === $filesInDirectory) {
105
            return null;
106 1
        }
107 1
        if (1 === count($filesInDirectory)) {
108
            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 2
116
    private function getOverrideDirectoryForFile(string $relativePathToFileInProject): string
117 2
    {
118 2
        $path = $this->getPathToOverridesDirectory() . \dirname($relativePathToFileInProject);
119
        if (!is_dir($path) && !(mkdir($path, 0777, true) && is_dir($path))) {
120
            throw new \RuntimeException('Failed making override directory path ' . $path);
121
        }
122 2
123
        return $this->getRealPath($path);
124
    }
125
126
    /**
127
     * @return string
128 5
     */
129
    public function getPathToOverridesDirectory(): string
130 5
    {
131
        return $this->getRealPath($this->pathToOverridesDirectory);
132
    }
133
134
    /**
135
     * @param string $pathToOverridesDirectory
136
     *
137
     * @return FileOverrider
138 5
     */
139
    public function setPathToOverridesDirectory(string $pathToOverridesDirectory): FileOverrider
140 5
    {
141
        $this->pathToOverridesDirectory = $this->getRealPath($pathToOverridesDirectory);
142 5
143
        return $this;
144
    }
145 2
146
    private function getFileNameNoExtensionForPathInProject(string $relativePathToFileInProject): string
147 2
    {
148
        $fileName = basename($relativePathToFileInProject);
149 2
150
        return substr($fileName, 0, -self::EXTENSION_LENGTH_NO_HASH_IN_PROJECT);
151
    }
152 3
153
    private function getProjectFileHash(string $relativePathToFileInProject): string
154 3
    {
155
        return $this->getFileHash($this->pathToProjectRoot . '/' . $relativePathToFileInProject);
156
    }
157 4
158
    private function getFileHash(string $path): string
159 4
    {
160
        $contents = \ts\file_get_contents($path);
161 4
162
        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 1
     */
170
    public function updateOverrideFiles(): array
171 1
    {
172 1
        $filesUpdated = [];
173 1
        $fileSame     = [];
174 1
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
175 1
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
176
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
177
                $fileSame[] = $relativePathToFileInProject;
178
                continue;
179 1
            }
180 1
            $pathToFileInProject = $this->pathToProjectRoot . $relativePathToFileInProject;
181
            if (false === is_file($pathToFileInProject)) {
182
                throw new \RuntimeException('path ' . $pathToFileInProject . ' is not a file');
183 1
            }
184 1
            copy($pathToFileInProject, $pathToFileInOverrides);
185
            $filesUpdated[] = $relativePathToFileInProject;
186
        }
187 1
188
        return [$this->sortFiles($filesUpdated), $this->sortFiles($fileSame)];
189
    }
190
191
    /**
192
     * Yield file paths in the override folder
193
     *
194
     * @return \Generator|string[]
195 3
     */
196
    private function getOverridesIterator(): \Generator
197
    {
198 3
        try {
199 3
            $recursiveIterator = new \RecursiveIteratorIterator(
200 3
                new \RecursiveDirectoryIterator(
201 3
                    $this->getPathToOverridesDirectory(),
202
                    \RecursiveDirectoryIterator::SKIP_DOTS
203 3
                ),
204
                \RecursiveIteratorIterator::SELF_FIRST
205 3
            );
206
            foreach ($recursiveIterator as $fileInfo) {
207
                /**
208
                 * @var \SplFileInfo $fileInfo
209 3
                 */
210 3
                if ($fileInfo->isFile()) {
211
                    if (self::OVERRIDE_EXTENSION !== substr(
212
                        $fileInfo->getFilename(),
213 3
                        -strlen(self::OVERRIDE_EXTENSION)
214 3
                    )
215 3
                    ) {
216
                        continue;
217 3
                    }
218
                    yield $fileInfo->getPathname();
219 3
                }
220
            }
221 3
        } finally {
222 3
            $recursiveIterator = null;
223 3
            unset($recursiveIterator);
224 3
        }
225
    }
226 3
227 3
    private function getRelativePathInProjectFromOverridePath(string $pathToFileInOverrides): string
228
    {
229
        $relativePath = substr($pathToFileInOverrides, strlen($this->getPathToOverridesDirectory()));
230
        $relativeDir  = dirname($relativePath);
231
        $filename     = basename($pathToFileInOverrides);
232
        $filename     = substr($filename, 0, -self::EXTENSION_LENGTH_WITH_HASH_IN_OVERRIDES) . '.php';
233
234
        return $this->getRelativePathToFile(
235
            $this->getRealPath($this->pathToProjectRoot . '/' . $relativeDir . '/' . $filename)
236
        );
237
    }
238 2
239
    /**
240 2
     * Is the file in the project the same as the override file already?
241
     *
242 2
     * @param string $pathToFileInOverrides
243 2
     *
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
               $this->getFileHash($pathToFileInOverrides);
252
    }
253
254
    private function sortFiles(array $files): array
255
    {
256
        sort($files, SORT_STRING);
257
258 2
        return $files;
259
    }
260 2
261 2
    /**
262 2
     * Loop over all the override files and copy into the project
263 2
     *
264 2
     * @return array[] the file paths that have been updated
265 2
     */
266 1
    public function applyOverrides(): array
267
    {
268
        $filesUpdated = [];
269 1
        $filesSame    = [];
270 1
        $errors       = [];
271 1
        foreach ($this->getOverridesIterator() as $pathToFileInOverrides) {
272
            $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
273 1
            if ($this->overrideFileHashIsCorrect($pathToFileInOverrides)) {
274
                if (false === is_file($pathToFileInOverrides)) {
275
                    throw new \RuntimeException('path ' . $pathToFileInOverrides . ' is not a file');
276
                }
277 1
                copy($pathToFileInOverrides, $this->pathToProjectRoot . $relativePathToFileInProject);
278
                $filesUpdated[] = $relativePathToFileInProject;
279 2
                continue;
280 1
            }
281
            if ($this->projectFileIsSameAsOverride($pathToFileInOverrides)) {
282
                $filesSame[] = $relativePathToFileInProject;
283 1
                continue;
284
            }
285
            $errors[$pathToFileInOverrides] = $this->getProjectFileHash($relativePathToFileInProject);
286 2
        }
287
        if ([] !== $errors) {
288 2
            throw new \RuntimeException('These file hashes were not up to date:' . print_r($errors, true));
289 2
        }
290
291
        return [$this->sortFiles($filesUpdated), $this->sortFiles($filesSame)];
292 2
    }
293 2
294
    private function overrideFileHashIsCorrect(string $pathToFileInOverrides): bool
295 2
    {
296
        $filenameParts = explode('.', basename($pathToFileInOverrides));
297
        if (4 !== count($filenameParts)) {
298
            throw new \RuntimeException('Invalid override filename ' . $pathToFileInOverrides);
299
        }
300
        $hash                        = $filenameParts[1];
301
        $relativePathToFileInProject = $this->getRelativePathInProjectFromOverridePath($pathToFileInOverrides);
302
303
        return $hash === $this->getProjectFileHash($relativePathToFileInProject);
304
    }
305
}
306