Passed
Pull Request — master (#20)
by
unknown
02:14
created

Entities::normalizeNames()   C

Complexity

Conditions 13
Paths 205

Size

Total Lines 61
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 34
c 3
b 1
f 0
dl 0
loc 61
rs 5.7208
cc 13
nc 205
nop 1

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\Entity;
8
use Cycle\Annotated\Exception\AnnotationException;
9
use Cycle\Schema\Definition\Entity as EntitySchema;
10
use Cycle\Schema\Exception\RegistryException;
11
use Cycle\Schema\Exception\RelationException;
12
use Cycle\Schema\GeneratorInterface;
13
use Cycle\Schema\Registry;
14
use Doctrine\Common\Annotations\Reader as DoctrineReader;
15
use Doctrine\Inflector\Inflector;
16
use Doctrine\Inflector\Rules\English\InflectorFactory;
17
use Spiral\Attributes\ReaderInterface;
18
use Spiral\Tokenizer\ClassesInterface;
19
20
/**
21
 * Generates ORM schema based on annotated classes.
22
 */
23
final class Entities implements GeneratorInterface
24
{
25
    // table name generation
26
    public const TABLE_NAMING_PLURAL   = 1;
27
    public const TABLE_NAMING_SINGULAR = 2;
28
    public const TABLE_NAMING_NONE     = 3;
29
30
    /** @var ClassesInterface */
31
    private $locator;
32
33
    /** @var ReaderInterface */
34
    private $reader;
35
36
    /** @var Configurator */
37
    private $generator;
38
39
    /** @var int */
40
    private $tableNaming;
41
42
    /** @var Inflector */
43
    private $inflector;
44
45
    /**
46
     * @param ClassesInterface $locator
47
     * @param object<ReaderInterface|DoctrineReader>|null $reader
48
     * @param int $tableNaming
49
     */
50
    public function __construct(
51
        ClassesInterface $locator,
52
        object $reader = null,
53
        int $tableNaming = self::TABLE_NAMING_PLURAL
54
    ) {
55
        $this->locator = $locator;
56
        $this->reader = ReaderFactory::create($reader);
57
        $this->generator = new Configurator($this->reader);
58
        $this->tableNaming = $tableNaming;
59
        $this->inflector = (new InflectorFactory())->build();
60
    }
61
62
    /**
63
     * @param Registry $registry
64
     * @return Registry
65
     */
66
    public function run(Registry $registry): Registry
67
    {
68
        /** @var EntitySchema[] $children */
69
        $children = [];
70
        foreach ($this->locator->getClasses() as $class) {
71
            try {
72
                /** @var Entity $ann */
73
                $ann = $this->reader->firstClassMetadata($class, Entity::class);
74
            } catch (\Exception $e) {
75
                throw new AnnotationException($e->getMessage(), $e->getCode(), $e);
76
            }
77
78
            if ($ann === null) {
79
                continue;
80
            }
81
82
            $e = $this->generator->initEntity($ann, $class);
83
84
            // columns
85
            $this->generator->initFields($e, $class);
86
87
            // relations
88
            $this->generator->initRelations($e, $class);
89
90
            // additional columns (mapped to local fields automatically)
91
            $this->generator->initColumns($e, $ann->getColumns(), $class);
92
93
            if ($this->hasParent($registry, $e->getClass())) {
0 ignored issues
show
Bug introduced by
It seems like $e->getClass() can also be of type null; however, parameter $class of Cycle\Annotated\Entities::hasParent() 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

93
            if ($this->hasParent($registry, /** @scrutinizer ignore-type */ $e->getClass())) {
Loading history...
94
                $children[] = $e;
95
                continue;
96
            }
97
98
            // register entity (OR find parent)
99
            $registry->register($e);
100
101
            $registry->linkTable(
102
                $e,
103
                $ann->getDatabase(),
104
                $ann->getTable() ?? $this->tableName($e->getRole())
105
            );
106
        }
107
108
        foreach ($children as $e) {
109
            $registry->registerChild($registry->getEntity($this->findParent($registry, $e->getClass())), $e);
0 ignored issues
show
Bug introduced by
It seems like $this->findParent($registry, $e->getClass()) can also be of type null; however, parameter $role of Cycle\Schema\Registry::getEntity() 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

109
            $registry->registerChild($registry->getEntity(/** @scrutinizer ignore-type */ $this->findParent($registry, $e->getClass())), $e);
Loading history...
110
        }
111
112
        return $this->normalizeNames($registry);
113
    }
114
115
    /**
116
     * @param Registry $registry
117
     * @return Registry
118
     */
119
    protected function normalizeNames(Registry $registry): Registry
120
    {
121
        // resolve all the relation target names into roles
122
        foreach ($this->locator->getClasses() as $class) {
123
            if (!$registry->hasEntity($class->getName())) {
124
                continue;
125
            }
126
127
            $e = $registry->getEntity($class->getName());
128
129
            // relations
130
            foreach ($e->getRelations() as $name => $r) {
131
                try {
132
                    $r->setTarget($this->resolveTarget($registry, $r->getTarget()));
133
134
                    if ($r->getOptions()->has('though')) {
135
                        $though = $r->getOptions()->get('though');
136
                        if ($though !== null) {
137
                            $r->getOptions()->set(
138
                                'though',
139
                                $this->resolveTarget($registry, $though)
140
                            );
141
                        }
142
                    }
143
144
                    if ($r->getOptions()->has('through')) {
145
                        $through = $r->getOptions()->get('through');
146
                        if ($through !== null) {
147
                            $r->getOptions()->set(
148
                                'through',
149
                                $this->resolveTarget($registry, $through)
150
                            );
151
                        }
152
                    }
153
154
                    if ($r->getOptions()->has('throughInnerKey')) {
155
                        if ($throughInnerKey = (array)$r->getOptions()->get('throughInnerKey')) {
156
                            $r->getOptions()->set('throughInnerKey', $throughInnerKey[0]);
157
                        }
158
                    }
159
160
                    if ($r->getOptions()->has('throughOuterKey')) {
161
                        if ($throughOuterKey = (array)$r->getOptions()->get('throughOuterKey')) {
162
                            $r->getOptions()->set('throughOuterKey', $throughOuterKey[0]);
163
                        }
164
                    }
165
                } catch (RegistryException $ex) {
166
                    throw new RelationException(
167
                        sprintf(
168
                            'Unable to resolve `%s`.`%s` relation target (not found or invalid)',
169
                            $e->getRole(),
170
                            $name
171
                        ),
172
                        $ex->getCode(),
173
                        $ex
174
                    );
175
                }
176
            }
177
        }
178
179
        return $registry;
180
    }
181
182
    /**
183
     * @param Registry $registry
184
     * @param string   $name
185
     * @return string|null
186
     */
187
    protected function resolveTarget(Registry $registry, string $name): ?string
188
    {
189
        if (is_null($name) || interface_exists($name, true)) {
190
            // do not resolve interfaces
191
            return $name;
192
        }
193
194
        if (!$registry->hasEntity($name)) {
195
            // point all relations to the parent
196
            foreach ($registry as $entity) {
197
                foreach ($registry->getChildren($entity) as $child) {
198
                    if ($child->getClass() === $name || $child->getRole() === $name) {
199
                        return $entity->getRole();
200
                    }
201
                }
202
            }
203
        }
204
205
        return $registry->getEntity($name)->getRole();
206
    }
207
208
    /**
209
     * @param string $role
210
     * @return string
211
     */
212
    protected function tableName(string $role): string
213
    {
214
        $table = $this->inflector->tableize($role);
215
216
        switch ($this->tableNaming) {
217
            case self::TABLE_NAMING_PLURAL:
218
                return $this->inflector->pluralize($this->inflector->tableize($role));
219
220
            case self::TABLE_NAMING_SINGULAR:
221
                return $this->inflector->singularize($this->inflector->tableize($role));
222
223
            default:
224
                return $table;
225
        }
226
    }
227
228
    /**
229
     * @param Registry $registry
230
     * @param string   $class
231
     * @return bool
232
     */
233
    protected function hasParent(Registry $registry, string $class): bool
234
    {
235
        return $this->findParent($registry, $class) !== null;
236
    }
237
238
    /**
239
     * @param Registry $registry
240
     * @param string   $class
241
     * @return string|null
242
     */
243
    protected function findParent(Registry $registry, string $class): ?string
0 ignored issues
show
Unused Code introduced by
The parameter $registry is not used and could be removed. ( Ignorable by Annotation )

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

243
    protected function findParent(/** @scrutinizer ignore-unused */ Registry $registry, string $class): ?string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
244
    {
245
        $parents = class_parents($class);
246
247
        foreach (array_reverse($parents) as $parent) {
248
            try {
249
                $class = new \ReflectionClass($parent);
250
            } catch (\ReflectionException $e) {
251
                continue;
252
            }
253
254
            if ($class->getDocComment() === false) {
255
                continue;
256
            }
257
258
            $ann = $this->reader->firstClassMetadata($class, Entity::class);
259
            if ($ann !== null) {
260
                return $parent;
261
            }
262
        }
263
264
        return null;
265
    }
266
}
267