1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Cycle ORM Schema Builder. |
5
|
|
|
* |
6
|
|
|
* @license MIT |
7
|
|
|
* @author Anton Titov (Wolfy-J) |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
declare(strict_types=1); |
11
|
|
|
|
12
|
|
|
namespace Cycle\Schema; |
13
|
|
|
|
14
|
|
|
use Cycle\ORM\Mapper\Mapper; |
15
|
|
|
use Cycle\ORM\Schema; |
16
|
|
|
use Cycle\ORM\Select\Repository; |
17
|
|
|
use Cycle\ORM\Select\Source; |
18
|
|
|
use Cycle\Schema\Definition\Comparator\FieldComparator; |
19
|
|
|
use Cycle\Schema\Definition\Entity; |
20
|
|
|
use Cycle\Schema\Definition\Field; |
21
|
|
|
use Spiral\Database\Exception\CompilerException; |
22
|
|
|
|
23
|
|
|
final class Compiler |
24
|
|
|
{ |
25
|
|
|
/** @var array */ |
26
|
|
|
private $result = []; |
27
|
|
|
|
28
|
|
|
/** @var array */ |
29
|
|
|
private $defaults = [ |
30
|
|
|
Schema::MAPPER => Mapper::class, |
31
|
|
|
Schema::REPOSITORY => Repository::class, |
32
|
|
|
Schema::SOURCE => Source::class, |
33
|
|
|
Schema::CONSTRAIN => null, |
34
|
|
|
]; |
35
|
|
|
|
36
|
|
|
/** @var \Doctrine\Inflector\Inflector */ |
37
|
|
|
private $inflector; |
38
|
|
|
|
39
|
|
|
public function __construct() |
40
|
|
|
{ |
41
|
|
|
$this->inflector = (new \Doctrine\Inflector\Rules\English\InflectorFactory())->build(); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Compile the registry schema. |
46
|
|
|
* |
47
|
|
|
* @param Registry $registry |
48
|
|
|
* @param GeneratorInterface[] $generators |
49
|
|
|
* @param array $defaults |
50
|
|
|
* @return array |
51
|
|
|
* |
52
|
|
|
*/ |
53
|
|
|
public function compile(Registry $registry, array $generators = [], array $defaults = []): array |
54
|
|
|
{ |
55
|
|
|
$this->defaults = $defaults + $this->defaults; |
56
|
|
|
|
57
|
|
|
foreach ($generators as $generator) { |
58
|
|
|
if (!$generator instanceof GeneratorInterface) { |
59
|
|
|
throw new CompilerException(sprintf( |
60
|
|
|
'Invalid generator `%s`', |
61
|
|
|
is_object($generator) ? get_class($generator) : gettype($generator) |
62
|
|
|
)); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$registry = $generator->run($registry); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
foreach ($registry->getIterator() as $entity) { |
69
|
|
|
if ($this->getPrimary($entity) === null) { |
|
|
|
|
70
|
|
|
// incomplete entity, skip |
71
|
|
|
continue; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$this->compute($registry, $entity); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return $this->result; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Get compiled schema result. |
82
|
|
|
* |
83
|
|
|
* @return array |
84
|
|
|
*/ |
85
|
|
|
public function getSchema(): array |
86
|
|
|
{ |
87
|
|
|
return $this->result; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Compile entity and relation definitions into packed ORM schema. |
92
|
|
|
* |
93
|
|
|
* @param Registry $registry |
94
|
|
|
* @param Entity $entity |
95
|
|
|
*/ |
96
|
|
|
protected function compute(Registry $registry, Entity $entity): void |
97
|
|
|
{ |
98
|
|
|
$schema = [ |
99
|
|
|
Schema::ENTITY => $entity->getClass(), |
100
|
|
|
Schema::SOURCE => $entity->getSource() ?? $this->defaults[Schema::SOURCE], |
101
|
|
|
Schema::MAPPER => $entity->getMapper() ?? $this->defaults[Schema::MAPPER], |
102
|
|
|
Schema::REPOSITORY => $entity->getRepository() ?? $this->defaults[Schema::REPOSITORY], |
103
|
|
|
Schema::CONSTRAIN => $entity->getConstrain() ?? $this->defaults[Schema::CONSTRAIN], |
104
|
|
|
Schema::SCHEMA => $entity->getSchema(), |
105
|
|
|
Schema::PRIMARY_KEY => $this->getPrimary($entity), |
|
|
|
|
106
|
|
|
Schema::COLUMNS => $this->renderColumns($entity), |
107
|
|
|
Schema::FIND_BY_KEYS => $this->renderReferences($entity), |
108
|
|
|
Schema::TYPECAST => $this->renderTypecast($entity), |
109
|
|
|
Schema::RELATIONS => $this->renderRelations($registry, $entity) |
110
|
|
|
]; |
111
|
|
|
|
112
|
|
|
if ($registry->hasTable($entity)) { |
113
|
|
|
$schema[Schema::DATABASE] = $registry->getDatabase($entity); |
114
|
|
|
$schema[Schema::TABLE] = $registry->getTable($entity); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
// table inheritance |
118
|
|
|
foreach ($registry->getChildren($entity) as $child) { |
119
|
|
|
$this->result[$child->getClass()] = [Schema::ROLE => $entity->getRole()]; |
120
|
|
|
$schema[Schema::CHILDREN][$this->childAlias($child)] = $child->getClass(); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
ksort($schema); |
124
|
|
|
$this->result[$entity->getRole()] = $schema; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @param Entity $entity |
129
|
|
|
* @return array |
130
|
|
|
*/ |
131
|
|
|
protected function renderColumns(Entity $entity): array |
132
|
|
|
{ |
133
|
|
|
// Check field duplicates |
134
|
|
|
/** @var Field[][] $fieldGroups */ |
135
|
|
|
$fieldGroups = []; |
136
|
|
|
// Collect and group fields by column name |
137
|
|
|
foreach ($entity->getFields() as $name => $field) { |
138
|
|
|
$fieldGroups[$field->getColumn()][$name] = $field; |
139
|
|
|
} |
140
|
|
|
foreach ($fieldGroups as $fieldName => $fields) { |
141
|
|
|
// We need duplicates only |
142
|
|
|
if (count($fields) === 1) { |
143
|
|
|
continue; |
144
|
|
|
} |
145
|
|
|
// Compare |
146
|
|
|
$comparator = new FieldComparator(); |
147
|
|
|
foreach ($fields as $name => $field) { |
148
|
|
|
$comparator->addField($name, $field); |
149
|
|
|
} |
150
|
|
|
try { |
151
|
|
|
$comparator->compare(); |
152
|
|
|
} catch (\Throwable $e) { |
153
|
|
|
throw new Exception\CompilerException( |
154
|
|
|
sprintf("Error compiling the `%s` role.\n\n%s", $entity->getRole(), $e->getMessage()), |
155
|
|
|
$e->getCode() |
156
|
|
|
); |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
$schema = []; |
161
|
|
|
foreach ($entity->getFields() as $name => $field) { |
162
|
|
|
$schema[$name] = $field->getColumn(); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return $schema; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @param Entity $entity |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
|
|
protected function renderTypecast(Entity $entity): array |
173
|
|
|
{ |
174
|
|
|
$schema = []; |
175
|
|
|
foreach ($entity->getFields() as $name => $field) { |
176
|
|
|
if ($field->hasTypecast()) { |
177
|
|
|
$schema[$name] = $field->getTypecast(); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
return $schema; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @param Entity $entity |
186
|
|
|
* @return array |
187
|
|
|
*/ |
188
|
|
|
protected function renderReferences(Entity $entity): array |
189
|
|
|
{ |
190
|
|
|
$schema = [$this->getPrimary($entity)]; |
|
|
|
|
191
|
|
|
|
192
|
|
|
foreach ($entity->getFields() as $name => $field) { |
193
|
|
|
if ($field->isReferenced()) { |
194
|
|
|
$schema[] = $name; |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return array_unique($schema); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @param Registry $registry |
203
|
|
|
* @param Entity $entity |
204
|
|
|
* @return array |
205
|
|
|
*/ |
206
|
|
|
protected function renderRelations(Registry $registry, Entity $entity): array |
207
|
|
|
{ |
208
|
|
|
$result = []; |
209
|
|
|
foreach ($registry->getRelations($entity) as $name => $relation) { |
210
|
|
|
$result[$name] = $relation->packSchema(); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $result; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* @param Entity $entity |
218
|
|
|
* @return string|null |
219
|
|
|
*/ |
220
|
|
|
protected function getPrimary(Entity $entity): ?string |
221
|
|
|
{ |
222
|
|
|
foreach ($entity->getFields() as $name => $field) { |
223
|
|
|
if ($field->isPrimary()) { |
224
|
|
|
return $name; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
return null; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Return the unique alias for the child entity. |
233
|
|
|
* |
234
|
|
|
* @param Entity $entity |
235
|
|
|
* @return string |
236
|
|
|
*/ |
237
|
|
|
protected function childAlias(Entity $entity): string |
238
|
|
|
{ |
239
|
|
|
$r = new \ReflectionClass($entity->getClass()); |
240
|
|
|
|
241
|
|
|
return $this->inflector->classify($r->getShortName()); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.