RelationMap::get()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 4
nc 3
nop 1
1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\ORM\Entities;
9
10
use Spiral\ORM\Commands\TransactionalCommand;
11
use Spiral\ORM\ContextualCommandInterface;
12
use Spiral\ORM\Exceptions\RelationException;
13
use Spiral\ORM\ORMInterface;
14
use Spiral\ORM\RecordInterface;
15
use Spiral\ORM\RelationInterface;
16
17
/**
18
 * Represent set of entity relations.
19
 */
20
class RelationMap
21
{
22
    /**
23
     * @var array|RelationInterface[]
24
     */
25
    private $relations = [];
26
27
    /**
28
     * Parent class name.
29
     *
30
     * @var string
31
     */
32
    private $class;
33
34
    /**
35
     * Parent model.
36
     *
37
     * @var RecordInterface
38
     */
39
    private $parent;
40
41
    /**
42
     * Relations schema.
43
     *
44
     * @var array
45
     */
46
    private $schema = [];
47
48
    /**
49
     * Associates ORM manager.
50
     *
51
     * @var ORMInterface
52
     */
53
    protected $orm;
54
55
    /**
56
     * @param RecordInterface $record
57
     * @param ORMInterface    $orm
58
     */
59
    public function __construct(RecordInterface $record, ORMInterface $orm)
60
    {
61
        $this->class = get_class($record);
62
        $this->parent = $record;
63
        $this->schema = (array)$orm->define($this->class, ORMInterface::R_RELATIONS);
64
        $this->orm = $orm;
65
    }
66
67
    /**
68
     * Extract relations data from given entity fields.
69
     *
70
     * @param array $data
71
     */
72
    public function extractRelations(array &$data)
73
    {
74
        //Fetch all relations
75
        $relations = array_intersect_key($data, $this->schema);
76
77
        foreach ($relations as $name => $relation) {
78
            $this->relations[$name] = $relation;
79
            unset($data[$name]);
80
        }
81
    }
82
83
    /**
84
     * Generate command tree with or without relation to parent command in order to specify update
85
     * or insert sequence. Commands might define dependencies between each other in order to extend
86
     * FK values.
87
     *
88
     * @param ContextualCommandInterface $parent
89
     *
90
     * @return ContextualCommandInterface
91
     */
92
    public function queueRelations(ContextualCommandInterface $parent): ContextualCommandInterface
93
    {
94
        if (empty($this->relations)) {
95
            //No relations exists, nothing to do
96
            return $parent;
97
        }
98
99
        //We have to execute multiple commands at once
100
        $transaction = new TransactionalCommand();
101
102
        foreach ($this->leadingRelations() as $relation) {
103
            //Generating commands needed to save given relation prior to parent command
104
            if ($relation->isLoaded()) {
105
                $transaction->addCommand($relation->queueCommands($parent));
106
            }
107
        }
108
109
        //Parent model save operations (true state that this is leading/primary command)
110
        $transaction->addCommand($parent, true);
111
112
        foreach ($this->dependedRelations() as $relation) {
113
            //Generating commands needed to save relations after parent command being executed
114
            if ($relation->isLoaded()) {
115
                $transaction->addCommand($relation->queueCommands($parent));
116
            }
117
        }
118
119
        return $transaction;
120
    }
121
122
    /**
123
     * Check if parent entity has associated relation.
124
     *
125
     * @param string $relation
126
     *
127
     * @return bool
128
     */
129
    public function has(string $relation): bool
130
    {
131
        return isset($this->schema[$relation]);
132
    }
133
134
    /**
135
     * Check if relation has any associated data with it (attention, non loaded relation will be
136
     * automatically pre-loaded).
137
     *
138
     * @param string $relation
139
     *
140
     * @return bool
141
     */
142
    public function hasRelated(string $relation): bool
143
    {
144
        //Checking with only loaded records
145
        return $this->get($relation)->hasRelated();
146
    }
147
148
    /**
149
     * Data data which is being associated with relation, relation is allowed to return itself if
150
     * needed.
151
     *
152
     * @param string $relation
153
     *
154
     * @return RelationInterface|RecordInterface|mixed
155
     *
156
     * @throws RelationException
157
     */
158
    public function getRelated(string $relation)
159
    {
160
        return $this->get($relation)->getRelated();
161
    }
162
163
    /**
164
     * Associated relation with new value (must be compatible with relation format).
165
     *
166
     * @param string $relation
167
     * @param mixed  $value
168
     *
169
     * @throws RelationException
170
     */
171
    public function setRelated(string $relation, $value)
172
    {
173
        $this->get($relation)->setRelated($value);
174
    }
175
176
    /**
177
     * Get associated relation instance.
178
     *
179
     * @param string $relation
180
     *
181
     * @return RelationInterface
182
     */
183
    public function get(string $relation): RelationInterface
184
    {
185
        if (isset($this->relations[$relation]) && $this->relations[$relation] instanceof RelationInterface) {
186
            return $this->relations[$relation];
187
        }
188
189
        $instance = $this->orm->makeRelation($this->class, $relation);
190
        if (array_key_exists($relation, $this->relations)) {
191
            //Indicating that relation is loaded
192
            $instance = $instance->withContext($this->parent, true, $this->relations[$relation]);
193
        } else {
194
            //Not loaded relation
195
            $instance = $instance->withContext($this->parent, false);
196
        }
197
198
        return $this->relations[$relation] = $instance;
199
    }
200
201
    /**
202
     * Information about loaded relations.
203
     *
204
     * @return array
205
     */
206
    public function __debugInfo()
207
    {
208
        $relations = [];
209
210
        foreach ($this->schema as $relation => $schema) {
211
            $accessor = $this->get($relation);
212
213
            $type = (new \ReflectionClass($accessor))->getShortName();
214
            $class = (new \ReflectionClass($accessor->getClass()))->getShortName();
215
216
            //[+] for loaded, [~] for lazy loaded
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
217
            $relations[$relation] = $type . '(' . $class . ') [' . ($accessor->isLoaded() ? '+]' : '~]');
218
        }
219
220
        return $relations;
221
    }
222
223
    /**
224
     * list of relations which lead data of parent record (BELONGS_TO).
225
     *
226
     * Example:
227
     *
228
     * $post = new Post();
229
     * $post->user = new User();
230
     *
231
     * @return RelationInterface[]|\Generator
232
     */
233
    protected function leadingRelations()
234
    {
235
        foreach ($this->relations as $relation) {
236
            if ($relation instanceof RelationInterface && $relation->isLeading()) {
237
                yield $relation;
238
            }
239
        }
240
    }
241
242
    /**
243
     * list of loaded relations which depend on parent record (HAS_MANY, MANY_TO_MANY and etc).
244
     *
245
     * Example:
246
     *
247
     * $post = new Post();
248
     * $post->comments->add(new Comment());
249
     *
250
     * @return RelationInterface[]|\Generator
251
     */
252
    protected function dependedRelations()
253
    {
254
        foreach ($this->relations as $relation) {
255
            if ($relation instanceof RelationInterface && !$relation->isLeading()) {
256
                yield $relation;
257
            }
258
        }
259
    }
260
}