Passed
Pull Request — 2.x (#525)
by Aleksei
20:02
created

BelongsTo::queue()   B

Complexity

Conditions 10
Paths 15

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.1371

Importance

Changes 0
Metric Value
cc 10
eloc 27
c 0
b 0
f 0
nc 15
nop 2
dl 0
loc 41
ccs 24
cts 27
cp 0.8889
crap 10.1371
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Relation;
6
7
use Cycle\ORM\Exception\Relation\NullException;
8
use Cycle\ORM\Heap\Node;
9
use Cycle\ORM\Heap\State;
10
use Cycle\ORM\ORMInterface;
11
use Cycle\ORM\Reference\Reference;
12
use Cycle\ORM\Reference\ReferenceInterface;
13
use Cycle\ORM\Relation\Traits\ToOneTrait;
14
use Cycle\ORM\Service\EntityProviderInterface;
15
use Cycle\ORM\Transaction\Pool;
16
use Cycle\ORM\Transaction\Tuple;
17
18
/**
19
 * Provides ability to link to the parent object.
20
 * Will claim branch up to the parent object and it's relations. To disable
21
 * branch walk-through use RefersTo relation.
22
 *
23
 * @internal
24
 */
25
class BelongsTo extends AbstractRelation implements DependencyInterface
26
{
27
    use ToOneTrait;
28
29 1008
    public function __construct(ORMInterface $orm, string $role, string $name, string $target, array $schema)
30
    {
31 1008
        $this->entityProvider = $orm->getService(EntityProviderInterface::class);
32
33 1008
        parent::__construct($orm, $role, $name, $target, $schema);
34
    }
35
36 544
    public function prepare(Pool $pool, Tuple $tuple, mixed $related, bool $load = true): void
37
    {
38 544
        $state = $tuple->state;
39
        $relName = $this->getName();
40 544
41 544
        if (SpecialValue::isNotSet($related)) {
42 64
            if (!$state->hasRelation($relName)) {
43 64
                $state->setRelationStatus($relName, RelationInterface::STATUS_DEFERRED);
44 64
                return;
45 48
            }
46
47
            $related = $state->getRelation($relName);
48 544
        }
49
50 544
        if ($state->hasRelation($relName)) {
51 96
            $prefill = $state->getRelation($relName);
52 96
            $nodeValue = $tuple->node->getRelation($relName);
53
            if ($nodeValue === $related) {
54 490
                $related = $prefill;
55 490
            }
56 32
        }
57 32
        $state->setRelation($relName, $related);
58
59
        if ($related === null) {
60 490
            $this->setNullFromRelated($tuple, true);
61 490
            return;
62 56
        }
63
        $this->registerWaitingFields($tuple->state);
64 450
        if ($related instanceof ReferenceInterface && $this->resolve($related, false) !== null) {
65 450
            $related = $related->getValue();
66 314
            $tuple->state->setRelation($relName, $related);
67
        }
68
69
        $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_PROCESS);
70 544
        if ($related instanceof ReferenceInterface) {
71
            return;
72 544
        }
73 544
        $rTuple = $pool->offsetGet($related);
74
        if ($rTuple === null) {
75 544
            $pool->attachStore($related, $this->isCascade(), null, null, false);
76 8
        }
77 8
    }
78
79 544
    public function queue(Pool $pool, Tuple $tuple): void
80 64
    {
81 64
        $state = $tuple->state;
82
        $relName = $this->getName();
83 490
84 48
        if (!$state->hasRelation($relName)) {
85 48
            $state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
86 48
            return;
87 48
        }
88
89 48
        $related = $state->getRelation($relName);
90 48
91
        if ($related instanceof ReferenceInterface && $related->hasValue()) {
92
            $related = $related->getValue();
93
            $state->setRelation($relName, $related);
94
        }
95
        if ($related === null) {
96
            $this->setNullFromRelated($tuple, false);
97
            return;
98 450
        }
99
        if ($related instanceof ReferenceInterface) {
100 450
            $scope = $related->getScope();
101 314
            if (\array_intersect($this->outerKeys, \array_keys($scope))) {
102 314
                foreach ($this->outerKeys as $i => $outerKey) {
103 314
                    $state->register($this->innerKeys[$i], $scope[$outerKey]);
104
                }
105
                $state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
106
                return;
107 450
            }
108
            if ($tuple->status >= Tuple::STATUS_WAITED) {
109 450
                $state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
110 450
            }
111 192
            return;
112 192
        }
113 136
        /** @var Tuple $rTuple */
114
        $rTuple = $pool->offsetGet($related);
115
116 450
        if ($this->shouldPull($tuple, $rTuple)) {
117 97
            $this->pullValues($state, $rTuple->state);
118
            $state->setRelation($relName, $related);
119 450
            $state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
120
        }
121 450
    }
122
123
    private function shouldPull(Tuple $tuple, Tuple $rTuple): bool
124
    {
125 450
        $minStatus = Tuple::STATUS_PREPROCESSED;
126 450
        if ($this->inversion !== null) {
127 450
            $relName = $this->getTargetRelationName();
128 450
            if ($rTuple->state->getRelationStatus($relName) === RelationInterface::STATUS_RESOLVED) {
129 450
                $minStatus = Tuple::STATUS_DEFERRED;
130 450
            }
131 450
        }
132 314
        if ($rTuple->status < $minStatus) {
133
            return false;
134 296
        }
135
136
        // Check bidirected relation: when related entity has been removed from HasSome relation
137 288
        $oldData = $tuple->node->getData();
138 288
        $newData = $rTuple->state->getTransactionData();
139 288
        $current = $tuple->state->getData();
140
        $noChanges = true;
141
        $toReference = [];
142
        foreach ($this->outerKeys as $i => $outerKey) {
143
            if (!\array_key_exists($outerKey, $newData)) {
144
                continue;
145
            }
146
147
            $innerKey = $this->innerKeys[$i];
148
            if (!\array_key_exists($innerKey, $oldData) || $oldData[$innerKey] !== $newData[$outerKey]) {
149
                return true;
150
            }
151
            $toReference[$outerKey] = $current[$innerKey];
152
            $noChanges = $noChanges && Node::compare($current[$innerKey], $oldData[$innerKey]) === 0;
153
        }
154 314
        // If no changes
155
        if ($noChanges) {
156 314
            $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
157 314
            return false;
158 314
        }
159 314
        // Nullable relation and null values
160
        if ($this->isNullable()) {
161
            $isNull = true;
162
            foreach ($this->innerKeys as $innerKey) {
163
                if (!\array_key_exists($innerKey, $current) || $current[$innerKey] !== null) {
164 56
                    $isNull = false;
165
                    break;
166 56
                }
167 8
            }
168
            if ($isNull) {
169
                $tuple->state->setRelation($this->getName(), null);
170 56
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
171 56
                return false;
172
            }
173 40
        }
174
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
175
176 56
        $reference = new Reference($this->target, $toReference);
177 56
        $tuple->state->setRelation(
178 56
            $this->getName(),
179 56
            $this->resolve($reference, false) ?? $reference,
180 40
        );
181
182 16
        return false;
183
    }
184
185 16
    private function pullValues(State $state, State $related): void
186 16
    {
187 16
        $changes = $related->getTransactionData();
188
        foreach ($this->outerKeys as $i => $outerKey) {
189
            if (isset($changes[$outerKey])) {
190 104
                $state->register($this->innerKeys[$i], $changes[$outerKey]);
191
            }
192 104
        }
193 104
    }
194 104
195 56
    private function checkNullValuePossibility(Tuple $tuple): bool
196
    {
197 48
        if ($tuple->status < Tuple::STATUS_WAITED) {
198 48
            return true;
199 48
        }
200 40
201
        if ($tuple->status < Tuple::STATUS_PREPROCESSED
202 40
            && \array_intersect($this->innerKeys, $tuple->state->getWaitingFields(false)) !== []
203
        ) {
204
            return true;
205 48
        }
206 56
        // Check
207 40
        $values = [];
208
        $data = $tuple->state->getData();
209 56
        foreach ($this->innerKeys as $i => $innerKey) {
210
            if (!isset($data[$innerKey])) {
211
                return false;
212 48
            }
213 48
            $values[$this->outerKeys[$i]] = $data[$innerKey];
214
        }
215 48
216 48
        $tuple->state->setRelation($this->getName(), new Reference($this->target, $values));
217
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
218
        return true;
219 48
    }
220
221
    private function setNullFromRelated(Tuple $tuple, bool $isPreparing): void
222
    {
223
        $state = $tuple->state;
224
        $node = $tuple->node;
225
        if (!$this->isNullable()) {
226
            if ($isPreparing) {
227
                // set null unchanged fields
228
                $changes = $state->getChanges();
229
                foreach ($this->innerKeys as $innerKey) {
230
                    if (!isset($changes[$innerKey])) {
231
                        $state->register($innerKey, null);
232
                        // Field must be filled
233
                        $state->waitField($innerKey, true);
234
                    }
235
                }
236
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_PROCESS);
237
            } elseif (!$this->checkNullValuePossibility($tuple)) {
238
                throw new NullException(\sprintf('Relation `%s`.%s can not be null.', $node->getRole(), (string) $this));
239
            }
240
            return;
241
        }
242
243
        $original = $node->getRelation($this->getName());
244
        if ($original !== null) {
245
            // reset keys
246
            foreach ($this->innerKeys as $innerKey) {
247
                $state->register($innerKey, null);
248
            }
249
        }
250
        $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
251
    }
252
}
253