Passed
Push — 3.x ( 080893...4e5cb3 )
by Aleksei
09:48
created

TableInheritance::run()   B

Complexity

Conditions 11
Paths 48

Size

Total Lines 60
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 31
nc 48
nop 1
dl 0
loc 60
ccs 29
cts 29
cp 1
crap 11
rs 7.3166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\Annotated;
6
7
use Cycle\Annotated\Annotation\Inheritance;
8
use Cycle\Annotated\Exception\AnnotationException;
9
use Cycle\Annotated\Utils\EntityUtils;
10
use Cycle\Schema\Definition\Entity as EntitySchema;
11
use Cycle\Schema\Definition\Inheritance\JoinedTable as JoinedTableInheritanceSchema;
12
use Cycle\Schema\Definition\Inheritance\SingleTable as SingleTableInheritanceSchema;
13
use Cycle\Schema\GeneratorInterface;
14
use Cycle\Schema\Registry;
15
use Doctrine\Common\Annotations\Reader as DoctrineReader;
16
use Spiral\Attributes\ReaderInterface;
17
18
class TableInheritance implements GeneratorInterface
19
{
20
    private ReaderInterface $reader;
21
    private EntityUtils $utils;
22
23 36
    public function __construct(
24
        DoctrineReader|ReaderInterface $reader = null
25
    ) {
26 36
        $this->reader = ReaderFactory::create($reader);
27 36
        $this->utils = new EntityUtils($this->reader);
28 36
    }
29
30 36
    public function run(Registry $registry): Registry
31
    {
32
        /** @var EntitySchema[] $found */
33 36
        $found = [];
34
35 36
        foreach ($registry as $entity) {
36
            // Only child entities can have table inheritance annotation
37 36
            $children = $registry->getChildren($entity);
38
39 36
            foreach ($children as $child) {
40
                /** @var Inheritance $annotation */
41 36
                if ($annotation = $this->parseMetadata($child, Inheritance::class)) {
42 36
                    $childClass = $child->getClass();
43
44
                    // Child entities always have parent entity
45
                    do {
46 36
                        $parent = $this->findParent(
47
                            $registry,
48 36
                            $this->utils->findParent($childClass, false)
0 ignored issues
show
Bug introduced by
It seems like $this->utils->findParent($childClass, false) can also be of type null; however, parameter $role of Cycle\Annotated\TableInheritance::findParent() 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

48
                            /** @scrutinizer ignore-type */ $this->utils->findParent($childClass, false)
Loading history...
49
                        );
50
51 36
                        if ($annotation instanceof Inheritance\JoinedTable) {
52 36
                            break;
53
                        }
54
55 36
                        $childClass = $parent->getClass();
56 36
                    } while ($this->parseMetadata($parent, Inheritance::class) !== null);
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type null; however, parameter $entity of Cycle\Annotated\TableInheritance::parseMetadata() does only seem to accept Cycle\Schema\Definition\Entity, 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

56
                    } while ($this->parseMetadata(/** @scrutinizer ignore-type */ $parent, Inheritance::class) !== null);
Loading history...
57
58 36
                    if ($entity = $this->initInheritance($annotation, $child, $parent)) {
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type null; however, parameter $parent of Cycle\Annotated\TableInh...ance::initInheritance() does only seem to accept Cycle\Schema\Definition\Entity, 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

58
                    if ($entity = $this->initInheritance($annotation, $child, /** @scrutinizer ignore-type */ $parent)) {
Loading history...
59 36
                        $found[] = $entity;
60
                    }
61
                }
62
63
                // All child should be presented in a schema as separated entity
64
                // Every child will be handled according its table inheritance type
65 36
                if (!$registry->hasEntity($child->getRole())) {
66 36
                    $registry->register($child);
67
68 36
                    $registry->linkTable(
69
                        $child,
70 36
                        $child->getDatabase(),
71 36
                        $child->getTableName(),
72
                    );
73
                }
74
            }
75
        }
76
77 36
        foreach ($found as $entity) {
78 36
            if ($entity->getInheritance() instanceof SingleTableInheritanceSchema) {
79 36
                $allowedEntities = \array_map(
80 36
                    static fn (string $role) => $registry->getEntity($role)->getClass(),
81 36
                    $entity->getInheritance()->getChildren()
82
                );
83 36
                $this->removeStiExtraFields($entity, $allowedEntities);
84 36
            } elseif ($entity->getInheritance() instanceof JoinedTableInheritanceSchema) {
85 36
                $this->removeJtiExtraFields($entity);
86
            }
87
        }
88
89 36
        return $registry;
90
    }
91
92 36
    private function findParent(Registry $registry, string $role): ?EntitySchema
93
    {
94 36
        foreach ($registry as $entity) {
95 36
            if ($entity->getRole() === $role || $entity->getClass() === $role) {
96 36
                return $entity;
97
            }
98
99 36
            $children = $registry->getChildren($entity);
100 36
            foreach ($children as $child) {
101 36
                if ($child->getRole() === $role || $child->getClass() === $role) {
102 36
                    return $child;
103
                }
104
            }
105
        }
106
107
        return null;
108
    }
109
110 36
    private function initInheritance(
111
        Inheritance $inheritance,
112
        EntitySchema $entity,
113
        EntitySchema $parent
114
    ): ?EntitySchema {
115 36
        if ($inheritance instanceof Inheritance\SingleTable) {
116 36
            if (!$parent->getInheritance() instanceof SingleTableInheritanceSchema) {
117 36
                $parent->setInheritance(new SingleTableInheritanceSchema());
118
            }
119
120 36
            $parent->getInheritance()->addChild(
0 ignored issues
show
Bug introduced by
The method addChild() does not exist on Cycle\Schema\Definition\Inheritance. It seems like you code against a sub-type of Cycle\Schema\Definition\Inheritance such as Cycle\Schema\Definition\Inheritance\SingleTable. ( Ignorable by Annotation )

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

120
            $parent->getInheritance()->/** @scrutinizer ignore-call */ addChild(
Loading history...
121 36
                $inheritance->getValue() ?? $entity->getRole(),
0 ignored issues
show
Bug introduced by
It seems like $inheritance->getValue() ?? $entity->getRole() can also be of type null; however, parameter $discriminatorValue of Cycle\Schema\Definition\...SingleTable::addChild() 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

121
                /** @scrutinizer ignore-type */ $inheritance->getValue() ?? $entity->getRole(),
Loading history...
122 36
                $entity->getClass()
0 ignored issues
show
Bug introduced by
It seems like $entity->getClass() can also be of type null; however, parameter $class of Cycle\Schema\Definition\...SingleTable::addChild() 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

122
                /** @scrutinizer ignore-type */ $entity->getClass()
Loading history...
123
            );
124
125 36
            $entity->markAsChildOfSingleTableInheritance($parent->getClass());
126
127
            // Root STI may have a discriminator annotation
128
            /** @var Inheritance\DiscriminatorColumn $annotation */
129 36
            if ($annotation = $this->parseMetadata($parent, Inheritance\DiscriminatorColumn::class)) {
130 36
                $parent->getInheritance()->setDiscriminator($annotation->getName());
0 ignored issues
show
Bug introduced by
The method setDiscriminator() does not exist on Cycle\Schema\Definition\Inheritance. It seems like you code against a sub-type of Cycle\Schema\Definition\Inheritance such as Cycle\Schema\Definition\Inheritance\SingleTable. ( Ignorable by Annotation )

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

130
                $parent->getInheritance()->/** @scrutinizer ignore-call */ setDiscriminator($annotation->getName());
Loading history...
131
            }
132
133 36
            return $parent;
134
        }
135 36
        if ($inheritance instanceof Inheritance\JoinedTable) {
136 36
            $entity->setInheritance(
137 36
                new JoinedTableInheritanceSchema(
138
                    $parent,
139 36
                    $inheritance->getOuterKey()
140
                )
141
            );
142
143 36
            return $entity;
144
        }
145
146
        // Custom table inheritance types developers can handle in their own generators
147
        return null;
148
    }
149
150
    /**
151
     * @template T
152
     *
153
     * @param class-string<T> $name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
154
     *
155
     * @return T|null
156
     */
157 36
    private function parseMetadata(EntitySchema $entity, string $name): ?object
158
    {
159
        try {
160 36
            $class = $entity->getClass();
161
            assert($class !== null);
162 36
            return $this->reader->firstClassMetadata(new \ReflectionClass($class), $name);
163
        } catch (\Exception $e) {
164
            throw new AnnotationException($e->getMessage(), $e->getCode(), $e);
165
        }
166
    }
167
168
    /**
169
     * Removes parent entity fields from given entity except Primary key.
170
     */
171 36
    private function removeJtiExtraFields(EntitySchema $entity): void
172
    {
173 36
        foreach ($entity->getFields() as $name => $field) {
174 36
            if ($field->getEntityClass() === $entity->getClass()) {
175 36
                continue;
176
            }
177
178 36
            if (!$field->isPrimary()) {
179 36
                $entity->getFields()->remove($name);
180
            } else {
181 36
                if ($field->getType() === 'primary') {
182 36
                    $field->setType('integer')->setPrimary(true);
183
                } elseif ($field->getType() === 'bigPrimary') {
184
                    $field->setType('bigInteger')->setPrimary(true);
185
                }
186
            }
187
        }
188 36
    }
189
190
    /**
191
     * Removes non STI child entity fields from given entity.
192
     */
193 36
    private function removeStiExtraFields(EntitySchema $entity, array $allowedEntities): void
194
    {
195 36
        $allowedEntities[] = $entity->getClass();
196
197 36
        foreach ($entity->getFields() as $name => $field) {
198 36
            if (\in_array($field->getEntityClass(), $allowedEntities, true)) {
199 36
                continue;
200
            }
201
202 36
            $entity->getFields()->remove($name);
203
        }
204 36
    }
205
}
206