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

Generator::defaultParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 19
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 as RelationAnnotation;
14
use Cycle\Annotated\Annotation\Table;
15
use Cycle\Annotated\Exception\AnnotationException;
16
use Cycle\Schema\Definition\Entity as EntitySchema;
17
use Cycle\Schema\Definition\Field;
18
use Cycle\Schema\Definition\Relation;
19
use Cycle\Schema\Generator\SyncTables;
20
use Doctrine\Common\Inflector\Inflector;
21
use Spiral\Annotations\Parser;
22
23
final class Generator
24
{
25
    /** @var Parser */
26
    private $parser;
27
28
    /**
29
     * @param Parser $parser
30
     */
31
    public function __construct(Parser $parser)
32
    {
33
        $this->parser = $parser;
34
    }
35
36
    /**
37
     * @param Entity           $ann
38
     * @param \ReflectionClass $class
39
     * @return EntitySchema
40
     */
41
    public function initEntity(Entity $ann, \ReflectionClass $class): EntitySchema
42
    {
43
        $e = new EntitySchema();
44
        $e->setClass($class->getName());
45
46
        $e->setRole($ann->getRole() ?? Inflector::camelize($class->getShortName()));
47
48
        // representing classes
49
        $e->setMapper($this->resolveName($ann->getMapper(), $class));
50
        $e->setRepository($this->resolveName($ann->getRepository(), $class));
51
        $e->setSource($this->resolveName($ann->getSource(), $class));
52
        $e->setConstrain($this->resolveName($ann->getConstrain(), $class));
53
54
        if ($ann->isReadonlySchema()) {
55
            $e->getOptions()->set(SyncTables::READONLY_SCHEMA, true);
56
        }
57
58
        return $e;
59
    }
60
61
    /**
62
     * @param EntitySchema     $entity
63
     * @param \ReflectionClass $class
64
     */
65
    public function initFields(EntitySchema $entity, \ReflectionClass $class)
66
    {
67
        foreach ($class->getProperties() as $property) {
68
            if ($property->getDocComment() === false) {
69
                continue;
70
            }
71
72
            $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

72
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $property->getDocComment());
Loading history...
73
            if (!isset($ann[Column::NAME])) {
74
                continue;
75
            }
76
77
            $entity->getFields()->set(
78
                $property->getName(),
79
                $this->initField(
80
                    $property->getName(),
81
                    $ann[Column::NAME],
82
                    $class
83
                )
84
            );
85
        }
86
    }
87
88
    /**
89
     * @param EntitySchema     $entity
90
     * @param \ReflectionClass $class
91
     */
92
    public function initRelations(EntitySchema $entity, \ReflectionClass $class)
93
    {
94
        foreach ($class->getProperties() as $property) {
95
            if ($property->getDocComment() === false) {
96
                continue;
97
            }
98
99
            $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

99
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $property->getDocComment());
Loading history...
100
101
            foreach ($ann as $ra) {
102
                if (!$ra instanceof RelationAnnotation\RelationInterface) {
103
                    continue;
104
                }
105
106
                if ($ra->getTarget() === null) {
107
                    throw new AnnotationException(
108
                        "Relation target definition is required on `{$entity->getClass()}`"
109
                    );
110
                }
111
112
                $relation = new Relation();
113
                $relation->setTarget($this->resolveName($ra->getTarget(), $class));
114
                $relation->setType($ra->getName());
115
116
                if ($ra->isInversed()) {
117
                    $relation->setInverse($ra->getInverseName(), $ra->getInverseType());
118
                }
119
120
                foreach ($ra->getOptions() as $option => $value) {
121
                    if ($option === "though") {
122
                        $value = $this->resolveName($value, $class);
123
                    }
124
125
                    $relation->getOptions()->set($option, $value);
126
                }
127
128
                // need relation definition
129
                $entity->getRelations()->set($property->getName(), $relation);
130
            }
131
        }
132
    }
133
134
    /**
135
     * @param EntitySchema     $entity
136
     * @param Column[]         $columns
137
     * @param \ReflectionClass $class
138
     */
139
    public function initColumns(EntitySchema $entity, array $columns, \ReflectionClass $class)
140
    {
141
        foreach ($columns as $name => $column) {
142
            if ($column->getColumn() === null && is_numeric($name)) {
143
                throw new AnnotationException(
144
                    "Column name definition is required on `{$entity->getClass()}`"
145
                );
146
            }
147
148
            if ($column->getType() === null) {
149
                throw new AnnotationException(
150
                    "Column type definition is required on `{$entity->getClass()}`"
151
                );
152
            }
153
154
            $entity->getFields()->set(
155
                $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

155
                /** @scrutinizer ignore-type */ $name ?? $column->getColumn(),
Loading history...
156
                $this->initField($column->getColumn() ?? $name, $column, $class)
157
            );
158
        }
159
    }
160
161
    /**
162
     * @param string           $name
163
     * @param Column           $column
164
     * @param \ReflectionClass $class
165
     * @return Field
166
     */
167
    public function initField(string $name, Column $column, \ReflectionClass $class): Field
168
    {
169
        if ($column->getType() === null) {
170
            throw new AnnotationException(
171
                "Column type definition is required on `{$class->getName()}`.`{$name}`"
172
            );
173
        }
174
175
        $field = new Field();
176
177
        $field->setType($column->getType());
178
        $field->setColumn($column->getColumn() ?? Inflector::tableize($name));
179
        $field->setPrimary($column->isPrimary());
180
        $field->setTypecast($column->getTypecast());
181
182
        if ($column->isNullable()) {
183
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_NULLABLE, true);
184
        }
185
186
        if ($column->hasDefault()) {
187
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_DEFAULT, $column->getDefault());
188
        }
189
190
        if ($column->isCastedDefault()) {
191
            $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_CAST_DEFAULT, true);
192
        }
193
194
        return $field;
195
    }
196
197
    /**
198
     * Resolve class or role name relative to the current class.
199
     *
200
     * @param string           $name
201
     * @param \ReflectionClass $class
202
     * @return string
203
     */
204
    public function resolveName(?string $name, \ReflectionClass $class): ?string
205
    {
206
        if (is_null($name) || class_exists($name, true) || interface_exists($name, true)) {
207
            return $name;
208
        }
209
210
        $resolved = sprintf(
211
            "%s\\%s",
212
            $class->getNamespaceName(),
213
            ltrim(str_replace('/', '\\', $name), '\\')
214
        );
215
216
        if (class_exists($resolved, true) || interface_exists($resolved, true)) {
217
            return $resolved;
218
        }
219
220
        return $name;
221
    }
222
223
    /**
224
     * @return Parser
225
     */
226
    public static function defaultParser(): Parser
227
    {
228
        $p = new Parser();
229
        $p->register(new Entity());
230
        $p->register(new Column());
231
        $p->register(new Table());
232
        $p->register(new Table\Index());
233
234
        // embedded relations
235
        $p->register(new RelationAnnotation\BelongsTo());
236
        $p->register(new RelationAnnotation\HasOne());
237
        $p->register(new RelationAnnotation\HasMany());
238
        $p->register(new RelationAnnotation\RefersTo());
239
        $p->register(new RelationAnnotation\ManyToMany());
240
        $p->register(new RelationAnnotation\Morphed\BelongsToMorphed());
241
        $p->register(new RelationAnnotation\Morphed\MorphedHasOne());
242
        $p->register(new RelationAnnotation\Morphed\MorphedHasMany());
243
244
        return $p;
245
    }
246
}