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 (#101)
by joseph
18:59
created

NamespaceHelper::getEntitySubPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

685
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
686
            if (null === $projectRootNamespace) {
687
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
688
            }
689
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
690
                $ownedEntityFqn,
691
                $srcFolder,
692
                $projectRootNamespace
693
            );
694
            $interfaceSubDirectories = \array_slice($ownedSubDirectories, 2);
695
            $owningInterfaceFqn      = $this->getOwningRelationsRootFqn(
696
                $projectRootNamespace,
697
                $interfaceSubDirectories
698
            );
699
            $owningInterfaceFqn      .= '\\' . $ownedClassName . '\\Interfaces\\Has' . $ownedHasName . 'Interface';
700
701
            return $this->tidy($owningInterfaceFqn);
702
        } catch (\Exception $e) {
703
            throw new DoctrineStaticMetaException(
704
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
705
                $e->getCode(),
706
                $e
707
            );
708
        }
709
    }
710
711
    public function getEntityInterfaceFromEntityFqn(string $entityFqn): string
712
    {
713
        return \str_replace(
714
            '\\Entities\\',
715
            '\\Entity\\Interfaces\\',
716
            $entityFqn
717
        ) . 'Interface';
718
    }
719
}
720