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

removeRelationsBySingularOrPlural()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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