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 (#77)
by joseph
15:21
created

getHasPluralInterfaceFqnForEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 1
nc 1
nop 1
crap 1
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration;
4
5
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
8
use EdmondsCommerce\DoctrineStaticMeta\Config;
9
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
10
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
11
12
/**
13
 * Class NamespaceHelper
14
 *
15
 * Pure functions for working with namespaces and to calculate namespaces
16
 *
17
 * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration
18
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
19
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
20
 */
21
class NamespaceHelper
22
{
23 8
    public function swapSuffix(string $fqn, string $currentSuffix, string $newSuffix): string
24
    {
25 8
        return $this->cropSuffix($fqn, $currentSuffix) . $newSuffix;
26
    }
27
28
    /**
29
     * Crop a suffix from an FQN if it is there.
30
     *
31
     * If it is not there, do nothing and return the FQN as is
32
     *
33
     * @param string $fqn
34
     * @param string $suffix
35
     *
36
     * @return string
37
     */
38 27
    public function cropSuffix(string $fqn, string $suffix): string
39
    {
40 27
        if ($suffix === \substr($fqn, -\strlen($suffix))) {
41 26
            return \substr($fqn, 0, -\strlen($suffix));
42
        }
43
44 7
        return $fqn;
45
    }
46
47
    /**
48
     * @param mixed|object $object
49
     *
50
     * @return string
51
     */
52 1
    public function getObjectShortName($object): string
53
    {
54 1
        return $this->getClassShortName($this->getObjectFqn($object));
55
    }
56
57
    /**
58
     * @param string $className
59
     *
60
     * @return string
61
     */
62 8
    public function getClassShortName(string $className): string
63
    {
64 8
        $exp = explode('\\', $className);
65
66 8
        return end($exp);
67
    }
68
69
    /**
70
     * @param mixed|object $object
71
     *
72
     * @see https://gist.github.com/ludofleury/1708784
73
     * @return string
74
     */
75 6
    public function getObjectFqn($object): string
76
    {
77 6
        return \get_class($object);
78
    }
79
80
    /**
81
     * Get the basename of a namespace
82
     *
83
     * @param string $namespace
84
     *
85
     * @return string
86
     */
87 6
    public function basename(string $namespace): string
88
    {
89 6
        $strrpos = \strrpos($namespace, '\\');
90 6
        if (false === $strrpos) {
91
            return $namespace;
92
        }
93
94 6
        return $this->tidy(\substr($namespace, $strrpos + 1));
95
    }
96
97
    /**
98
     * Checks and tidies up a given namespace
99
     *
100
     * @param string $namespace
101
     *
102
     * @return string
103
     * @throws \RuntimeException
104
     */
105 140
    public function tidy(string $namespace): string
106
    {
107 140
        if (\ts\stringContains($namespace, '/')) {
108
            throw new \RuntimeException('Invalid namespace ' . $namespace);
109
        }
110
        #remove repeated separators
111 140
        $namespace = preg_replace(
112 140
            '#' . '\\\\' . '+#',
113 140
            '\\',
114 140
            $namespace
115
        );
116
117 140
        return $namespace;
118
    }
119
120
    /**
121
     * Work out the entity namespace root from a single entity reflection object.
122
     *
123
     * @param \ts\Reflection\ReflectionClass $entityReflection
124
     *
125
     * @return string
126
     */
127 1
    public function getEntityNamespaceRootFromEntityReflection(
128
        \ts\Reflection\ReflectionClass $entityReflection
129
    ): string {
130 1
        return $this->tidy(
131 1
            $this->getNamespaceRootToDirectoryFromFqn(
132 1
                $entityReflection->getName(),
133 1
                AbstractGenerator::ENTITIES_FOLDER_NAME
134
            )
135
        );
136
    }
137
138
    /**
139
     * Get the namespace root up to and including a specified directory
140
     *
141
     * @param string $fqn
142
     * @param string $directory
143
     *
144
     * @return null|string
145
     */
146 1
    public function getNamespaceRootToDirectoryFromFqn(string $fqn, string $directory): ?string
147
    {
148 1
        $strPos = \strrpos(
149 1
            $fqn,
150 1
            $directory
151
        );
152 1
        if (false !== $strPos) {
153 1
            return $this->tidy(\substr($fqn, 0, $strPos + \strlen($directory)));
154
        }
155
156
        return null;
157
    }
158
159
    /**
160
     * Get the sub path for an Entity file, start from the Entities path - normally `/path/to/project/src/Entities`
161
     *
162
     * @param string $entityFqn
163
     *
164
     * @return string
165
     */
166 3
    public function getEntityFileSubPath(
167
        string $entityFqn
168
    ): string {
169 3
        return $this->getEntitySubPath($entityFqn) . '.php';
170
    }
171
172
    /**
173
     * Get the folder structure for an Entity, start from the Entities path - normally `/path/to/project/src/Entities`
174
     *
175
     * This is not the path to the file, but the sub path of directories for storing entity related items.
176
     *
177
     * @param string $entityFqn
178
     *
179
     * @return string
180
     */
181 5
    public function getEntitySubPath(
182
        string $entityFqn
183
    ): string {
184 5
        $entityPath = str_replace(
185 5
            '\\',
186 5
            '/',
187 5
            $this->getEntitySubNamespace($entityFqn)
188
        );
189
190 5
        return '/' . $entityPath;
191
    }
192
193
    /**
194
     * Get the Namespace for an Entity, start from the Entities Fully Qualified Name base - normally
195
     * `\My\Project\Entities\`
196
     *
197
     * @param string $entityFqn
198
     *
199
     * @return string
200
     */
201 67
    public function getEntitySubNamespace(
202
        string $entityFqn
203
    ): string {
204 67
        return $this->tidy(
205 67
            \substr(
206 67
                $entityFqn,
207 67
                \strrpos(
208 67
                    $entityFqn,
209 67
                    '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\'
210
                )
211 67
                + \strlen('\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\')
212
            )
213
        );
214
    }
215
216
    /**
217
     * Get the Fully Qualified Namespace root for Traits for the specified Entity
218
     *
219
     * @param string $entityFqn
220
     *
221
     * @return string
222
     */
223 1
    public function getTraitsNamespaceForEntity(
224
        string $entityFqn
225
    ): string {
226 1
        $traitsNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn)
227 1
                           . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE
228 1
                           . '\\' . $this->getEntitySubNamespace($entityFqn)
229 1
                           . '\\Traits';
230
231 1
        return $traitsNamespace;
232
    }
233
234
    /**
235
     * Use the fully qualified name of two Entities to calculate the Project Namespace Root
236
     *
237
     * - note: this assumes a single namespace level for entities, eg `Entities`
238
     *
239
     * @param string $entityFqn
240
     *
241
     * @return string
242
     */
243 6
    public function getProjectNamespaceRootFromEntityFqn(string $entityFqn): string
244
    {
245 6
        return $this->tidy(
246 6
            \substr(
247 6
                $entityFqn,
248 6
                0,
249 6
                \strrpos(
250 6
                    $entityFqn,
251 6
                    '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\'
252
                )
253
            )
254
        );
255
    }
256
257
    /**
258
     * Get the Fully Qualified Namespace for the "HasEntities" interface for the specified Entity
259
     *
260
     * @param string $entityFqn
261
     *
262
     * @return string
263
     */
264 2
    public function getHasPluralInterfaceFqnForEntity(
265
        string $entityFqn
266
    ): string {
267 2
        $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn);
268
269 2
        return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getPlural()) . 'Interface';
270
    }
271
272
    /**
273
     * Get the Fully Qualified Namespace root for Interfaces for the specified Entity
274
     *
275
     * @param string $entityFqn
276
     *
277
     * @return string
278
     */
279 4
    public function getInterfacesNamespaceForEntity(
280
        string $entityFqn
281
    ): string {
282 4
        $interfacesNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn)
283 4
                               . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE
284 4
                               . '\\' . $this->getEntitySubNamespace($entityFqn)
285 4
                               . '\\Interfaces';
286
287 4
        return $this->tidy($interfacesNamespace);
288
    }
289
290
    /**
291
     * Get the Fully Qualified Namespace for the "HasEntity" interface for the specified Entity
292
     *
293
     * @param string $entityFqn
294
     *
295
     * @return string
296
     * @throws DoctrineStaticMetaException
297
     */
298 1
    public function getHasSingularInterfaceFqnForEntity(
299
        string $entityFqn
300
    ): string {
301
        try {
302 1
            $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn);
303
304 1
            return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getSingular()) . 'Interface';
305
        } catch (\Exception $e) {
306
            throw new DoctrineStaticMetaException(
307
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
308
                $e->getCode(),
309
                $e
310
            );
311
        }
312
    }
313
314
    /**
315
     * Get the Fully Qualified Namespace for the Relation Trait for a specific Entity and hasType
316
     *
317
     * @param string      $hasType
318
     * @param string      $ownedEntityFqn
319
     * @param string|null $projectRootNamespace
320
     * @param string      $srcFolder
321
     *
322
     * @return string
323
     * @throws DoctrineStaticMetaException
324
     */
325 24
    public function getOwningTraitFqn(
326
        string $hasType,
327
        string $ownedEntityFqn,
328
        ?string $projectRootNamespace = null,
329
        string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER
330
    ): string {
331
        try {
332 24
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, $projectRootNamespace);
0 ignored issues
show
Bug introduced by
It seems like $projectRootNamespace can also be of type null; however, parameter $projectRootNamespace of EdmondsCommerce\Doctrine...lper::getOwnedHasName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
333 24
            if (null === $projectRootNamespace) {
334
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
335
            }
336 24
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
337 24
                $ownedEntityFqn,
338 24
                $srcFolder,
339 24
                $projectRootNamespace
340
            );
341 24
            $traitSubDirectories = \array_slice($ownedSubDirectories, 2);
342 24
            $owningTraitFqn      = $this->getOwningRelationsRootFqn(
343 24
                $projectRootNamespace,
344 24
                $traitSubDirectories
345
            );
346 24
            $owningTraitFqn      .= $ownedClassName . '\\Traits\\Has' . $ownedHasName
347 24
                                    . '\\Has' . $ownedHasName . $this->stripPrefixFromHasType($hasType);
348
349 24
            return $this->tidy($owningTraitFqn);
350
        } catch (\Exception $e) {
351
            throw new DoctrineStaticMetaException(
352
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
353
                $e->getCode(),
354
                $e
355
            );
356
        }
357
    }
358
359
    /**
360
     * Based on the $hasType, we calculate exactly what type of `Has` we have
361
     *
362
     * @param string $hasType
363
     * @param string $ownedEntityFqn
364
     * @param string $srcOrTestSubFolder
365
     *
366
     * @param string $projectRootNamespace
367
     *
368
     * @return string
369
     * @SuppressWarnings(PHPMD.StaticAccess)
370
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
371
     */
372 24
    public function getOwnedHasName(
373
        string $hasType,
374
        string $ownedEntityFqn,
375
        string $srcOrTestSubFolder,
376
        string $projectRootNamespace
377
    ): string {
378 24
        $parsedFqn = $this->parseFullyQualifiedName(
379 24
            $ownedEntityFqn,
380 24
            $srcOrTestSubFolder,
381 24
            $projectRootNamespace
382
        );
383
384 24
        $subDirectories = $parsedFqn[2];
385
386 24
        if (\in_array(
387 24
            $hasType,
388 24
            RelationsGenerator::HAS_TYPES_PLURAL,
389 24
            true
390
        )) {
391 23
            return $this->getPluralNamespacedName($ownedEntityFqn, $subDirectories);
392
        }
393
394 8
        return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories);
395
    }
396
397
    /**
398
     * From the fully qualified name, parse out:
399
     *  - class name,
400
     *  - namespace
401
     *  - the namespace parts not including the project root namespace
402
     *
403
     * @param string      $fqn
404
     *
405
     * @param string      $srcOrTestSubFolder eg 'src' or 'test'
406
     *
407
     * @param string|null $projectRootNamespace
408
     *
409
     * @return array [$className,$namespace,$subDirectories]
410
     * @throws DoctrineStaticMetaException
411
     */
412 137
    public function parseFullyQualifiedName(
413
        string $fqn,
414
        string $srcOrTestSubFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER,
415
        string $projectRootNamespace = null
416
    ): array {
417
        try {
418 137
            $fqn = $this->root($fqn);
419 137
            if (null === $projectRootNamespace) {
420
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcOrTestSubFolder);
421
            }
422 137
            $projectRootNamespace = $this->root($projectRootNamespace);
423 137
            if (false === \ts\stringContains($fqn, $projectRootNamespace)) {
424
                throw new DoctrineStaticMetaException(
425
                    'The $fqn [' . $fqn . '] does not contain the project root namespace'
426
                    . ' [' . $projectRootNamespace . '] - are you sure it is the correct FQN?'
427
                );
428
            }
429 137
            $fqnParts       = explode('\\', $fqn);
430 137
            $className      = array_pop($fqnParts);
431 137
            $namespace      = implode('\\', $fqnParts);
432 137
            $rootParts      = explode('\\', $projectRootNamespace);
433 137
            $subDirectories = [];
434 137
            foreach ($fqnParts as $k => $fqnPart) {
435 137
                if (isset($rootParts[$k]) && $rootParts[$k] === $fqnPart) {
436 137
                    continue;
437
                }
438 137
                $subDirectories[] = $fqnPart;
439
            }
440 137
            array_unshift($subDirectories, $srcOrTestSubFolder);
441
442
            return [
443 137
                $className,
444 137
                $this->root($namespace),
445 137
                $subDirectories,
446
            ];
447
        } catch (\Exception $e) {
448
            throw new DoctrineStaticMetaException(
449
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
450
                $e->getCode(),
451
                $e
452
            );
453
        }
454
    }
455
456
    /**
457
     * Generate a tidy root namespace without a leading \
458
     *
459
     * @param string $namespace
460
     *
461
     * @return string
462
     */
463 137
    public function root(string $namespace): string
464
    {
465 137
        return $this->tidy(ltrim($namespace, '\\'));
466
    }
467
468
    /**
469
     * Read src autoloader from composer json
470
     *
471
     * @param string $dirForNamespace
472
     *
473
     * @return string
474
     * @throws DoctrineStaticMetaException
475
     * @SuppressWarnings(PHPMD.StaticAccess)
476
     */
477 138
    public function getProjectRootNamespaceFromComposerJson(
478
        string $dirForNamespace = 'src'
479
    ): string {
480
        try {
481 138
            $dirForNamespace = trim($dirForNamespace, '/');
482 138
            $json            = json_decode(
483 138
                \ts\file_get_contents(Config::getProjectRootDirectory() . '/composer.json'),
484 138
                true
485
            );
486
            /**
487
             * @var string[][][][] $json
488
             */
489 138
            if (isset($json['autoload']['psr-4'])) {
490 138
                foreach ($json['autoload']['psr-4'] as $namespace => $dirs) {
491 138
                    foreach ($dirs as $dir) {
492 138
                        $dir = trim($dir, '/');
493 138
                        if ($dir === $dirForNamespace) {
494 138
                            return $this->tidy(rtrim($namespace, '\\'));
495
                        }
496
                    }
497
                }
498
            }
499
        } catch (\Exception $e) {
500
            throw new DoctrineStaticMetaException(
501
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
502
                $e->getCode(),
503
                $e
504
            );
505
        }
506
        throw new DoctrineStaticMetaException('Failed to find psr-4 namespace root');
507
    }
508
509
    /**
510
     * @param string $entityFqn
511
     * @param array  $subDirectories
512
     *
513
     * @return string
514
     * @SuppressWarnings(PHPMD.StaticAccess)
515
     */
516 28
    public function getPluralNamespacedName(string $entityFqn, array $subDirectories): string
517
    {
518 28
        $plural = \ucfirst(MappingHelper::getPluralForFqn($entityFqn));
519
520 28
        return $this->getNamespacedName($plural, $subDirectories);
521
    }
522
523
    /**
524
     * @param string $entityName
525
     * @param array  $subDirectories
526
     *
527
     * @return string
528
     */
529 28
    public function getNamespacedName(string $entityName, array $subDirectories): string
530
    {
531 28
        $noEntitiesDirectory = \array_slice($subDirectories, 2);
532 28
        $namespacedName      = \array_merge($noEntitiesDirectory, [$entityName]);
533
534 28
        return \ucfirst(\implode('', $namespacedName));
535
    }
536
537
    /**
538
     * @param string $entityFqn
539
     * @param array  $subDirectories
540
     *
541
     * @return string
542
     * @SuppressWarnings(PHPMD.StaticAccess)
543
     */
544 28
    public function getSingularNamespacedName(string $entityFqn, array $subDirectories): string
545
    {
546 28
        $singular = \ucfirst(MappingHelper::getSingularForFqn($entityFqn));
547
548 28
        return $this->getNamespacedName($singular, $subDirectories);
549
    }
550
551
    /**
552
     * Get the Namespace root for Entity Relations
553
     *
554
     * @param string $projectRootNamespace
555
     * @param array  $subDirectories
556
     *
557
     * @return string
558
     */
559 24
    public function getOwningRelationsRootFqn(
560
        string $projectRootNamespace,
561
        array $subDirectories
562
    ): string {
563
        $relationsRootFqn = $projectRootNamespace
564 24
                            . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE . '\\';
565 24
        if (count($subDirectories) > 0) {
566 24
            $relationsRootFqn .= implode('\\', $subDirectories) . '\\';
567
        }
568
569 24
        return $this->tidy($relationsRootFqn);
570
    }
571
572
    /**
573
     * Normalise a has type, removing prefixes that are not required
574
     *
575
     * Inverse hasTypes use the standard template without the prefix
576
     * The exclusion ot this are the ManyToMany and OneToOne relations
577
     *
578
     * @param string $hasType
579
     *
580
     * @return string
581
     */
582 24
    public function stripPrefixFromHasType(
583
        string $hasType
584
    ): string {
585
        foreach ([
586 24
                     RelationsGenerator::INTERNAL_TYPE_MANY_TO_MANY,
587
                     RelationsGenerator::INTERNAL_TYPE_ONE_TO_ONE,
588
                 ] as $noStrip) {
589 24
            if (\ts\stringContains($hasType, $noStrip)) {
590 24
                return $hasType;
591
            }
592
        }
593
594
        foreach ([
595 6
                     RelationsGenerator::INTERNAL_TYPE_ONE_TO_MANY,
596
                     RelationsGenerator::INTERNAL_TYPE_MANY_TO_ONE,
597
                 ] as $stripAll) {
598 6
            if (\ts\stringContains($hasType, $stripAll)) {
599 6
                return str_replace(
600
                    [
601 6
                        RelationsGenerator::PREFIX_OWNING,
602
                        RelationsGenerator::PREFIX_INVERSE,
603
                    ],
604 6
                    '',
605 6
                    $hasType
606
                );
607
            }
608
        }
609
610
        return str_replace(
611
            [
612
                RelationsGenerator::PREFIX_INVERSE,
613
            ],
614
            '',
615
            $hasType
616
        );
617
    }
618
619
    /**
620
     * @param string $ownedEntityFqn
621
     * @param string $srcOrTestSubFolder
622
     * @param string $projectRootNamespace
623
     *
624
     * @return string
625
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
626
     */
627 24
    public function getReciprocatedHasName(
628
        string $ownedEntityFqn,
629
        string $srcOrTestSubFolder,
630
        string $projectRootNamespace
631
    ): string {
632 24
        $parsedFqn = $this->parseFullyQualifiedName(
633 24
            $ownedEntityFqn,
634 24
            $srcOrTestSubFolder,
635 24
            $projectRootNamespace
636
        );
637
638 24
        $subDirectories = $parsedFqn[2];
639
640 24
        return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories);
641
    }
642
643
    /**
644
     * Get the Fully Qualified Namespace for the Relation Interface for a specific Entity and hasType
645
     *
646
     * @param string      $hasType
647
     * @param string      $ownedEntityFqn
648
     * @param string|null $projectRootNamespace
649
     * @param string      $srcFolder
650
     *
651
     * @return string
652
     * @throws DoctrineStaticMetaException
653
     */
654 24
    public function getOwningInterfaceFqn(
655
        string $hasType,
656
        string $ownedEntityFqn,
657
        string $projectRootNamespace = null,
658
        string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER
659
    ): string {
660
        try {
661 24
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, $projectRootNamespace);
0 ignored issues
show
Bug introduced by
It seems like $projectRootNamespace can also be of type null; however, parameter $projectRootNamespace of EdmondsCommerce\Doctrine...lper::getOwnedHasName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

661
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
662 24
            if (null === $projectRootNamespace) {
663
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
664
            }
665 24
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
666 24
                $ownedEntityFqn,
667 24
                $srcFolder,
668 24
                $projectRootNamespace
669
            );
670 24
            $interfaceSubDirectories = \array_slice($ownedSubDirectories, 2);
671 24
            $owningInterfaceFqn      = $this->getOwningRelationsRootFqn(
672 24
                $projectRootNamespace,
673 24
                $interfaceSubDirectories
674
            );
675 24
            $owningInterfaceFqn      .= '\\' . $ownedClassName . '\\Interfaces\\Has' . $ownedHasName . 'Interface';
676
677 24
            return $this->tidy($owningInterfaceFqn);
678
        } catch (\Exception $e) {
679
            throw new DoctrineStaticMetaException(
680
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
681
                $e->getCode(),
682
                $e
683
            );
684
        }
685
    }
686
687 94
    public function getEntityInterfaceFromEntityFqn(string $entityFqn): string
688
    {
689 94
        return \str_replace(
690 94
            '\\Entities\\',
691 94
            '\\Entity\\Interfaces\\',
692 94
            $entityFqn
693 94
        ) . 'Interface';
694
    }
695
}
696