Completed
Branch feature/pre-split (24875e)
by Anton
03:28
created

RelationBuilder::inverseRelations()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 7
nop 1
dl 0
loc 32
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
8
namespace Spiral\ORM\Schemas;
9
10
use Spiral\Core\FactoryInterface;
11
use Spiral\ORM\Configs\RelationsConfig;
12
use Spiral\ORM\Exceptions\DefinitionException;
13
use Spiral\ORM\Schemas\Definitions\RelationDefinition;
14
15
/**
16
 * Subsection of SchemaBuilder used to configure tables and columns defined by model to model
17
 * relations.
18
 */
19
class RelationBuilder
20
{
21
    /**
22
     * @invisible
23
     * @var RelationsConfig
24
     */
25
    protected $config;
26
27
    /**
28
     * @invisible
29
     * @var FactoryInterface
30
     */
31
    protected $factory;
32
33
    /**
34
     * Set of relation definitions.
35
     *
36
     * @var RelationInterface[]
37
     */
38
    private $relations = [];
39
40
    /**
41
     * @param RelationsConfig  $config
42
     * @param FactoryInterface $factory
43
     */
44
    public function __construct(RelationsConfig $config, FactoryInterface $factory)
45
    {
46
        $this->config = $config;
47
        $this->factory = $factory;
48
    }
49
50
    /**
51
     * Registering new relation definition. At this moment function would not check if relation is
52
     * unique and will redeclare it.
53
     *
54
     * @param RelationDefinition $definition Relation options (definition).
55
     *
56
     * @throws DefinitionException
57
     */
58
    public function registerRelation(RelationDefinition $definition)
59
    {
60
        if (!$this->config->hasRelation($definition->getType())) {
61
            throw new DefinitionException(sprintf(
62
                "Undefined relation type '%s' in '%s'.'%s'",
63
                $definition->getType(),
64
                $definition->sourceContext()->getClass(),
65
                $definition->getName()
66
            ));
67
        }
68
69
        $class = $this->config->relationClass(
70
            $definition->getType(),
71
            RelationsConfig::SCHEMA_CLASS
72
        );
73
74
        //Creating relation schema
75
        $relation = $this->factory->make($class, compact('definition'));
76
77
        $this->relations[] = $relation;
78
    }
79
80
    /**
81
     * Create inverse relations where needed.
82
     *
83
     * @param SchemaBuilder $builder
84
     *
85
     * @throws DefinitionException
86
     */
87
    public function inverseRelations(SchemaBuilder $builder)
88
    {
89
        /**
90
         * Inverse process is relation specific.
91
         */
92
        foreach ($this->relations as $relation) {
93
            $definition = $relation->getDefinition();
94
95
            if ($definition->needInversion()) {
96
                if (!$relation instanceof InversableRelationInterface) {
97
                    throw new DefinitionException(sprintf(
98
                        "Unable to inverse relation '%s'.'%s', relation schema '%s' is non inversable",
99
                        $definition->sourceContext()->getClass(),
100
                        $definition->getName(),
101
                        get_class($relation)
102
                    ));
103
                }
104
105
                $inversed = $relation->inverseDefinition($builder, $definition->getInverse());
106
107
                if ($inversed instanceof RelationDefinition) {
108
                    //Inversed into singular relation
109
                    $this->registerRelation($inversed);
110
                } elseif (is_array($inversed)) {
111
                    //Inversed into multiple back relation (see morphed relations)
112
                    foreach ($inversed as $definition) {
113
                        $this->registerRelation($definition);
114
                    }
115
                }
116
            }
117
        }
118
    }
119
120
    /**
121
     * All declared relations.
122
     *
123
     * @return RelationInterface[]
124
     */
125
    public function getRelations(): array
126
    {
127
        return $this->relations;
128
    }
129
130
    /**
131
     * Declare set of tables for each relation. Method must return Generator of AbstractTable
132
     * sequentially (attention, non sequential processing will cause collision issues between
133
     * tables).
134
     *
135
     * @param SchemaBuilder $builder
136
     *
137
     * @return \Generator
138
     */
139
    public function declareTables(SchemaBuilder $builder): \Generator
140
    {
141
        foreach ($this->relations as $relation) {
142
            foreach ($relation->declareTables($builder) as $table) {
143
                yield $table;
144
            }
145
        }
146
    }
147
148
    /**
149
     * Pack relation schemas for specific model class in order to be saved in memory.
150
     *
151
     * @param string        $class
152
     * @param SchemaBuilder $builder
153
     *
154
     * @return array
155
     */
156
    public function packRelations(string $class, SchemaBuilder $builder): array
157
    {
158
        $result = [];
159
        foreach ($this->relations as $relation) {
160
            $definition = $relation->getDefinition();
161
162
            if ($definition->sourceContext()->getClass() == $class) {
163
                //Packing relation, relation schema are given with associated table
164
                $result[$definition->getName()] = $relation->packRelation($builder);
165
            }
166
        }
167
168
        return $result;
169
    }
170
}