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

UnusedRelationsRemover::setProjectRootNamespace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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