BelongsTo   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 90.68%

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 206
ccs 107
cts 118
cp 0.9068
rs 8.48
c 0
b 0
f 0
wmc 49

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B queue() 0 34 9
B prepare() 0 31 8
A checkNullValuePossibility() 0 24 6
B setNullFromRelated() 0 30 8
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
40 544
        $relName = $this->getName();
41 544
        if ($state->hasRelation($relName)) {
42 64
            $prefill = $state->getRelation($relName);
43 64
            $nodeValue = $tuple->node->getRelation($relName);
44 64
            if ($nodeValue === $related) {
45 48
                $related = $prefill;
46
            }
47
        }
48 544
        $state->setRelation($relName, $related);
49
50 544
        if ($related === null) {
51 96
            $this->setNullFromRelated($tuple, true);
52 96
            return;
53
        }
54 490
        $this->registerWaitingFields($tuple->state);
55 490
        if ($related instanceof ReferenceInterface && $this->resolve($related, false) !== null) {
56 32
            $related = $related->getValue();
57 32
            $tuple->state->setRelation($relName, $related);
58
        }
59
60 490
        $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_PROCESS);
61 490
        if ($related instanceof ReferenceInterface) {
62 56
            return;
63
        }
64 450
        $rTuple = $pool->offsetGet($related);
65 450
        if ($rTuple === null) {
66 314
            $pool->attachStore($related, $this->isCascade(), null, null, false);
67
        }
68
    }
69
70 544
    public function queue(Pool $pool, Tuple $tuple): void
71
    {
72 544
        $state = $tuple->state;
73 544
        $related = $state->getRelation($this->getName());
74
75 544
        if ($related instanceof ReferenceInterface && $related->hasValue()) {
76 8
            $related = $related->getValue();
77 8
            $state->setRelation($this->getName(), $related);
78
        }
79 544
        if ($related === null) {
80 64
            $this->setNullFromRelated($tuple, false);
81 64
            return;
82
        }
83 490
        if ($related instanceof ReferenceInterface) {
84 48
            $scope = $related->getScope();
85 48
            if (array_intersect($this->outerKeys, array_keys($scope))) {
86 48
                foreach ($this->outerKeys as $i => $outerKey) {
87 48
                    $state->register($this->innerKeys[$i], $scope[$outerKey]);
88
                }
89 48
                $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
90 48
                return;
91
            }
92
            if ($tuple->status >= Tuple::STATUS_WAITED) {
93
                $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
94
            }
95
            return;
96
        }
97
        /** @var Tuple $rTuple */
98 450
        $rTuple = $pool->offsetGet($related);
99
100 450
        if ($this->shouldPull($tuple, $rTuple)) {
101 314
            $this->pullValues($state, $rTuple->state);
102 314
            $state->setRelation($this->getName(), $related);
103 314
            $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
104
        }
105
    }
106
107 450
    private function shouldPull(Tuple $tuple, Tuple $rTuple): bool
108
    {
109 450
        $minStatus = Tuple::STATUS_PREPROCESSED;
110 450
        if ($this->inversion !== null) {
111 192
            $relName = $this->getTargetRelationName();
112 192
            if ($rTuple->state->getRelationStatus($relName) === RelationInterface::STATUS_RESOLVED) {
113 136
                $minStatus = Tuple::STATUS_DEFERRED;
114
            }
115
        }
116 450
        if ($rTuple->status < $minStatus) {
117 97
            return false;
118
        }
119 450
120
        // Check bidirected relation: when related entity has been removed from HasSome relation
121 450
        $oldData = $tuple->node->getData();
122
        $newData = $rTuple->state->getTransactionData();
123
        $current = $tuple->state->getData();
124
        $noChanges = true;
125 450
        $toReference = [];
126 450
        foreach ($this->outerKeys as $i => $outerKey) {
127 450
            $innerKey = $this->innerKeys[$i];
128 450
            if (!array_key_exists($innerKey, $oldData) || $oldData[$innerKey] !== $newData[$outerKey]) {
129 450
                return true;
130 450
            }
131 450
            $toReference[$outerKey] = $current[$innerKey];
132 314
            $noChanges = $noChanges && Node::compare($current[$innerKey], $oldData[$innerKey]) === 0;
133
        }
134 296
        // If no changes
135
        if ($noChanges) {
136
            $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
137 288
            return false;
138 288
        }
139 288
        // Nullable relation and null values
140
        if ($this->isNullable()) {
141
            $isNull = true;
142
            foreach ($this->innerKeys as $innerKey) {
143
                if (!array_key_exists($innerKey, $current) || $current[$innerKey] !== null) {
144
                    $isNull = false;
145
                    break;
146
                }
147
            }
148
            if ($isNull) {
149
                $tuple->state->setRelation($this->getName(), null);
150
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
151
                return false;
152
            }
153
        }
154 314
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
155
156 314
        $reference = new Reference($this->target, $toReference);
157 314
        $tuple->state->setRelation(
158 314
            $this->getName(),
159 314
            $this->resolve($reference, false) ?? $reference,
160
        );
161
162
        return false;
163
    }
164 56
165
    private function pullValues(State $state, State $related): void
166 56
    {
167 8
        $changes = $related->getTransactionData();
168
        foreach ($this->outerKeys as $i => $outerKey) {
169
            if (isset($changes[$outerKey])) {
170 56
                $state->register($this->innerKeys[$i], $changes[$outerKey]);
171 56
            }
172
        }
173 40
    }
174
175
    private function checkNullValuePossibility(Tuple $tuple): bool
176 56
    {
177 56
        if ($tuple->status < Tuple::STATUS_WAITED) {
178 56
            return true;
179 56
        }
180 40
181
        if ($tuple->status < Tuple::STATUS_PREPROCESSED
182 16
            && \array_intersect($this->innerKeys, $tuple->state->getWaitingFields(false)) !== []
183
        ) {
184
            return true;
185 16
        }
186 16
        // Check
187 16
        $values = [];
188
        $data = $tuple->state->getData();
189
        foreach ($this->innerKeys as $i => $innerKey) {
190 104
            if (!isset($data[$innerKey])) {
191
                return false;
192 104
            }
193 104
            $values[$this->outerKeys[$i]] = $data[$innerKey];
194 104
        }
195 56
196
        $tuple->state->setRelation($this->getName(), new Reference($this->target, $values));
197 48
        $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
198 48
        return true;
199 48
    }
200 40
201
    private function setNullFromRelated(Tuple $tuple, bool $isPreparing): void
202 40
    {
203
        $state = $tuple->state;
204
        $node = $tuple->node;
205 48
        if (!$this->isNullable()) {
206 56
            if ($isPreparing) {
207 40
                // set null unchanged fields
208
                $changes = $state->getChanges();
209 56
                foreach ($this->innerKeys as $innerKey) {
210
                    if (!isset($changes[$innerKey])) {
211
                        $state->register($innerKey, null);
212 48
                        // Field must be filled
213 48
                        $state->waitField($innerKey, true);
214
                    }
215 48
                }
216 48
                $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_PROCESS);
217
            } elseif (!$this->checkNullValuePossibility($tuple)) {
218
                throw new NullException(sprintf('Relation `%s`.%s can not be null.', $node->getRole(), (string)$this));
219 48
            }
220
            return;
221
        }
222
223
        $original = $node->getRelation($this->getName());
224
        if ($original !== null) {
225
            // reset keys
226
            foreach ($this->innerKeys as $innerKey) {
227
                $state->register($innerKey, null);
228
            }
229
        }
230
        $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
231
    }
232
}
233