Passed
Push — master ( 0af3e6...98851a )
by Anton
02:52
created

RelationMap::sameReference()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 3
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 7
rs 10
1
<?php
2
/**
3
 * Cycle DataMapper ORM
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
declare(strict_types=1);
9
10
namespace Cycle\ORM;
11
12
use Cycle\ORM\Command\Branch\ContextSequence;
13
use Cycle\ORM\Command\CommandInterface;
14
use Cycle\ORM\Command\ContextCarrierInterface as CC;
15
use Cycle\ORM\Heap\Node;
16
use Cycle\ORM\Promise\ReferenceInterface;
17
use Cycle\ORM\Relation\DependencyInterface;
18
use Cycle\ORM\Relation\RelationInterface;
19
20
/**
21
 * Manages the position of node in the relation graph and provide access to neighbours.
22
 */
23
final class RelationMap
24
{
25
    /** @var ORMInterface @internal */
26
    private $orm;
27
28
    /** @var RelationInterface[] */
29
    private $relations = [];
30
31
    /** @var DependencyInterface[] */
32
    private $dependencies = [];
33
34
    /**
35
     * @param ORMInterface $orm
36
     * @param array        $relations
37
     */
38
    public function __construct(ORMInterface $orm, array $relations)
39
    {
40
        $this->orm = $orm;
41
        $this->relations = $relations;
42
43
        foreach ($this->relations as $name => $relation) {
44
            if ($relation instanceof DependencyInterface) {
45
                $this->dependencies[$name] = $relation;
46
            }
47
        }
48
    }
49
50
    /**
51
     * Init relation data in entity data and entity state.
52
     *
53
     * @param Node  $node
54
     * @param array $data
55
     * @return array
56
     */
57
    public function init(Node $node, array $data): array
58
    {
59
        foreach ($this->relations as $name => $relation) {
60
            if (!array_key_exists($name, $data)) {
61
                if ($node->hasRelation($name)) {
62
                    continue;
63
                }
64
65
                list($data[$name], $orig) = $relation->initPromise($node);
66
                $node->setRelation($name, $orig);
67
                continue;
68
            }
69
70
            $item = $data[$name];
71
            if (is_object($item) || $item === null) {
72
                // cyclic initialization
73
                $node->setRelation($name, $item);
74
                continue;
75
            }
76
77
            // init relation for the entity and for state and the same time
78
            list($data[$name], $orig) = $relation->init($node, $item);
79
            $node->setRelation($name, $orig);
80
        }
81
82
        return $data;
83
    }
84
85
    /**
86
     * Init non initialized reference relations with real entities.
87
     *
88
     * @param Node  $node
89
     * @param array $data
90
     * @param array $current
91
     * @return array
92
     */
93
    public function merge(Node $node, array $data, array $current): array
94
    {
95
        $merged = [];
96
        foreach ($this->relations as $name => $relation) {
97
            if (!array_key_exists($name, $data)) {
98
                continue;
99
            }
100
101
            // automatically resolve entity pointers (cyclic relations)
102
            if ($this->sameReference($current[$name] ?? null, $node->getRelation($name))) {
103
                $item = $data[$name];
104
                if (is_object($item) || $item === null) {
105
                    $merged[$name] = $item;
106
                    $node->setRelation($name, $item);
107
                    continue;
108
                }
109
110
                // init relation for the entity and for state and the same time
111
                list($merged[$name], $orig) = $relation->init($node, $item);
112
                $node->setRelation($name, $orig);
113
            }
114
        }
115
116
        return $merged;
117
    }
118
119
    /**
120
     * Queue entity relations.
121
     *
122
     * @param CC     $parentStore
123
     * @param object $parentEntity
124
     * @param Node   $parentNode
125
     * @param array  $parentData
126
     * @return CC
127
     */
128
    public function queueRelations(CC $parentStore, $parentEntity, Node $parentNode, array $parentData): CC
129
    {
130
        $state = $parentNode->getState();
131
        $sequence = new ContextSequence();
132
133
        // queue all "left" graph branches
134
        foreach ($this->dependencies as $name => $relation) {
135
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
136
                continue;
137
            }
138
            $state->markVisited($name);
139
140
            $command = $this->queueRelation(
141
                $parentStore,
142
                $parentEntity,
143
                $parentNode,
144
                $relation,
145
                $relation->extract($parentData[$name] ?? null),
146
                $parentNode->getRelation($name)
147
            );
148
149
            if ($command !== null) {
150
                $sequence->addCommand($command);
151
            }
152
        }
153
154
        // queue target entity
155
        $sequence->addPrimary($parentStore);
156
157
        // queue all "right" graph branches
158
        foreach ($this->relations as $name => $relation) {
159
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
160
                continue;
161
            }
162
            $state->markVisited($name);
163
164
            $command = $this->queueRelation(
165
                $parentStore,
166
                $parentEntity,
167
                $parentNode,
168
                $relation,
169
                $relation->extract($parentData[$name] ?? null),
170
                $parentNode->getRelation($name)
171
            );
172
173
            if ($command !== null) {
174
                $sequence->addCommand($command);
175
            }
176
        }
177
178
        if (\count($sequence) === 1) {
179
            return current($sequence->getCommands());
180
        }
181
182
        return $sequence;
183
    }
184
185
    /**
186
     * Queue the relation.
187
     *
188
     * @param CC                $parentStore
189
     * @param object            $parentEntity
190
     * @param Node              $parentNode
191
     * @param RelationInterface $relation
192
     * @param mixed             $related
193
     * @param mixed             $original
194
     * @return CommandInterface|null
195
     */
196
    private function queueRelation(
197
        CC $parentStore,
198
        $parentEntity,
199
        Node $parentNode,
200
        RelationInterface $relation,
201
        $related,
202
        $original
203
    ): ?CommandInterface {
204
        if (($related instanceof ReferenceInterface || $related === null) && $related === $original) {
205
            // no changes in non changed promised relation
206
            return null;
207
        }
208
209
        $relStore = $relation->queue(
210
            $parentStore,
211
            $parentEntity,
212
            $parentNode,
213
            $related,
214
            $original
215
        );
216
217
        // update current relation state
218
        $parentNode->getState()->setRelation($relation->getName(), $related);
219
220
        return $relStore;
221
    }
222
223
    /**
224
     * Check if both references are equal.
225
     *
226
     * @param mixed $a
227
     * @param mixed $b
228
     * @return bool
229
     */
230
    private function sameReference($a, $b): bool
231
    {
232
        if (!$a instanceof ReferenceInterface || !$b instanceof ReferenceInterface) {
233
            return false;
234
        }
235
236
        return $a->__role() === $b->__role() && $a->__scope() === $b->__scope();
237
    }
238
}
239