Passed
Push — master ( 4be7e9...eef0b9 )
by Anton
01:32
created

Entities   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 22
eloc 62
dl 0
loc 169
c 0
b 0
f 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A hasParent() 0 3 1
A __construct() 0 5 1
B run() 0 45 6
A findParent() 0 16 4
A tableName() 0 3 1
A resolveTarget() 0 8 3
A normalizeNames() 0 35 6
1
<?php declare(strict_types=1);
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Cycle\Annotated;
10
11
use Cycle\Annotated\Annotation\Entity;
12
use Cycle\Schema\Definition\Entity as EntitySchema;
13
use Cycle\Schema\Exception\RegistryException;
14
use Cycle\Schema\Exception\RelationException;
15
use Cycle\Schema\GeneratorInterface;
16
use Cycle\Schema\Registry;
17
use Doctrine\Common\Inflector\Inflector;
18
use Spiral\Annotations\Parser;
19
use Spiral\Tokenizer\ClassesInterface;
20
21
/**
22
 * Generates ORM schema based on annotated classes.
23
 */
24
final class Entities implements GeneratorInterface
25
{
26
    /** @var ClassesInterface */
27
    private $locator;
28
29
    /** @var Parser */
30
    private $parser;
31
32
    /** @var Generator */
33
    private $generator;
34
35
    /**
36
     * @param ClassesInterface $locator
37
     * @param Parser           $parser
38
     */
39
    public function __construct(ClassesInterface $locator, Parser $parser)
40
    {
41
        $this->locator = $locator;
42
        $this->parser = $parser;
43
        $this->generator = new Generator($parser);
44
    }
45
46
    /**
47
     * @param Registry $registry
48
     * @return Registry
49
     */
50
    public function run(Registry $registry): Registry
51
    {
52
        /** @var EntitySchema[] $children */
53
        $children = [];
54
        foreach ($this->locator->getClasses() as $class) {
55
            if ($class->getDocComment() === false) {
56
                continue;
57
            }
58
59
            $ann = $this->parser->parse($class->getDocComment());
60
            if (!isset($ann[Entity::NAME])) {
61
                continue;
62
            }
63
64
            /** @var Entity $ea */
65
            $ea = $ann[Entity::NAME];
66
67
            $e = $this->generator->initEntity($ea, $class);
68
69
            // columns
70
            $this->generator->initFields($e, $class);
71
72
            // relations
73
            $this->generator->initRelations($e, $class);
74
75
            if ($this->hasParent($registry, $e->getClass())) {
0 ignored issues
show
Bug introduced by Wolfy-J
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

75
            if ($this->hasParent($registry, /** @scrutinizer ignore-type */ $e->getClass())) {
Loading history...
76
                $children[] = $e;
77
                continue;
78
            }
79
80
            // register entity (OR find parent)
81
            $registry->register($e);
82
83
            $registry->linkTable(
84
                $e,
85
                $ea->getDatabase(),
86
                $ea->getTable() ?? $this->tableName($e->getRole())
87
            );
88
        }
89
90
        foreach ($children as $e) {
91
            $registry->registerChild($registry->getEntity($this->findParent($registry, $e->getClass())), $e);
0 ignored issues
show
Bug introduced by Wolfy-J
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

91
            $registry->registerChild($registry->getEntity(/** @scrutinizer ignore-type */ $this->findParent($registry, $e->getClass())), $e);
Loading history...
92
        }
93
94
        return $this->normalizeNames($registry);
95
    }
96
97
    /**
98
     * @param Registry $registry
99
     * @return Registry
100
     */
101
    protected function normalizeNames(Registry $registry): Registry
102
    {
103
        // resolve all the relation target names into roles
104
        foreach ($this->locator->getClasses() as $class) {
105
            if (!$registry->hasEntity($class->getName())) {
106
                continue;
107
            }
108
109
            $e = $registry->getEntity($class->getName());
110
111
            // relations
112
            foreach ($e->getRelations() as $name => $r) {
113
                try {
114
                    $r->setTarget($this->resolveTarget($registry, $r->getTarget()));
115
116
                    if ($r->getOptions()->has('though')) {
117
                        $r->getOptions()->set(
118
                            'though',
119
                            $this->resolveTarget($registry, $r->getOptions()->get('though'))
120
                        );
121
                    }
122
                } catch (RegistryException $ex) {
123
                    throw new RelationException(
124
                        sprintf("Unable to resolve `%s`.`%s` relation target (not found or invalid)",
125
                            $e->getRole(),
126
                            $name
127
                        ),
128
                        $ex->getCode(),
129
                        $ex
130
                    );
131
                }
132
            }
133
        }
134
135
        return $registry;
136
    }
137
138
    /**
139
     * @param Registry $registry
140
     * @param string   $name
141
     * @return string|null
142
     */
143
    protected function resolveTarget(Registry $registry, string $name): ?string
144
    {
145
        if (is_null($name) || interface_exists($name, true)) {
146
            // do not resolve interfaces
147
            return $name;
148
        }
149
150
        return $registry->getEntity($name)->getRole();
151
    }
152
153
    /**
154
     * @param string $role
155
     * @return string
156
     */
157
    protected function tableName(string $role): string
158
    {
159
        return Inflector::pluralize(Inflector::tableize($role));
160
    }
161
162
    /**
163
     * @param Registry $registry
164
     * @param string   $class
165
     * @return bool
166
     */
167
    protected function hasParent(Registry $registry, string $class): bool
168
    {
169
        return $this->findParent($registry, $class) !== null;
170
    }
171
172
    /**
173
     * @param Registry $registry
174
     * @param string   $class
175
     * @return string|null
176
     */
177
    protected function findParent(Registry $registry, string $class): ?string
0 ignored issues
show
Unused Code introduced by Wolfy-J
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

177
    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...
178
    {
179
        $parents = class_parents($class);
180
        foreach (array_reverse($parents) as $parent) {
181
            $class = new \ReflectionClass($parent);
182
            if ($class->getDocComment() === false) {
183
                continue;
184
            }
185
186
            $ann = $this->parser->parse($class->getDocComment());
0 ignored issues
show
Bug introduced by Wolfy-J
It seems like $class->getDocComment() can also be of type true; however, parameter $body of Spiral\Annotations\Parser::parse() 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

186
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $class->getDocComment());
Loading history...
187
            if (isset($ann[Entity::NAME])) {
188
                return $parent;
189
            }
190
        }
191
192
        return null;
193
    }
194
}