Passed
Push — master ( 8ff0f0...b1da4e )
by Anton
01:53
created

RelationMap::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
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) || is_null($item)) {
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($item);
79
            $node->setRelation($name, $orig);
80
        }
81
82
        return $data;
83
    }
84
85
    /**
86
     * Queue entity relations.
87
     *
88
     * @param CC     $parentStore
89
     * @param object $parentEntity
90
     * @param Node   $parentNode
91
     * @param array  $parentData
92
     * @return CC
93
     */
94
    public function queueRelations(CC $parentStore, $parentEntity, Node $parentNode, array $parentData): CC
95
    {
96
        $state = $parentNode->getState();
97
        $sequence = new ContextSequence();
98
99
        // queue all "left" graph branches
100
        foreach ($this->dependencies as $name => $relation) {
101
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
102
                continue;
103
            }
104
            $state->markVisited($name);
105
106
            $command = $this->queueRelation(
107
                $parentStore,
108
                $parentEntity,
109
                $parentNode,
110
                $relation,
111
                $relation->extract($parentData[$name] ?? null),
112
                $parentNode->getRelation($name)
113
            );
114
115
            if ($command !== null) {
116
                $sequence->addCommand($command);
117
            }
118
        }
119
120
        // queue target entity
121
        $sequence->addPrimary($parentStore);
122
123
        // queue all "right" graph branches
124
        foreach ($this->relations as $name => $relation) {
125
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
126
                continue;
127
            }
128
            $state->markVisited($name);
129
130
            $command = $this->queueRelation(
131
                $parentStore,
132
                $parentEntity,
133
                $parentNode,
134
                $relation,
135
                $relation->extract($parentData[$name] ?? null),
136
                $parentNode->getRelation($name)
137
            );
138
139
            if ($command !== null) {
140
                $sequence->addCommand($command);
141
            }
142
        }
143
144
        if (count($sequence) === 1) {
145
            return current($sequence->getCommands());
146
        }
147
148
        return $sequence;
149
    }
150
151
    /**
152
     * Queue the relation.
153
     *
154
     * @param CC                $parentStore
155
     * @param object            $parentEntity
156
     * @param Node              $parentNode
157
     * @param RelationInterface $relation
158
     * @param mixed             $related
159
     * @param mixed             $original
160
     * @return CommandInterface|null
161
     */
162
    private function queueRelation(
163
        CC $parentStore,
164
        $parentEntity,
165
        Node $parentNode,
166
        RelationInterface $relation,
167
        $related,
168
        $original
169
    ): ?CommandInterface {
170
        if (($related instanceof ReferenceInterface || is_null($related)) && $related === $original) {
171
            // no changes in non changed promised relation
172
            return null;
173
        }
174
175
        $relStore = $relation->queue(
176
            $parentStore,
177
            $parentEntity,
178
            $parentNode,
179
            $related,
180
            $original
181
        );
182
183
        // update current relation state
184
        $parentNode->getState()->setRelation($relation->getName(), $related);
185
186
        return $relStore;
187
    }
188
}