BelongsTo::shouldPull()   C
last analyzed

Complexity

Conditions 14
Paths 96

Size

Total Lines 56
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 15.8701

Importance

Changes 0
Metric Value
cc 14
eloc 37
c 0
b 0
f 0
nc 96
nop 2
dl 0
loc 56
ccs 26
cts 33
cp 0.7879
crap 15.8701
rs 6.2666

How to fix   Long Method    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
            $innerKey = $this->innerKeys[$i];
144
            if (!\array_key_exists($innerKey, $oldData) || $oldData[$innerKey] !== $newData[$outerKey]) {
145
                return true;
146
            }
147
            $toReference[$outerKey] = $current[$innerKey];
148
            $noChanges = $noChanges && Node::compare($current[$innerKey], $oldData[$innerKey]) === 0;
149
        }
150
        // If no changes
151
        if ($noChanges) {
152
            $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
153
            return false;
154 314
        }
155
        // Nullable relation and null values
156 314
        if ($this->isNullable()) {
157 314
            $isNull = true;
158 314
            foreach ($this->innerKeys as $innerKey) {
159 314
                if (!\array_key_exists($innerKey, $current) || $current[$innerKey] !== null) {
160
                    $isNull = false;
161
                    break;
162
                }
163
            }
164 56
            if ($isNull) {
165
                $tuple->state->setRelation($this->getName(), null);
166 56
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
167 8
                return false;
168
            }
169
        }
170 56
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
171 56
172
        $reference = new Reference($this->target, $toReference);
173 40
        $tuple->state->setRelation(
174
            $this->getName(),
175
            $this->resolve($reference, false) ?? $reference,
176 56
        );
177 56
178 56
        return false;
179 56
    }
180 40
181
    private function pullValues(State $state, State $related): void
182 16
    {
183
        $changes = $related->getTransactionData();
184
        foreach ($this->outerKeys as $i => $outerKey) {
185 16
            if (isset($changes[$outerKey])) {
186 16
                $state->register($this->innerKeys[$i], $changes[$outerKey]);
187 16
            }
188
        }
189
    }
190 104
191
    private function checkNullValuePossibility(Tuple $tuple): bool
192 104
    {
193 104
        if ($tuple->status < Tuple::STATUS_WAITED) {
194 104
            return true;
195 56
        }
196
197 48
        if ($tuple->status < Tuple::STATUS_PREPROCESSED
198 48
            && \array_intersect($this->innerKeys, $tuple->state->getWaitingFields(false)) !== []
199 48
        ) {
200 40
            return true;
201
        }
202 40
        // Check
203
        $values = [];
204
        $data = $tuple->state->getData();
205 48
        foreach ($this->innerKeys as $i => $innerKey) {
206 56
            if (!isset($data[$innerKey])) {
207 40
                return false;
208
            }
209 56
            $values[$this->outerKeys[$i]] = $data[$innerKey];
210
        }
211
212 48
        $tuple->state->setRelation($this->getName(), new Reference($this->target, $values));
213 48
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
214
        return true;
215 48
    }
216 48
217
    private function setNullFromRelated(Tuple $tuple, bool $isPreparing): void
218
    {
219 48
        $state = $tuple->state;
220
        $node = $tuple->node;
221
        if (!$this->isNullable()) {
222
            if ($isPreparing) {
223
                // set null unchanged fields
224
                $changes = $state->getChanges();
225
                foreach ($this->innerKeys as $innerKey) {
226
                    if (!isset($changes[$innerKey])) {
227
                        $state->register($innerKey, null);
228
                        // Field must be filled
229
                        $state->waitField($innerKey, true);
230
                    }
231
                }
232
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_PROCESS);
233
            } elseif (!$this->checkNullValuePossibility($tuple)) {
234
                throw new NullException(\sprintf('Relation `%s`.%s can not be null.', $node->getRole(), (string) $this));
235
            }
236
            return;
237
        }
238
239
        $original = $node->getRelation($this->getName());
240
        if ($original !== null) {
241
            // reset keys
242
            foreach ($this->innerKeys as $innerKey) {
243
                $state->register($innerKey, null);
244
            }
245
        }
246
        $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
247
    }
248
}
249