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

UnusedRelationsRemover   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 129
dl 0
loc 308
ccs 0
cts 225
cp 0
rs 9.28
c 1
b 0
f 0
wmc 39

22 Methods

Rating   Name   Duplication   Size   Complexity  
A removeHasPluralOrSingularInterfaceAndAbstract() 0 16 2
A removeRelationsBySingularOrPlural() 0 17 4
A removeUnusedEntityRelations() 0 24 5
A getRelationRegexPatternFromRelationTraitSubFqn() 0 6 1
A getEntitySubSubFqn() 0 3 1
A removeAllRelationFilesForEntity() 0 14 3
A run() 0 9 2
A getPluralSingularFromEntitySubFqn() 0 7 1
A initArrayOfRelationTraits() 0 28 3
A getRegexForRelationTraitUseStatement() 0 6 1
A getSubPathFromSrcAndTrimExtension() 0 6 1
A setProjectRootNamespace() 0 5 1
A removeFile() 0 7 2
A getPathToRelationRootForEntity() 0 8 1
A getFileInfoObjectsInDirs() 0 6 1
A getEntitySubFqnFromEntityFilePath() 0 5 1
A removeFoundFiles() 0 4 2
A removeRelation() 0 10 2
A setPathToProjectRoot() 0 5 1
A convertPathToNamespace() 0 3 1
A __construct() 0 6 1
A initAllEntitySubFqns() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration;
6
7
use EdmondsCommerce\DoctrineStaticMeta\Config;
8
use Symfony\Component\Finder\Finder;
9
use Symfony\Component\Finder\SplFileInfo;
10
11
use function array_keys;
12
use function file_exists;
13
use function file_get_contents;
14
use function is_dir;
15
use function preg_match;
16
use function str_replace;
17
use function strlen;
18
19
/**
20
 * Class UnusedCodeRemover
21
 *
22
 * Finds and removes generated code that does not seem to be used
23
 *
24
 * Caution - this is destructive - do not use in a dirty repo!!!
25
 *
26
 * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration
27
 */
28
class UnusedRelationsRemover
29
{
30
    /**
31
     * @var NamespaceHelper
32
     */
33
    protected $namespaceHelper;
34
    /**
35
     * @var Config
36
     */
37
    protected $config;
38
    /**
39
     * @var array
40
     */
41
    private $filesRemoved = [];
42
    /**
43
     * @var string
44
     */
45
    private $pathToProjectRoot;
46
47
    /**
48
     * @param string $pathToProjectRoot
49
     *
50
     * @return UnusedRelationsRemover
51
     */
52
    public function setPathToProjectRoot(string $pathToProjectRoot): UnusedRelationsRemover
53
    {
54
        $this->pathToProjectRoot = $pathToProjectRoot;
55
56
        return $this;
57
    }
58
59
    /**
60
     * @param string $projectRootNamespace
61
     *
62
     * @return UnusedRelationsRemover
63
     */
64
    public function setProjectRootNamespace(string $projectRootNamespace): UnusedRelationsRemover
65
    {
66
        $this->projectRootNamespace = $projectRootNamespace;
67
68
        return $this;
69
    }
70
    /**
71
     * @var string
72
     */
73
    private $projectRootNamespace;
74
    /**
75
     * @var array
76
     */
77
    private $relationTraits = [];
78
    /**
79
     * @var array
80
     */
81
    private $entitySubFqnsToName = [];
82
    /**
83
     * @var array
84
     */
85
    private $entityPaths = [];
86
87
    public function __construct(NamespaceHelper $namespaceHelper, Config $config)
88
    {
89
        $this->namespaceHelper      = $namespaceHelper;
90
        $this->config               = $config;
91
        $this->pathToProjectRoot    = $this->config::getProjectRootDirectory();
92
        $this->projectRootNamespace = $this->namespaceHelper->getProjectRootNamespaceFromComposerJson();
93
    }
94
95
96
    public function run(): array
97
    {
98
        $this->initArrayOfRelationTraits();
99
        $this->initAllEntitySubFqns();
100
        foreach (array_keys($this->entitySubFqnsToName) as $entitySubFqn) {
101
            $this->removeUnusedEntityRelations($entitySubFqn);
102
        }
103
104
        return $this->filesRemoved;
105
    }
106
107
    private function initArrayOfRelationTraits(): void
108
    {
109
        $this->relationTraits = [];
110
        $pluralRelations      = $this->getFileInfoObjectsInDirs(
111
            [
112
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasTemplateEntities',
113
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasRequiredTemplateEntities',
114
            ]
115
        );
116
        foreach ($pluralRelations as $pluralRelation) {
117
            $realPath                                  = $pluralRelation->getRealPath();
118
            $this->relationTraits['plural'][$realPath] = $this->convertPathToNamespace(
119
                $this->getSubPathFromSrcAndTrimExtension(
120
                    $realPath
121
                )
122
            );
123
        }
124
        $singularRelations = $this->getFileInfoObjectsInDirs(
125
            [
126
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasTemplateEntity',
127
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasRequiredTemplateEntity',
128
            ]
129
        );
130
        foreach ($singularRelations as $singularRelation) {
131
            $realPath                                    = $singularRelation->getRealPath();
132
            $this->relationTraits['singular'][$realPath] = $this->convertPathToNamespace(
133
                $this->getSubPathFromSrcAndTrimExtension(
134
                    $realPath
135
                )
136
            );
137
        }
138
    }
139
140
    /**
141
     * @param array $dirs
142
     *
143
     * @return array|SplFileInfo[]
144
     */
145
    private function getFileInfoObjectsInDirs(array $dirs): array
146
    {
147
        $finder   = new Finder();
148
        $iterable = $finder->files()->in($dirs);
149
150
        return iterator_to_array($iterable);
151
    }
152
153
    private function convertPathToNamespace(string $path): string
154
    {
155
        return str_replace('/', '\\', $path);
156
    }
157
158
    private function getSubPathFromSrcAndTrimExtension(string $path): string
159
    {
160
        $subPath = substr($path, strpos($path, 'src') + 3);
161
        $subPath = substr($subPath, 0, strpos($subPath, '.php'));
162
163
        return $subPath;
164
    }
165
166
    private function initAllEntitySubFqns(): void
167
    {
168
        $files                     = $this->getFileInfoObjectsInDirs([$this->pathToProjectRoot . '/src/Entities']);
169
        $this->entitySubFqnsToName = [];
170
        foreach ($files as $file) {
171
            $realPath                                 = $file->getRealPath();
172
            $this->entityPaths[$realPath]             = file_get_contents($realPath);
173
            $entitySubFqn                             = $this->getEntitySubFqnFromEntityFilePath($realPath);
174
            $this->entitySubFqnsToName[$entitySubFqn] = $this->getPluralSingularFromEntitySubFqn($entitySubFqn);
175
        }
176
    }
177
178
    private function getEntitySubFqnFromEntityFilePath(string $path): string
179
    {
180
        $subPath = $this->getSubPathFromSrcAndTrimExtension($path);
181
182
        return str_replace('/', '\\', $subPath);
183
    }
184
185
    private function getPluralSingularFromEntitySubFqn(string $entitySubFqn): array
186
    {
187
        $entityFqn = $this->projectRootNamespace . $entitySubFqn;
188
189
        return [
190
            'singular' => ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular()),
191
            'plural'   => ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural()),
192
        ];
193
    }
194
195
    private function removeUnusedEntityRelations(string $entitySubFqn): void
196
    {
197
        $entitySubSubFqn = $this->getEntitySubSubFqn($entitySubFqn);
198
        $hasPlural       = $this->removeRelationsBySingularOrPlural('plural', $entitySubSubFqn);
199
        $hasSingular     = $this->removeRelationsBySingularOrPlural('singular', $entitySubSubFqn);
200
201
        if (false === $hasPlural && false === $hasSingular) {
202
            $this->removeAllRelationFilesForEntity($entitySubSubFqn);
203
204
            return;
205
        }
206
        if (false === $hasPlural) {
207
            $this->removeHasPluralOrSingularInterfaceAndAbstract(
208
                'plural',
209
                $entitySubFqn,
210
                $entitySubSubFqn
211
            );
212
        }
213
214
        if (false === $hasSingular) {
215
            $this->removeHasPluralOrSingularInterfaceAndAbstract(
216
                'singular',
217
                $entitySubFqn,
218
                $entitySubSubFqn
219
            );
220
        }
221
    }
222
223
    private function getEntitySubSubFqn(string $entitySubFqn): string
224
    {
225
        return substr($entitySubFqn, strlen('\\Entities\\'));
226
    }
227
228
    private function removeRelationsBySingularOrPlural(string $singularOrPlural, string $entitySubSubFqn): bool
229
    {
230
        $foundUsedRelations = false;
231
232
        foreach ($this->relationTraits[$singularOrPlural] as $relationTraitSubFqn) {
233
            $relationRegex = $this->getRelationRegexPatternFromRelationTraitSubFqn($relationTraitSubFqn);
234
            $pattern       = $this->getRegexForRelationTraitUseStatement($entitySubSubFqn, $relationRegex);
235
            foreach ($this->entityPaths as $entityFileContents) {
236
                if (1 === preg_match($pattern, $entityFileContents)) {
237
                    $foundUsedRelations = true;
238
                    continue 2;
239
                }
240
            }
241
            $this->removeRelation($entitySubSubFqn, $relationRegex);
242
        }
243
244
        return $foundUsedRelations;
245
    }
246
247
    private function getRelationRegexPatternFromRelationTraitSubFqn(string $relationTraitSubFqn): string
248
    {
249
250
        $baseTraitNamespace = $this->namespaceHelper->basename($relationTraitSubFqn);
251
252
        return preg_replace('%TemplateEntit(y|ies)%', '.+?', $baseTraitNamespace);
253
    }
254
255
    private function getRegexForRelationTraitUseStatement(string $entitySubSubFqn, string $relationRegex): string
256
    {
257
        $entitySubSubFqn = str_replace('\\', '\\\\', $entitySubSubFqn);
258
259
        return <<<REGEXP
260
%use .+?\\\\Entity\\\\Relations\\\\$entitySubSubFqn([^;]+?)$relationRegex%
261
REGEXP;
262
    }
263
264
    private function removeRelation(string $entitySubSubFqn, string $relationRegex): void
265
    {
266
        $directory = $this->getPathToRelationRootForEntity($entitySubSubFqn);
267
        if (!is_dir($directory)) {
268
            return;
269
        }
270
        $finder = (new Finder())->files()
271
                                ->in($directory)
272
                                ->path('%^(Interfaces|Traits).+?' . $relationRegex . '%');
273
        $this->removeFoundFiles($finder);
274
    }
275
276
    private function getPathToRelationRootForEntity(string $entitySubSubFqn): string
277
    {
278
        return $this->pathToProjectRoot
279
               . '/src/Entity/Relations/'
280
               . str_replace(
281
                   '\\',
282
                   '/',
283
                   $entitySubSubFqn
284
               );
285
    }
286
287
    private function removeFoundFiles(Finder $finder): void
288
    {
289
        foreach ($finder as $fileInfo) {
290
            $this->removeFile($fileInfo->getRealPath());
291
        }
292
    }
293
294
    private function removeFile(string $path): void
295
    {
296
        if (!file_exists($path)) {
297
            return;
298
        }
299
        $this->filesRemoved[] = $path;
300
        unlink($path);
301
    }
302
303
    private function removeAllRelationFilesForEntity(string $entitySubSubFqn): void
304
    {
305
        $relationsPath = $this->getPathToRelationRootForEntity($entitySubSubFqn);
306
        $directories   = [
307
            "$relationsPath/Traits",
308
            "$relationsPath/Interfaces",
309
        ];
310
        foreach ($directories as $directory) {
311
            if (!is_dir($directory)) {
312
                continue;
313
            }
314
            $finder = (new Finder())->files()
315
                                    ->in($directory);
316
            $this->removeFoundFiles($finder);
317
        }
318
    }
319
320
    private function removeHasPluralOrSingularInterfaceAndAbstract(
321
        string $pluralOrSingular,
322
        string $entitySubFqn,
323
        string $entitySubSubFqn
324
    ): void {
325
        $directory = $this->getPathToRelationRootForEntity($entitySubSubFqn);
326
        if (!is_dir($directory)) {
327
            return;
328
        }
329
        $hasName = $this->entitySubFqnsToName[$entitySubFqn][$pluralOrSingular];
330
        $finder  = (new Finder())->files()
331
                                 ->in($directory)
332
                                 ->path(
333
                                     '%^(Interfaces|Traits).+?Has' . $hasName . '(/|Abstract\.php|Interface\.php)%'
334
                                 );
335
        $this->removeFoundFiles($finder);
336
    }
337
}
338