Passed
Pull Request — master (#20)
by Aleksei
01:42
created

Compiler::renderColumns()   B

Complexity

Conditions 7
Paths 20

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 19
c 2
b 0
f 0
nc 20
nop 1
dl 0
loc 35
rs 8.8333
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) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPrimary($entity) targeting Cycle\Schema\Compiler::getPrimary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
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),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPrimary($entity) targeting Cycle\Schema\Compiler::getPrimary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
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)];
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPrimary($entity) targeting Cycle\Schema\Compiler::getPrimary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
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