Passed
Push — master ( 1511a6...137b49 )
by Anton
03:21
created

GenerateRelations   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 73
dl 0
loc 154
rs 10
c 0
b 0
f 0
wmc 19

6 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 11 3
A initRelation() 0 7 2
A inverse() 0 25 6
A register() 0 22 3
A getDefaultRelations() 0 11 1
A __construct() 0 14 4
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
        $relations = $relations ?? self::getDefaultRelations();
60
        $this->options = $optionSchema ?? new OptionSchema(self::OPTION_MAP);
61
62
        foreach ($relations as $id => $relation) {
63
            if (!$relation instanceof RelationInterface) {
64
                throw new \InvalidArgumentException(sprintf(
65
                    "Invalid relation type, RelationInterface excepted, `%s` given",
66
                    is_object($relation) ? get_class($relation) : gettype($relation)
67
                ));
68
            }
69
70
            $this->relations[$id] = $relation;
71
        }
72
    }
73
74
    /**
75
     * @param Registry $registry
76
     * @return Registry
77
     */
78
    public function run(Registry $registry): Registry
79
    {
80
        foreach ($registry as $entity) {
81
            $this->register($registry, $entity);
82
        }
83
84
        foreach ($registry as $entity) {
85
            $this->inverse($registry, $entity);
86
        }
87
88
        return $registry;
89
    }
90
91
    /**
92
     * @param Registry $registry
93
     * @param Entity   $entity
94
     */
95
    protected function register(Registry $registry, Entity $entity)
96
    {
97
        foreach ($entity->getRelations() as $name => $r) {
98
            $schema = $this->initRelation($r->getType())->withContext(
99
                $name,
100
                $entity->getRole(),
101
                $r->getTarget(),
102
                $this->options->withOptions($r->getOptions())
103
            );
104
105
            // compute relation values (field names, related entities and etc)
106
            try {
107
                $schema->compute($registry);
108
            } catch (RelationException $e) {
109
                throw new SchemaException(
110
                    "Unable to compute relation `{$entity->getRole()}`.`{$name}`",
111
                    $e->getCode(),
112
                    $e
113
                );
114
            }
115
116
            $registry->registerRelation($entity, $name, $schema);
117
        }
118
    }
119
120
    /**
121
     * @param Registry $registry
122
     * @param Entity   $entity
123
     */
124
    protected function inverse(Registry $registry, Entity $entity)
125
    {
126
        foreach ($entity->getRelations() as $name => $r) {
127
            if (!$r->isInversed()) {
128
                continue;
129
            }
130
131
            $schema = $registry->getRelation($entity, $name);
132
            if (!$schema instanceof InversableInterface) {
133
                throw new SchemaException("Unable to inverse relation of type " . get_class($schema));
134
            }
135
136
            foreach ($schema->inverseTargets($registry) as $target) {
137
                try {
138
                    $inversed = $schema->inverseRelation(
139
                        $this->initRelation($r->getInverseType()),
140
                        $r->getInverseName()
141
                    );
142
143
                    $registry->registerRelation($target, $r->getInverseName(), $inversed);
144
                } catch (RelationException $e) {
145
                    throw new SchemaException(
146
                        "Unable to inverse relation `{$entity->getRole()}`.`{$name}`",
147
                        $e->getCode(),
148
                        $e
149
                    );
150
                }
151
            }
152
        }
153
    }
154
155
    /**
156
     * @param string $type
157
     * @return RelationInterface
158
     */
159
    protected function initRelation(string $type): RelationInterface
160
    {
161
        if (!isset($this->relations[$type])) {
162
            throw new RegistryException("Undefined relation type `{$type}`");
163
        }
164
165
        return $this->relations[$type];
166
    }
167
168
    /**
169
     * @return array
170
     */
171
    protected static function getDefaultRelations(): array
172
    {
173
        return [
174
            'belongsTo'        => new Definition\BelongsTo(),
175
            'hasOne'           => new Definition\HasOne(),
176
            'hasMany'          => new Definition\HasMany(),
177
            'refersTo'         => new Definition\RefersTo(),
178
            'manyToMany'       => new Definition\ManyToMany(),
179
            'belongsToMorphed' => new Definition\Morphed\BelongsToMorphed(),
180
            'morphedHasOne'    => new Definition\Morphed\MorphedHasOne(),
181
            'morphedHasMany'   => new Definition\Morphed\MorphedHasMany(),
182
        ];
183
    }
184
}