BelongsTo   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Test Coverage

Coverage 90.68%

Importance

Changes 0
Metric Value
eloc 130
c 0
b 0
f 0
dl 0
loc 222
ccs 107
cts 118
cp 0.9068
rs 7.44
wmc 52

7 Methods

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

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
            $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