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 (#57)
by Ross
16:56
created

NamespaceHelper::getEntitySubPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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

335
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
336 23
            if (null === $projectRootNamespace) {
337
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
338
            }
339 23
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
340 23
                $ownedEntityFqn,
341 23
                $srcFolder,
342 23
                $projectRootNamespace
343
            );
344 23
            $traitSubDirectories = \array_slice($ownedSubDirectories, 2);
345 23
            $owningTraitFqn      = $this->getOwningRelationsRootFqn(
346 23
                $projectRootNamespace,
347 23
                $traitSubDirectories
348
            );
349 23
            $owningTraitFqn      .= $ownedClassName.'\\Traits\\Has'.$ownedHasName
350 23
                                    .'\\Has'.$ownedHasName.$this->stripPrefixFromHasType($hasType);
351
352 23
            return $this->tidy($owningTraitFqn);
353
        } catch (\Exception $e) {
354
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
355
        }
356
    }
357
358
    /**
359
     * Based on the $hasType, we calculate exactly what type of `Has` we have
360
     *
361
     * @param string $hasType
362
     * @param string $ownedEntityFqn
363
     * @param string $srcOrTestSubFolder
364
     *
365
     * @param string $projectRootNamespace
366
     *
367
     * @return string
368
     * @SuppressWarnings(PHPMD.StaticAccess)
369
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
370
     */
371 23
    public function getOwnedHasName(
372
        string $hasType,
373
        string $ownedEntityFqn,
374
        string $srcOrTestSubFolder,
375
        string $projectRootNamespace
376
    ): string {
377 23
        $parsedFqn = $this->parseFullyQualifiedName(
378 23
            $ownedEntityFqn,
379 23
            $srcOrTestSubFolder,
380 23
            $projectRootNamespace
381
        );
382
383 23
        $subDirectories = $parsedFqn[2];
384
385 23
        if (\in_array(
386 23
            $hasType,
387 23
            RelationsGenerator::HAS_TYPES_PLURAL,
388 23
            true
389
        )) {
390 23
            return $this->getPluralNamespacedName($ownedEntityFqn, $subDirectories);
391
        }
392
393 7
        return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories);
394
    }
395
396
    /**
397
     * @param string $ownedEntityFqn
398
     * @param string $srcOrTestSubFolder
399
     * @param string $projectRootNamespace
400
     *
401
     * @return string
402
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
403
     */
404 23
    public function getReciprocatedHasName(
405
        string $ownedEntityFqn,
406
        string $srcOrTestSubFolder,
407
        string $projectRootNamespace
408
    ): string {
409 23
        $parsedFqn = $this->parseFullyQualifiedName(
410 23
            $ownedEntityFqn,
411 23
            $srcOrTestSubFolder,
412 23
            $projectRootNamespace
413
        );
414
415 23
        $subDirectories = $parsedFqn[2];
416
417 23
        return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories);
418
    }
419
420
    /**
421
     * @param string $entityFqn
422
     * @param array  $subDirectories
423
     *
424
     * @return string
425
     * @SuppressWarnings(PHPMD.StaticAccess)
426
     */
427 26
    public function getSingularNamespacedName(string $entityFqn, array $subDirectories): string
428
    {
429 26
        $singular = \ucfirst(MappingHelper::getSingularForFqn($entityFqn));
430
431 26
        return $this->getNamespacedName($singular, $subDirectories);
432
    }
433
434
    /**
435
     * @param string $entityFqn
436
     * @param array  $subDirectories
437
     *
438
     * @return string
439
     * @SuppressWarnings(PHPMD.StaticAccess)
440
     */
441 26
    public function getPluralNamespacedName(string $entityFqn, array $subDirectories): string
442
    {
443 26
        $plural = \ucfirst(MappingHelper::getPluralForFqn($entityFqn));
444
445 26
        return $this->getNamespacedName($plural, $subDirectories);
446
    }
447
448
    /**
449
     * @param string $entityName
450
     * @param array  $subDirectories
451
     *
452
     * @return string
453
     */
454 26
    public function getNamespacedName(string $entityName, array $subDirectories): string
455
    {
456 26
        $noEntitiesDirectory = \array_slice($subDirectories, 2);
457 26
        $namespacedName      = \array_merge($noEntitiesDirectory, [$entityName]);
458
459 26
        return \ucfirst(\implode('', $namespacedName));
460
    }
461
462
    /**
463
     * Read src autoloader from composer json
464
     *
465
     * @param string $dirForNamespace
466
     *
467
     * @return string
468
     * @throws DoctrineStaticMetaException
469
     * @SuppressWarnings(PHPMD.StaticAccess)
470
     */
471 110
    public function getProjectRootNamespaceFromComposerJson(
472
        string $dirForNamespace = 'src'
473
    ): string {
474
        try {
475 110
            $dirForNamespace = trim($dirForNamespace, '/');
476 110
            $json            = json_decode(
477 110
                file_get_contents(Config::getProjectRootDirectory().'/composer.json'),
478 110
                true
479
            );
480
            /**
481
             * @var string[][][][] $json
482
             */
483 110
            if (isset($json['autoload']['psr-4'])) {
484 110
                foreach ($json['autoload']['psr-4'] as $namespace => $dirs) {
485 110
                    foreach ($dirs as $dir) {
486 110
                        $dir = trim($dir, '/');
487 110
                        if ($dir === $dirForNamespace) {
488 110
                            return $this->tidy(rtrim($namespace, '\\'));
489
                        }
490
                    }
491
                }
492
            }
493
        } catch (\Exception $e) {
494
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
495
        }
496
        throw new DoctrineStaticMetaException('Failed to find psr-4 namespace root');
497
    }
498
499
    /**
500
     * From the fully qualified name, parse out:
501
     *  - class name,
502
     *  - namespace
503
     *  - the namespace parts not including the project root namespace
504
     *
505
     * @param string $fqn
506
     *
507
     * @param string $srcOrTestSubFolder
508
     *
509
     * @param string $projectRootNamespace
510
     *
511
     * @return array [$className,$namespace,$subDirectories]
512
     * @throws DoctrineStaticMetaException
513
     */
514 109
    public function parseFullyQualifiedName(
515
        string $fqn,
516
        string $srcOrTestSubFolder,
517
        string $projectRootNamespace = null
518
    ): array {
519
        try {
520 109
            $fqn                  = $this->root($fqn);
521 109
            $projectRootNamespace = $this->root($projectRootNamespace);
0 ignored issues
show
Bug introduced by
It seems like $projectRootNamespace can also be of type null; however, parameter $namespace of EdmondsCommerce\Doctrine...NamespaceHelper::root() 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

521
            $projectRootNamespace = $this->root(/** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
522 109
            if (null === $projectRootNamespace) {
0 ignored issues
show
introduced by
The condition null === $projectRootNamespace is always false.
Loading history...
523
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcOrTestSubFolder);
524
            }
525 109
            if (false === \strpos($fqn, $projectRootNamespace)) {
526
                throw new DoctrineStaticMetaException(
527
                    'The $fqn ['.$fqn.'] does not contain the project root namespace'
528
                    .' ['.$projectRootNamespace.'] - are you sure it is the correct FQN?'
529
                );
530
            }
531 109
            $fqnParts       = explode('\\', $fqn);
532 109
            $className      = array_pop($fqnParts);
533 109
            $namespace      = implode('\\', $fqnParts);
534 109
            $rootParts      = explode('\\', $projectRootNamespace);
535 109
            $subDirectories = [];
536 109
            foreach ($fqnParts as $k => $fqnPart) {
537 109
                if (isset($rootParts[$k]) && $rootParts[$k] === $fqnPart) {
538 109
                    continue;
539
                }
540 109
                $subDirectories[] = $fqnPart;
541
            }
542 109
            array_unshift($subDirectories, $srcOrTestSubFolder);
543
544
            return [
545 109
                $className,
546 109
                $this->root($namespace),
547 109
                $subDirectories,
548
            ];
549
        } catch (\Exception $e) {
550
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
551
        }
552
    }
553
554
    /**
555
     * Get the Namespace root for Entity Relations
556
     *
557
     * @param string $projectRootNamespace
558
     * @param array  $subDirectories
559
     *
560
     * @return string
561
     */
562 23
    public function getOwningRelationsRootFqn(
563
        string $projectRootNamespace,
564
        array $subDirectories
565
    ): string {
566
        $relationsRootFqn = $projectRootNamespace
567 23
                            .AbstractGenerator::ENTITY_RELATIONS_NAMESPACE.'\\';
568 23
        if (count($subDirectories) > 0) {
569 23
            $relationsRootFqn .= implode('\\', $subDirectories).'\\';
570
        }
571
572 23
        return $this->tidy($relationsRootFqn);
573
    }
574
575
    /**
576
     * Normalise a has type, removing prefixes that are not required
577
     *
578
     * Inverse hasTypes use the standard template without the prefix
579
     * The exclusion ot this are the ManyToMany and OneToOne relations
580
     *
581
     * @param string $hasType
582
     *
583
     * @return string
584
     */
585 23
    public function stripPrefixFromHasType(
586
        string $hasType
587
    ): string {
588
        foreach ([
589 23
                     RelationsGenerator::INTERNAL_TYPE_MANY_TO_MANY,
590
                     RelationsGenerator::INTERNAL_TYPE_ONE_TO_ONE,
591
                 ] as $noStrip) {
592 23
            if (false !== strpos($hasType, $noStrip)) {
593 23
                return $hasType;
594
            }
595
        }
596
597
        foreach ([
598 6
                     RelationsGenerator::INTERNAL_TYPE_ONE_TO_MANY,
599
                     RelationsGenerator::INTERNAL_TYPE_MANY_TO_ONE,
600
                 ] as $stripAll) {
601 6
            if (false !== strpos($hasType, $stripAll)) {
602 6
                return str_replace(
603
                    [
604 6
                        RelationsGenerator::PREFIX_OWNING,
605
                        RelationsGenerator::PREFIX_INVERSE,
606
                    ],
607 6
                    '',
608 6
                    $hasType
609
                );
610
            }
611
        }
612
613
        return str_replace(
614
            [
615
                RelationsGenerator::PREFIX_INVERSE,
616
            ],
617
            '',
618
            $hasType
619
        );
620
    }
621
622
    /**
623
     * Get the Fully Qualified Namespace for the Relation Interface for a specific Entity and hasType
624
     *
625
     * @param string      $hasType
626
     * @param string      $ownedEntityFqn
627
     * @param string|null $projectRootNamespace
628
     * @param string      $srcFolder
629
     *
630
     * @return string
631
     * @throws DoctrineStaticMetaException
632
     */
633 23
    public function getOwningInterfaceFqn(
634
        string $hasType,
635
        string $ownedEntityFqn,
636
        string $projectRootNamespace = null,
637
        string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER
638
    ): string {
639
        try {
640 23
            $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

640
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
641 23
            if (null === $projectRootNamespace) {
642
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
643
            }
644 23
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
645 23
                $ownedEntityFqn,
646 23
                $srcFolder,
647 23
                $projectRootNamespace
648
            );
649 23
            $interfaceSubDirectories = \array_slice($ownedSubDirectories, 2);
650 23
            $owningInterfaceFqn      = $this->getOwningRelationsRootFqn(
651 23
                $projectRootNamespace,
652 23
                $interfaceSubDirectories
653
            );
654 23
            $owningInterfaceFqn      .= '\\'.$ownedClassName.'\\Interfaces\\Has'.$ownedHasName.'Interface';
655
656 23
            return $this->tidy($owningInterfaceFqn);
657
        } catch (\Exception $e) {
658
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
659
        }
660
    }
661
}
662