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

BelongsTo   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Test Coverage

Coverage 90.68%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 132
dl 0
loc 226
ccs 107
cts 118
cp 0.9068
rs 6.96
c 1
b 0
f 0
wmc 53

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B prepare() 0 40 10
B queue() 0 41 10
A checkNullValuePossibility() 0 24 6
B setNullFromRelated() 0 30 8
A pullValues() 0 6 3
C shouldPull() 0 60 15

How to fix   Complexity   

Complex Class

Complex classes like BelongsTo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BelongsTo, and based on these observations, apply Extract Interface, too.

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