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 (#199)
by joseph
19:04
created

UnusedRelationsRemover::removeRelation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 10
ccs 0
cts 10
cp 0
crap 6
rs 10
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
        $this->initArrayOfRelationTraits();
89
        $this->initAllEntitySubFqns();
90
        foreach (\array_keys($this->entitySubFqnsToName) as $entitySubFqn) {
91
            $this->removeUnusedEntityRelations($entitySubFqn);
92
        }
93
94
        return $this->filesRemoved;
95
    }
96
97
    private function initArrayOfRelationTraits(): void
98
    {
99
        $this->relationTraits = [];
100
        $pluralRelations      = $this->getFileInfoObjectsInDirs(
101
            [
102
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasTemplateEntities',
103
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasRequiredTemplateEntities',
104
            ]
105
        );
106
        foreach ($pluralRelations as $pluralRelation) {
107
            $realPath                                  = $pluralRelation->getRealPath();
108
            $this->relationTraits['plural'][$realPath] = $this->convertPathToNamespace(
109
                $this->getSubPathFromSrcAndTrimExtension(
110
                    $realPath
111
                )
112
            );
113
        }
114
        $singularRelations = $this->getFileInfoObjectsInDirs(
115
            [
116
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasTemplateEntity',
117
                __DIR__ . '/../../codeTemplates/src/Entity/Relations/TemplateEntity/Traits/HasRequiredTemplateEntity',
118
            ]
119
        );
120
        foreach ($singularRelations as $singularRelation) {
121
            $realPath                                    = $singularRelation->getRealPath();
122
            $this->relationTraits['singular'][$realPath] = $this->convertPathToNamespace(
123
                $this->getSubPathFromSrcAndTrimExtension(
124
                    $realPath
125
                )
126
            );
127
        }
128
    }
129
130
    /**
131
     * @param array $dirs
132
     *
133
     * @return array|SplFileInfo[]
134
     */
135
    private function getFileInfoObjectsInDirs(array $dirs): array
136
    {
137
        $finder   = new Finder();
138
        $iterable = $finder->files()->in($dirs);
139
140
        return iterator_to_array($iterable);
141
    }
142
143
    private function convertPathToNamespace(string $path): string
144
    {
145
        return \str_replace('/', '\\', $path);
146
    }
147
148
    private function getSubPathFromSrcAndTrimExtension(string $path): string
149
    {
150
        $subPath = substr($path, strpos($path, 'src') + 3);
151
        $subPath = substr($subPath, 0, strpos($subPath, '.php'));
152
153
        return $subPath;
154
    }
155
156
    private function initAllEntitySubFqns(): void
157
    {
158
        $files                     = $this->getFileInfoObjectsInDirs([$this->pathToProjectRoot . '/src/Entities']);
159
        $this->entitySubFqnsToName = [];
160
        foreach ($files as $file) {
161
            $realPath                                 = $file->getRealPath();
162
            $this->entityPaths[$realPath]             = \file_get_contents($realPath);
163
            $entitySubFqn                             = $this->getEntitySubFqnFromEntityFilePath($realPath);
164
            $this->entitySubFqnsToName[$entitySubFqn] = $this->getPluralSingularFromEntitySubFqn($entitySubFqn);
165
        }
166
    }
167
168
    private function getEntitySubFqnFromEntityFilePath(string $path): string
169
    {
170
        $subPath = $this->getSubPathFromSrcAndTrimExtension($path);
171
172
        return \str_replace('/', '\\', $subPath);
173
    }
174
175
    private function getPluralSingularFromEntitySubFqn(string $entitySubFqn): array
176
    {
177
        $entityFqn = $this->projectRootNamespace . $entitySubFqn;
178
179
        return [
180
            'singular' => ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular()),
181
            'plural'   => ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural()),
182
        ];
183
    }
184
185
    private function removeUnusedEntityRelations(string $entitySubFqn): void
186
    {
187
        $entitySubSubFqn = $this->getEntitySubSubFqn($entitySubFqn);
188
        $hasPlural       = $this->removeRelationsBySingularOrPlural('plural', $entitySubSubFqn);
189
        $hasSingular     = $this->removeRelationsBySingularOrPlural('singular', $entitySubSubFqn);
190
191
        if (false === $hasPlural && false === $hasSingular) {
192
            $this->removeAllRelationFilesForEntity($entitySubSubFqn);
193
194
            return;
195
        }
196
        if (false === $hasPlural) {
197
            $this->removeHasPluralOrSingularInterfaceAndAbstract(
198
                'plural',
199
                $entitySubFqn,
200
                $entitySubSubFqn
201
            );
202
        }
203
204
        if (false === $hasSingular) {
205
            $this->removeHasPluralOrSingularInterfaceAndAbstract(
206
                'singular',
207
                $entitySubFqn,
208
                $entitySubSubFqn
209
            );
210
        }
211
    }
212
213
    private function getEntitySubSubFqn(string $entitySubFqn): string
214
    {
215
        return substr($entitySubFqn, \strlen('\\Entities\\'));
216
    }
217
218
    private function removeRelationsBySingularOrPlural(string $singularOrPlural, string $entitySubSubFqn): bool
219
    {
220
        $foundUsedRelations = false;
221
222
        foreach ($this->relationTraits[$singularOrPlural] as $relationTraitSubFqn) {
223
            $relationRegex = $this->getRelationRegexPatternFromRelationTraitSubFqn($relationTraitSubFqn);
224
            $pattern       = $this->getRegexForRelationTraitUseStatement($entitySubSubFqn, $relationRegex);
225
            foreach ($this->entityPaths as $entityFileContents) {
226
                if (1 === \preg_match($pattern, $entityFileContents)) {
227
                    $foundUsedRelations = true;
228
                    continue 2;
229
                }
230
            }
231
            $this->removeRelation($entitySubSubFqn, $relationRegex);
232
        }
233
234
        return $foundUsedRelations;
235
    }
236
237
    private function getRelationRegexPatternFromRelationTraitSubFqn(string $relationTraitSubFqn): string
238
    {
239
240
        $baseTraitNamespace = $this->namespaceHelper->basename($relationTraitSubFqn);
241
242
        return preg_replace('%TemplateEntit(y|ies)%', '.+?', $baseTraitNamespace);
243
    }
244
245
    private function getRegexForRelationTraitUseStatement(string $entitySubSubFqn, string $relationRegex): string
246
    {
247
        $entitySubSubFqn = \str_replace('\\', '\\\\', $entitySubSubFqn);
248
249
        return <<<REGEXP
250
%use .+?\\\\Entity\\\\Relations\\\\$entitySubSubFqn([^;]+?)$relationRegex%
251
REGEXP;
252
    }
253
254
    private function removeRelation(string $entitySubSubFqn, string $relationRegex): void
255
    {
256
        $directory = $this->getPathToRelationRootForEntity($entitySubSubFqn);
257
        if (!\is_dir($directory)) {
258
            return;
259
        }
260
        $finder = (new Finder())->files()
261
                                ->in($directory)
262
                                ->path('%^(Interfaces|Traits).+?' . $relationRegex . '%');
263
        $this->removeFoundFiles($finder);
264
    }
265
266
    private function getPathToRelationRootForEntity(string $entitySubSubFqn): string
267
    {
268
        return $this->pathToProjectRoot
269
               . '/src/Entity/Relations/'
270
               . \str_replace(
271
                   '\\',
272
                   '/',
273
                   $entitySubSubFqn
274
               );
275
    }
276
277
    private function removeFoundFiles(Finder $finder): void
278
    {
279
        foreach ($finder as $fileInfo) {
280
            $this->removeFile($fileInfo->getRealPath());
281
        }
282
    }
283
284
    private function removeFile(string $path): void
285
    {
286
        if (!\file_exists($path)) {
287
            return;
288
        }
289
        $this->filesRemoved[] = $path;
290
        unlink($path);
291
    }
292
293
    private function removeAllRelationFilesForEntity(string $entitySubSubFqn): void
294
    {
295
        $relationsPath = $this->getPathToRelationRootForEntity($entitySubSubFqn);
296
        $directories   = [
297
            "$relationsPath/Traits",
298
            "$relationsPath/Interfaces",
299
        ];
300
        foreach ($directories as $directory) {
301
            if (!\is_dir($directory)) {
302
                continue;
303
            }
304
            $finder = (new Finder())->files()
305
                                    ->in($directory);
306
            $this->removeFoundFiles($finder);
307
        }
308
    }
309
310
    private function removeHasPluralOrSingularInterfaceAndAbstract(
311
        string $pluralOrSingular,
312
        string $entitySubFqn,
313
        string $entitySubSubFqn
314
    ): void {
315
        $directory = $this->getPathToRelationRootForEntity($entitySubSubFqn);
316
        if (!\is_dir($directory)) {
317
            return;
318
        }
319
        $hasName = $this->entitySubFqnsToName[$entitySubFqn][$pluralOrSingular];
320
        $finder  = (new Finder())->files()
321
                                 ->in($directory)
322
                                 ->path(
323
                                     '%^(Interfaces|Traits).+?Has' . $hasName . '(/|Abstract\.php|Interface\.php)%'
324
                                 );
325
        $this->removeFoundFiles($finder);
326
    }
327
}
328