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 (#121)
by joseph
56:14
created

getNamespaceRootToDirectoryFromFqn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
rs 10
c 0
b 0
f 0
ccs 0
cts 7
cp 0
cc 2
nc 2
nop 2
crap 6
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
     * Get the fully qualified name of the Fixture class for a specified Entity fully qualified name
143
     *
144
     * @param string $entityFqn
145
     *
146
     * @return string
147
     */
148
    public function getFixtureFqnFromEntityFqn(string $entityFqn): string
149
    {
150
        return \str_replace(
151
            '\\Entities',
152
            '\\Assets\\EntityFixtures',
153
            $entityFqn
154
        ) . 'Fixture';
155
    }
156
157
    /**
158
     * Work out the entity namespace root from a single entity reflection object.
159
     *
160
     * @param \ts\Reflection\ReflectionClass $entityReflection
161
     *
162
     * @return string
163
     */
164
    public function getEntityNamespaceRootFromEntityReflection(
165
        \ts\Reflection\ReflectionClass $entityReflection
166
    ): string {
167
        return $this->tidy(
168
            $this->getNamespaceRootToDirectoryFromFqn(
169
                $entityReflection->getName(),
170
                AbstractGenerator::ENTITIES_FOLDER_NAME
171
            )
172
        );
173
    }
174
175
    /**
176
     * Get the namespace root up to and including a specified directory
177
     *
178
     * @param string $fqn
179
     * @param string $directory
180
     *
181
     * @return null|string
182
     */
183
    public function getNamespaceRootToDirectoryFromFqn(string $fqn, string $directory): ?string
184
    {
185
        $strPos = \strrpos(
186
            $fqn,
187
            $directory
188
        );
189
        if (false !== $strPos) {
190
            return $this->tidy(\substr($fqn, 0, $strPos + \strlen($directory)));
191
        }
192
193
        return null;
194
    }
195
196
    /**
197
     * Get the sub path for an Entity file, start from the Entities path - normally `/path/to/project/src/Entities`
198
     *
199
     * @param string $entityFqn
200
     *
201
     * @return string
202
     */
203
    public function getEntityFileSubPath(
204
        string $entityFqn
205
    ): string {
206
        return $this->getEntitySubPath($entityFqn) . '.php';
207
    }
208
209
    /**
210
     * Get the folder structure for an Entity, start from the Entities path - normally `/path/to/project/src/Entities`
211
     *
212
     * This is not the path to the file, but the sub path of directories for storing entity related items.
213
     *
214
     * @param string $entityFqn
215
     *
216
     * @return string
217
     */
218
    public function getEntitySubPath(
219
        string $entityFqn
220
    ): string {
221
        $entityPath = str_replace(
222
            '\\',
223
            '/',
224
            $this->getEntitySubNamespace($entityFqn)
225
        );
226
227
        return '/' . $entityPath;
228
    }
229
230
    /**
231
     * Get the Namespace for an Entity, start from the Entities Fully Qualified Name base - normally
232
     * `\My\Project\Entities\`
233
     *
234
     * @param string $entityFqn
235
     *
236
     * @return string
237
     */
238
    public function getEntitySubNamespace(
239
        string $entityFqn
240
    ): string {
241
        return $this->tidy(
242
            \substr(
243
                $entityFqn,
244
                \strrpos(
245
                    $entityFqn,
246
                    '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\'
247
                )
248
                + \strlen('\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\')
249
            )
250
        );
251
    }
252
253
    /**
254
     * Get the Fully Qualified Namespace root for Traits for the specified Entity
255
     *
256
     * @param string $entityFqn
257
     *
258
     * @return string
259
     */
260
    public function getTraitsNamespaceForEntity(
261
        string $entityFqn
262
    ): string {
263
        $traitsNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn)
264
                           . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE
265
                           . '\\' . $this->getEntitySubNamespace($entityFqn)
266
                           . '\\Traits';
267
268
        return $traitsNamespace;
269
    }
270
271
    /**
272
     * Use the fully qualified name of two Entities to calculate the Project Namespace Root
273
     *
274
     * - note: this assumes a single namespace level for entities, eg `Entities`
275
     *
276
     * @param string $entityFqn
277
     *
278
     * @return string
279
     */
280
    public function getProjectNamespaceRootFromEntityFqn(string $entityFqn): string
281
    {
282
        return $this->tidy(
283
            \substr(
284
                $entityFqn,
285
                0,
286
                \strrpos(
287
                    $entityFqn,
288
                    '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\'
289
                )
290
            )
291
        );
292
    }
293
294
    /**
295
     * Get the Fully Qualified Namespace for the "HasEntities" interface for the specified Entity
296
     *
297
     * @param string $entityFqn
298
     *
299
     * @return string
300
     */
301
    public function getHasPluralInterfaceFqnForEntity(
302
        string $entityFqn
303
    ): string {
304
        $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn);
305
306
        return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural()) . 'Interface';
307
    }
308
309
    /**
310
     * Get the Fully Qualified Namespace root for Interfaces for the specified Entity
311
     *
312
     * @param string $entityFqn
313
     *
314
     * @return string
315
     */
316
    public function getInterfacesNamespaceForEntity(
317
        string $entityFqn
318
    ): string {
319
        $interfacesNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn)
320
                               . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE
321
                               . '\\' . $this->getEntitySubNamespace($entityFqn)
322
                               . '\\Interfaces';
323
324
        return $this->tidy($interfacesNamespace);
325
    }
326
327
    /**
328
     * Get the Fully Qualified Namespace for the "HasEntity" interface for the specified Entity
329
     *
330
     * @param string $entityFqn
331
     *
332
     * @return string
333
     * @throws DoctrineStaticMetaException
334
     */
335
    public function getHasSingularInterfaceFqnForEntity(
336
        string $entityFqn
337
    ): string {
338
        try {
339
            $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn);
340
341
            return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular())
342
                   . 'Interface';
343
        } catch (\Exception $e) {
344
            throw new DoctrineStaticMetaException(
345
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
346
                $e->getCode(),
347
                $e
348
            );
349
        }
350
    }
351
352
    /**
353
     * Get the Fully Qualified Namespace for the Relation Trait for a specific Entity and hasType
354
     *
355
     * @param string      $hasType
356
     * @param string      $ownedEntityFqn
357
     * @param string|null $projectRootNamespace
358
     * @param string      $srcFolder
359
     *
360
     * @return string
361
     * @throws DoctrineStaticMetaException
362
     */
363
    public function getOwningTraitFqn(
364
        string $hasType,
365
        string $ownedEntityFqn,
366
        ?string $projectRootNamespace = null,
367
        string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER
368
    ): string {
369
        try {
370
            $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

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

702
            $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, /** @scrutinizer ignore-type */ $projectRootNamespace);
Loading history...
703
            if (null === $projectRootNamespace) {
704
                $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder);
705
            }
706
            list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName(
707
                $ownedEntityFqn,
708
                $srcFolder,
709
                $projectRootNamespace
710
            );
711
            $interfaceSubDirectories = \array_slice($ownedSubDirectories, 2);
712
            $owningInterfaceFqn      = $this->getOwningRelationsRootFqn(
713
                $projectRootNamespace,
714
                $interfaceSubDirectories
715
            );
716
            $owningInterfaceFqn      .= '\\' . $ownedClassName . '\\Interfaces\\Has' . $ownedHasName . 'Interface';
717
718
            return $this->tidy($owningInterfaceFqn);
719
        } catch (\Exception $e) {
720
            throw new DoctrineStaticMetaException(
721
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
722
                $e->getCode(),
723
                $e
724
            );
725
        }
726
    }
727
728
    public function getEntityInterfaceFromEntityFqn(string $entityFqn): string
729
    {
730
        return \str_replace(
731
            '\\Entities\\',
732
            '\\Entity\\Interfaces\\',
733
            $entityFqn
734
        ) . 'Interface';
735
    }
736
}
737