Completed
Branch feature/pre-split (4ff102)
by Anton
03:27
created

RelationMap::queueRelations()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 27
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 5
nop 1
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ORM\Entities;
8
9
use Spiral\ORM\Commands\TransactionalCommand;
10
use Spiral\ORM\ContextualCommandInterface;
11
use Spiral\ORM\Exceptions\RelationException;
12
use Spiral\ORM\ORMInterface;
13
use Spiral\ORM\RecordInterface;
14
use Spiral\ORM\RelationInterface;
15
16
/**
17
 * Represent set of entity relations.
18
 */
19
class RelationMap
20
{
21
    /**
22
     * @var array|RelationInterface[]
23
     */
24
    private $relations = [];
25
26
    /**
27
     * Parent class name.
28
     *
29
     * @var string
30
     */
31
    private $class;
32
33
    /**
34
     * Parent model.
35
     *
36
     * @var RecordInterface
37
     */
38
    private $parent;
39
40
    /**
41
     * Relations schema.
42
     *
43
     * @var array
44
     */
45
    private $schema = [];
46
47
    /**
48
     * Associates ORM manager.
49
     *
50
     * @var ORMInterface
51
     */
52
    protected $orm;
53
54
    /**
55
     * @param RecordInterface $record
56
     * @param ORMInterface    $orm
57
     */
58
    public function __construct(RecordInterface $record, ORMInterface $orm)
59
    {
60
        $this->class = get_class($record);
61
        $this->parent = $record;
62
        $this->schema = $orm->define($this->class, ORMInterface::R_RELATIONS);
0 ignored issues
show
Documentation Bug introduced by
It seems like $orm->define($this->clas...Interface::R_RELATIONS) of type * is incompatible with the declared type array of property $schema.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
        $this->orm = $orm;
64
    }
65
66
    /**
67
     * Extract relations data from given entity fields.
68
     *
69
     * @param array $data
70
     */
71
    public function extractRelations(array &$data)
72
    {
73
        //Fetch all relations
74
        $relations = array_intersect_key($data, $this->schema);
75
76
        foreach ($relations as $name => $relation) {
77
            $this->relations[$name] = $relation;
78
            unset($data[$name]);
79
        }
80
    }
81
82
    /**
83
     * Generate command tree with or without relation to parent command in order to specify update
84
     * or insert sequence. Commands might define dependencies between each other in order to extend
85
     * FK values.
86
     *
87
     * @param ContextualCommandInterface $parent
88
     *
89
     * @return ContextualCommandInterface
90
     */
91
    public function queueRelations(ContextualCommandInterface $parent): ContextualCommandInterface
92
    {
93
        if (empty($this->relations)) {
94
            //No relations exists, nothing to do
95
            return $parent;
96
        }
97
98
        //We have to execute multiple commands at once
99
        $transaction = new TransactionalCommand();
100
101
        //Leading relations
102
        foreach ($this->leadingRelations() as $relation) {
103
            //Generating commands needed to save given relation prior to parent command
104
            $transaction->addCommand($relation->queueCommands($parent));
105
        }
106
107
        //Parent model save operations
108
        $transaction->addCommand($parent, true);
109
110
        //Depended relations
111
        foreach ($this->dependedRelations() as $relation) {
112
            //Generating commands needed to save relations after parent command being executed
113
            $transaction->addCommand($relation->queueCommands($parent));
114
        }
115
116
        return $transaction;
117
    }
118
119
    /**
120
     * Check if parent entity has associated relation.
121
     *
122
     * @param string $relation
123
     *
124
     * @return bool
125
     */
126
    public function has(string $relation): bool
127
    {
128
        return isset($this->schema[$relation]);
129
    }
130
131
    /**
132
     * Check if relation has any associated data with it (attention, non loaded relation will be
133
     * automatically pre-loaded).
134
     *
135
     * @param string $relation
136
     *
137
     * @return bool
138
     */
139
    public function hasRelated(string $relation): bool
140
    {
141
        return $this->get($relation)->hasRelated();
0 ignored issues
show
Bug introduced by
The method hasRelated() does not seem to exist on object<Spiral\ORM\RelationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
142
    }
143
144
    /**
145
     * Data data which is being associated with relation, relation is allowed to return itself if
146
     * needed.
147
     *
148
     * @param string $relation
149
     *
150
     * @return RelationInterface|RecordInterface|mixed
151
     *
152
     * @throws RelationException
153
     */
154
    public function getRelated(string $relation)
155
    {
156
        return $this->get($relation)->getRelated();
157
    }
158
159
    /**
160
     * Associated relation with new value (must be compatible with relation format).
161
     *
162
     * @param string $relation
163
     * @param mixed  $value
164
     *
165
     * @throws RelationException
166
     */
167
    public function setRelated(string $relation, $value)
168
    {
169
        $this->get($relation)->setRelated($value);
170
    }
171
172
    /**
173
     * Get associated relation instance.
174
     *
175
     * @param string $relation
176
     *
177
     * @return RelationInterface
178
     */
179
    public function get(string $relation): RelationInterface
180
    {
181
        if (isset($this->relations[$relation]) && $this->relations[$relation] instanceof RelationInterface) {
182
            return $this->relations[$relation];
183
        }
184
185
        $instance = $this->orm->makeRelation($this->class, $relation);
186
        if (array_key_exists($relation, $this->relations)) {
187
            //Indicating that relation is loaded
188
            $instance = $instance->withContext($this->parent, true, $this->relations[$relation]);
189
        } else {
190
            //Not loaded relation
191
            $instance = $instance->withContext($this->parent, false);
192
        }
193
194
        return $this->relations[$relation] = $instance;
195
    }
196
197
    /**
198
     * Information about loaded relations.
199
     *
200
     * @return array
201
     */
202
    public function __debugInfo()
203
    {
204
        $relations = [];
205
206
        foreach ($this->schema as $relation => $schema) {
207
            $accessor = $this->get($relation);
208
209
            //Only base class name
210
            $type = (new \ReflectionClass($accessor))->getShortName();
211
212
            $class = (new \ReflectionClass($accessor->getClass()))->getShortName();
213
214
            //[+] for loaded, [~] for lazy loaded
215
            $relations[$relation] = $type . '(' . $class . ') [' . ($accessor->isLoaded() ? '+]' : '~]');
216
        }
217
218
        return $relations;
219
    }
220
221
    /**
222
     * list of relations which lead data of parent record (BELONGS_TO).
223
     *
224
     * Example:
225
     *
226
     * $post = new Post();
227
     * $post->user = new User();
228
     *
229
     * @return RelationInterface[]|\Generator
230
     */
231
    protected function leadingRelations()
232
    {
233
        foreach ($this->relations as $relation) {
234
            if ($relation instanceof RelationInterface && $relation->isLeading()) {
235
                yield $relation;
236
            }
237
        }
238
    }
239
240
    /**
241
     * list of loaded relations which depend on parent record (HAS_MANY, MANY_TO_MANY and etc).
242
     *
243
     * Example:
244
     *
245
     * $post = new Post();
246
     * $post->comments->add(new Comment());
247
     *
248
     * @return RelationInterface[]|\Generator
249
     */
250
    protected function dependedRelations()
251
    {
252
        foreach ($this->relations as $relation) {
253
            if ($relation instanceof RelationInterface && !$relation->isLeading()) {
254
                yield $relation;
255
            }
256
        }
257
    }
258
}