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

NamespaceHelper::tidy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

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

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

645
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
646 22
            if (null === $projectRootNamespace) {
647
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
648
            }
649 22
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
650 22
                $ownedEntityFqn,
651 22
                $srcFolder,
652 22
                $projectRootNamespace
653
            );
654 22
            $interfaceSubDirectories = \array_slice($ownedSubDirectories, 2);
655 22
            $owningInterfaceFqn      = $this->getOwningRelationsRootFqn(
656 22
                $projectRootNamespace,
657 22
                $interfaceSubDirectories
658
            );
659 22
            $owningInterfaceFqn      .= '\\'.$ownedClassName.'\\Interfaces\\Has'.$ownedHasName.'Interface';
660
661 22
            return $this->tidy($owningInterfaceFqn);
662
        } catch (\Exception $e) {
663
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
664
        }
665
    }
666
667 92
    public function getEntityInterfaceFromEntityFqn(string $entityFqn): string
668
    {
669 92
        return \str_replace(
670 92
            '\\Entities\\',
671 92
            '\\Entity\\Interfaces\\',
672 92
            $entityFqn
673 92
        ).'Interface';
674
    }
675
}
676