Passed
Push — master ( eae31e...d51527 )
by Anton
01:59
created

RelationMap   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 169
rs 10
c 0
b 0
f 0
wmc 25

4 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 26 6
A __construct() 0 8 3
A queueRelation() 0 25 4
C queueRelations() 0 60 12
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
        if ($this->dependencies === [] && $this->relations === []) {
97
            return $parentStore;
98
        }
99
100
        $state = $parentNode->getState();
101
102
        $sequence = new ContextSequence();
103
104
        // queue all "left" graph branches
105
        foreach ($this->dependencies as $name => $relation) {
106
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
107
                continue;
108
            }
109
            $state->markVisited($name);
110
111
            $command = $this->queueRelation(
112
                $parentStore,
113
                $parentEntity,
114
                $parentNode,
115
                $relation,
116
                $relation->extract($parentData[$name] ?? null),
117
                $parentNode->getRelation($name)
118
            );
119
120
            if ($command !== null) {
121
                $sequence->addCommand($command);
122
            }
123
        }
124
125
        // queue target entity
126
        $sequence->addPrimary($parentStore);
127
128
        // queue all "right" graph branches
129
        foreach ($this->relations as $name => $relation) {
130
            if (!$relation->isCascade() || $parentNode->getState()->visited($name)) {
131
                continue;
132
            }
133
            $state->markVisited($name);
134
135
            $command = $this->queueRelation(
136
                $parentStore,
137
                $parentEntity,
138
                $parentNode,
139
                $relation,
140
                $relation->extract($parentData[$name] ?? null),
141
                $parentNode->getRelation($name)
142
            );
143
144
            if ($command !== null) {
145
                $sequence->addCommand($command);
146
            }
147
        }
148
149
        if (count($sequence) === 1) {
150
            return current($sequence->getCommands());
151
        }
152
153
        return $sequence;
154
    }
155
156
    /**
157
     * Queue the relation.
158
     *
159
     * @param CC                $parentStore
160
     * @param object            $parentEntity
161
     * @param Node              $parentNode
162
     * @param RelationInterface $relation
163
     * @param mixed             $related
164
     * @param mixed             $original
165
     * @return CommandInterface|null
166
     */
167
    private function queueRelation(
168
        CC $parentStore,
169
        $parentEntity,
170
        Node $parentNode,
171
        RelationInterface $relation,
172
        $related,
173
        $original
174
    ): ?CommandInterface {
175
        if (($related instanceof ReferenceInterface || is_null($related)) && $related === $original) {
176
            // no changes in non changed promised relation
177
            return null;
178
        }
179
180
        $relStore = $relation->queue(
181
            $parentStore,
182
            $parentEntity,
183
            $parentNode,
184
            $related,
185
            $original
186
        );
187
188
        // update current relation state
189
        $parentNode->getState()->setRelation($relation->getName(), $related);
190
191
        return $relStore;
192
    }
193
}