Passed
Push — master ( c68569...4be7e9 )
by Anton
01:54
created

Generator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 75
dl 0
loc 191
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A initColumns() 0 18 5
A initField() 0 28 5
A initEntity() 0 18 2
A __construct() 0 3 1
A initFields() 0 13 4
A resolveName() 0 17 4
B initRelations() 0 40 9
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\Column;
12
use Cycle\Annotated\Annotation\Entity;
13
use Cycle\Annotated\Annotation\Relation\RelationInterface;
14
use Cycle\Annotated\Exception\AnnotationException;
15
use Cycle\Schema\Definition\Entity as EntitySchema;
16
use Cycle\Schema\Definition\Field;
17
use Cycle\Schema\Definition\Relation;
18
use Cycle\Schema\Generator\SyncTables;
19
use Doctrine\Common\Inflector\Inflector;
20
use Spiral\Annotations\Parser;
21
22
final class Generator
23
{
24
    /** @var Parser */
25
    private $parser;
26
27
    /**
28
     * @param Parser $parser
29
     */
30
    public function __construct(Parser $parser)
31
    {
32
        $this->parser = $parser;
33
    }
34
35
    /**
36
     * @param Entity           $ann
37
     * @param \ReflectionClass $class
38
     * @return EntitySchema
39
     */
40
    public function initEntity(Entity $ann, \ReflectionClass $class): EntitySchema
41
    {
42
        $e = new EntitySchema();
43
        $e->setClass($class->getName());
44
45
        $e->setRole($ann->getRole() ?? Inflector::camelize($class->getShortName()));
46
47
        // representing classes
48
        $e->setMapper($this->resolveName($ann->getMapper(), $class));
49
        $e->setRepository($this->resolveName($ann->getRepository(), $class));
50
        $e->setSource($this->resolveName($ann->getSource(), $class));
51
        $e->setConstrain($this->resolveName($ann->getConstrain(), $class));
52
53
        if ($ann->isReadonlySchema()) {
54
            $e->getOptions()->set(SyncTables::READONLY_SCHEMA, true);
55
        }
56
57
        return $e;
58
    }
59
60
    /**
61
     * @param EntitySchema     $entity
62
     * @param \ReflectionClass $class
63
     */
64
    public function initFields(EntitySchema $entity, \ReflectionClass $class)
65
    {
66
        foreach ($class->getProperties() as $property) {
67
            if ($property->getDocComment() === false) {
68
                continue;
69
            }
70
71
            $ann = $this->parser->parse($property->getDocComment());
0 ignored issues
show
Bug introduced by Wolfy-J
It seems like $property->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

71
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $property->getDocComment());
Loading history...
72
            if (!isset($ann[Column::NAME])) {
73
                continue;
74
            }
75
76
            $entity->getFields()->set($property->getName(), $this->initField($property->getName(), $ann[Column::NAME]));
77
        }
78
    }
79
80
    /**
81
     * @param EntitySchema     $entity
82
     * @param \ReflectionClass $class
83
     */
84
    public function initRelations(EntitySchema $entity, \ReflectionClass $class)
85
    {
86
        foreach ($class->getProperties() as $property) {
87
            if ($property->getDocComment() === false) {
88
                continue;
89
            }
90
91
            $ann = $this->parser->parse($property->getDocComment());
0 ignored issues
show
Bug introduced by Wolfy-J
It seems like $property->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

91
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $property->getDocComment());
Loading history...
92
93
            foreach ($ann as $ra) {
94
                if (!$ra instanceof RelationInterface) {
95
                    continue;
96
                }
97
98
                if ($ra->getType() === null) {
99
                    throw new AnnotationException(
100
                        "Relation type definition is required on `{$entity->getClass()}`"
101
                    );
102
                }
103
104
                if ($ra->getTarget() === null) {
105
                    throw new AnnotationException(
106
                        "Relation target definition is required on `{$entity->getClass()}`"
107
                    );
108
                }
109
110
                $relation = new Relation();
111
                $relation->setTarget($this->resolveName($ra->getTarget(), $class));
112
                $relation->setType($ra->getType());
113
114
                if ($ra->isInversed()) {
115
                    $relation->setInverse($ra->getInverseName(), $ra->getInverseType());
116
                }
117
118
                foreach ($ra->getOptions() as $option => $value) {
119
                    $relation->getOptions()->set($option, $value);
120
                }
121
122
                // need relation definition
123
                $entity->getRelations()->set($property->getName(), $relation);
124
            }
125
        }
126
    }
127
128
    /**
129
     * @param EntitySchema $entity
130
     * @param Column[]     $columns
131
     */
132
    public function initColumns(EntitySchema $entity, array $columns)
133
    {
134
        foreach ($columns as $name => $column) {
135
            if ($column->getColumn() === null && is_numeric($name)) {
136
                throw new AnnotationException(
137
                    "Column name definition is required on `{$entity->getClass()}`"
138
                );
139
            }
140
141
            if ($column->getType() === null) {
142
                throw new AnnotationException(
143
                    "Column type definition is required on `{$entity->getClass()}`"
144
                );
145
            }
146
147
            $entity->getFields()->set(
148
                $name ?? $column->getColumn(),
0 ignored issues
show
Bug introduced by Wolfy-J
It seems like $name ?? $column->getColumn() can also be of type null; however, parameter $name of Cycle\Schema\Definition\Map\FieldMap::set() 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

148
                /** @scrutinizer ignore-type */ $name ?? $column->getColumn(),
Loading history...
149
                $this->initField($column->getColumn() ?? $name, $column)
150
            );
151
        }
152
    }
153
154
    /**
155
     * @param string $name
156
     * @param Column $column
157
     * @return Field
158
     */
159
    public function initField(string $name, Column $column): Field
160
    {
161
        if ($column->getType() === null) {
162
            throw new AnnotationException(
163
                "Column type definition is required on `{$name}`"
164
            );
165
        }
166
167
        $field = new Field();
168
169
        $field->setType($column->getType());
170
        $field->setColumn($column->getColumn() ?? Inflector::tableize($name));
171
        $field->setPrimary($column->isPrimary());
172
        $field->setTypecast($column->getTypecast());
173
174
        if ($column->isNullable()) {
175
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_NULLABLE, true);
176
        }
177
178
        if ($column->hasDefault()) {
179
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_DEFAULT, $column->getDefault());
180
        }
181
182
        if ($column->isCastedDefault()) {
183
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_CAST_DEFAULT, true);
184
        }
185
186
        return $field;
187
    }
188
189
    /**
190
     * Resolve class or role name relative to the current class.
191
     *
192
     * @param string           $name
193
     * @param \ReflectionClass $class
194
     * @return string
195
     */
196
    public function resolveName(?string $name, \ReflectionClass $class): ?string
197
    {
198
        if (is_null($name) || class_exists($name, true)) {
199
            return $name;
200
        }
201
202
        $resolved = sprintf(
203
            "%s\\%s",
204
            $class->getNamespaceName(),
205
            ltrim(str_replace('/', '\\', $name), '\\')
206
        );
207
208
        if (class_exists($resolved, true)) {
209
            return $resolved;
210
        }
211
212
        return $name;
213
    }
214
}