Passed
Push — master ( 011363...d360bf )
by Anton
01:35
created

GenerateRelations::defaultGenerator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 1
dl 0
loc 14
rs 9.9332
c 0
b 0
f 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\Schema\Generator;
10
11
use Cycle\ORM\Relation;
12
use Cycle\Schema\Definition\Entity;
13
use Cycle\Schema\Exception\RegistryException;
14
use Cycle\Schema\Exception\RelationException;
15
use Cycle\Schema\Exception\SchemaException;
16
use Cycle\Schema\GeneratorInterface;
17
use Cycle\Schema\InversableInterface;
18
use Cycle\Schema\Registry;
19
use Cycle\Schema\Relation as Definition;
20
use Cycle\Schema\Relation\OptionSchema;
21
use Cycle\Schema\Relation\RelationSchema;
22
use Cycle\Schema\RelationInterface;
23
24
25
/**
26
 * Generate relations based on their schematic definitions.
27
 */
28
final class GenerateRelations implements GeneratorInterface
29
{
30
    // aliases between option names and their internal IDs
31
    public const OPTION_MAP = [
32
        'nullable'       => Relation::NULLABLE,
33
        'innerKey'       => Relation::INNER_KEY,
34
        'outerKey'       => Relation::OUTER_KEY,
35
        'morphKey'       => Relation::MORPH_KEY,
36
        'though'         => Relation::THOUGH_ENTITY,
37
        'thoughInnerKey' => Relation::THOUGH_INNER_KEY,
38
        'thoughOuterKey' => Relation::THOUGH_OUTER_KEY,
39
        'thoughWhere'    => Relation::THOUGH_WHERE,
40
        'where'          => Relation::WHERE,
41
        'fkCreate'       => RelationSchema::FK_CREATE,
42
        'fkAction'       => RelationSchema::FK_ACTION,
43
        'indexCreate'    => RelationSchema::INDEX_CREATE,
44
        'morphKeyLength' => RelationSchema::MORPH_KEY_LENGTH
45
    ];
46
47
    /** @var OptionSchema */
48
    private $options;
49
50
    /** @var RelationInterface[] */
51
    private $relations = [];
52
53
    /**
54
     * @param array             $relations
55
     * @param OptionSchema|null $optionSchema
56
     */
57
    public function __construct(array $relations, OptionSchema $optionSchema = null)
58
    {
59
        $this->options = $optionSchema ?? new OptionSchema(self::OPTION_MAP);
60
61
        foreach ($relations as $id => $relation) {
62
            if (!$relation instanceof RelationInterface) {
63
                throw new \InvalidArgumentException(sprintf(
64
                    "Invalid relation type, RelationInterface excepted, `%s` given",
65
                    is_object($relation) ? get_class($relation) : gettype($relation)
66
                ));
67
            }
68
69
            $this->relations[$id] = $relation;
70
        }
71
    }
72
73
    /**
74
     * @param Registry $registry
75
     * @return Registry
76
     */
77
    public function run(Registry $registry): Registry
78
    {
79
        foreach ($registry as $entity) {
80
            $this->register($registry, $entity);
81
        }
82
83
        foreach ($registry as $entity) {
84
            $this->inverse($registry, $entity);
85
        }
86
87
        return $registry;
88
    }
89
90
    /**
91
     * @param Registry $registry
92
     * @param Entity   $entity
93
     */
94
    protected function register(Registry $registry, Entity $entity)
95
    {
96
        foreach ($entity->getRelations() as $name => $r) {
97
            $schema = $this->initRelation($r->getType())->withContext(
98
                $name,
99
                $entity->getRole(),
100
                $r->getTarget(),
101
                $this->options->withOptions($r->getOptions())
102
            );
103
104
            // compute relation values (field names, related entities and etc)
105
            try {
106
                $schema->compute($registry);
107
            } catch (RelationException $e) {
108
                throw new SchemaException(
109
                    "Unable to compute relation `{$entity->getRole()}`.`{$name}`",
110
                    $e->getCode(),
111
                    $e
112
                );
113
            }
114
115
            $registry->registerRelation($entity, $name, $schema);
116
        }
117
    }
118
119
    /**
120
     * @param Registry $registry
121
     * @param Entity   $entity
122
     */
123
    protected function inverse(Registry $registry, Entity $entity)
124
    {
125
        foreach ($entity->getRelations() as $name => $r) {
126
            if (!$r->isInversed()) {
127
                continue;
128
            }
129
130
            $schema = $registry->getRelation($entity, $name);
131
            if (!$schema instanceof InversableInterface) {
132
                throw new SchemaException("Unable to inverse relation of type " . get_class($schema));
133
            }
134
135
            foreach ($schema->inverseTargets($registry) as $target) {
136
                try {
137
                    $inversed = $schema->inverseRelation(
138
                        $this->initRelation($r->getInverseType()),
139
                        $r->getInverseName()
140
                    );
141
142
                    $registry->registerRelation($target, $r->getInverseName(), $inversed);
143
                } catch (RelationException $e) {
144
                    throw new SchemaException(
145
                        "Unable to inverse relation `{$entity->getRole()}`.`{$name}`",
146
                        $e->getCode(),
147
                        $e
148
                    );
149
                }
150
            }
151
        }
152
    }
153
154
    /**
155
     * @param string $type
156
     * @return RelationInterface
157
     */
158
    protected function initRelation(string $type): RelationInterface
159
    {
160
        if (!isset($this->relations[$type])) {
161
            throw new RegistryException("Undefined relation type `{$type}`");
162
        }
163
164
        return $this->relations[$type];
165
    }
166
167
    /**
168
     * @param OptionSchema|null $optionSchema
169
     * @return GenerateRelations
170
     */
171
    public static function defaultGenerator(OptionSchema $optionSchema = null): GenerateRelations
172
    {
173
        return new self(
174
            [
175
                'belongsTo'        => new Definition\BelongsTo(),
176
                'hasOne'           => new Definition\HasOne(),
177
                'hasMany'          => new Definition\HasMany(),
178
                'refersTo'         => new Definition\RefersTo(),
179
                'manyToMany'       => new Definition\ManyToMany(),
180
                'belongsToMorphed' => new Definition\Morphed\BelongsToMorphed(),
181
                'morphedHasOne'    => new Definition\Morphed\MorphedHasOne(),
182
                'morphedHasMany'   => new Definition\Morphed\MorphedHasMany(),
183
            ],
184
            $optionSchema
185
        );
186
    }
187
}