Passed
Pull Request — 2.x (#517)
by Aleksei
16:17
created

RefersTo::prepare()   B

Complexity

Conditions 9
Paths 17

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 22
c 0
b 0
f 0
nc 17
nop 4
dl 0
loc 34
ccs 22
cts 22
cp 1
crap 9
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Relation;
6
7
use Cycle\ORM\Heap\Node;
8
use Cycle\ORM\Heap\State;
9
use Cycle\ORM\ORMInterface;
10
use Cycle\ORM\Reference\ReferenceInterface;
11
use Cycle\ORM\Relation\Traits\ToOneTrait;
12
use Cycle\ORM\Service\EntityProviderInterface;
13
use Cycle\ORM\Transaction\Pool;
14
use Cycle\ORM\Transaction\Tuple;
15
16
/**
17
 * Variation of belongs-to relation which provides the ability to be self linked. Relation can be used
18
 * to create cyclic references. Relation does not trigger store operation of referenced object!
19
 *
20
 * @internal
21
 */
22
class RefersTo extends AbstractRelation implements DependencyInterface
23
{
24
    use ToOneTrait;
25
26 870
    public function __construct(ORMInterface $orm, string $role, string $name, string $target, array $schema)
27
    {
28 870
        $this->entityProvider = $orm->getService(EntityProviderInterface::class);
29
30 870
        parent::__construct($orm, $role, $name, $target, $schema);
31
    }
32
33 406
    public function prepare(Pool $pool, Tuple $tuple, mixed $related, bool $load = true): void
34
    {
35 406
        $state = $tuple->state;
36 406
        $relName = $this->getName();
37
38 406
        if (SpecialValue::isNotSet($related)) {
39 8
            if (!$state->hasRelation($relName)) {
40 8
                $state->setRelationStatus($relName, RelationInterface::STATUS_DEFERRED);
41
                return;
42 406
            }
43 142
44
            $related = $state->getRelation($relName);
45 376
        }
46 376
47 96
        $node = $tuple->node;
48 96
        $tuple->state->setRelation($relName, $related);
49
50
        if ($related instanceof ReferenceInterface && $this->resolve($related, false) !== null) {
51 352
            $related = $related->getValue();
52 352
            $tuple->state->setRelation($relName, $related);
53 352
        }
54 320
        if ($this->checkNullValue($node, $tuple->state, $related)) {
55
            return;
56
        }
57
        $this->registerWaitingFields($tuple->state, false);
58 424
        if ($related instanceof ReferenceInterface) {
59
            $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_DEFERRED);
60 424
            return;
61 424
        }
62
63 424
        $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_PROCESS);
64 8
        $rTuple = $pool->offsetGet($related);
65 8
        if ($rTuple === null && $this->isCascade()) {
66
            $pool->attachStore($related, false, null, null, false);
67 424
        }
68 88
    }
69 88
70 88
    public function queue(Pool $pool, Tuple $tuple): void
71 88
    {
72
        $state = $tuple->state;
73 88
        $relName = $this->getName();
74 88
75 88
        if (!$state->hasRelation($relName)) {
76
            $state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
77
            return;
78 408
        }
79 56
80
        $node = $tuple->node;
81 352
        $related = $tuple->state->getRelation($relName);
82 352
83
        if ($related instanceof ReferenceInterface && ($related->hasValue() || $this->resolve($related, false) !== null)) {
84
            $related = $related->getValue();
85
            $tuple->state->setRelation($relName, $related);
86
        }
87
        if ($related instanceof ReferenceInterface) {
88
            $scope = $related->getScope();
89
            if (\array_intersect($this->outerKeys, \array_keys($scope))) {
90
                foreach ($this->outerKeys as $i => $outerKey) {
91
                    $tuple->state->register($this->innerKeys[$i], $scope[$outerKey]);
92
                }
93
                $node->setRelation($relName, $related);
94
                $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
95 352
                return;
96 208
            }
97 208
        }
98 352
        if ($this->checkNullValue($tuple->node, $tuple->state, $related)) {
99
            return;
100 328
        }
101 328
        $rTuple = $pool->offsetGet($related);
102 328
        if ($rTuple === null) {
103 328
            if ($this->isCascade()) {
104
                // todo: cascade true?
105
                $rTuple = $pool->attachStore($related, false, null, null, false);
106 192
            } elseif (
107 192
                $tuple->state->getRelationStatus($relName) !== RelationInterface::STATUS_DEFERRED
108
                || $tuple->status !== Tuple::STATUS_PROPOSED
109
            ) {
110
                $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_DEFERRED);
111 328
                return;
112
            } else {
113 328
                $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
114 328
                return;
115 328
            }
116 328
        }
117
118
        if ($rTuple->status === Tuple::STATUS_PROCESSED
119
            || ($rTuple->status > Tuple::STATUS_PREPARING
120
                && $rTuple->state->getStatus() !== node::NEW
121 454
                && \array_intersect($this->outerKeys, $rTuple->state->getWaitingFields()) === [])
122
        ) {
123 454
            $this->pullValues($tuple->state, $rTuple->state);
124 376
            $node->setRelation($relName, $related);
125
            $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_RESOLVED);
126 198
            return;
127
        }
128 198
129
        if ($tuple->status !== Tuple::STATUS_PREPARING) {
130 96
            $tuple->state->setRelationStatus($relName, RelationInterface::STATUS_DEFERRED);
131 96
        }
132
    }
133
134
    private function pullValues(State $state, State $rState): void
135 198
    {
136 198
        $changes = $rState->getTransactionData();
137 198
        foreach ($this->outerKeys as $i => $outerKey) {
138
            if (isset($changes[$outerKey])) {
139
                $state->register($this->innerKeys[$i], $changes[$outerKey]);
140
            }
141
        }
142
    }
143
144
    private function checkNullValue(Node $node, State $state, mixed $value): bool
145
    {
146
        if ($value !== null) {
147
            return false;
148
        }
149
        $original = $node->getRelation($this->getName());
150
        // Original is not null
151
        if ($original !== null) {
152
            // Reset keys
153
            foreach ($this->innerKeys as $innerKey) {
154
                $state->register($innerKey, null);
155
            }
156
        }
157
158
        $node->setRelation($this->getName(), null);
159
        $state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED);
160
        return true;
161
    }
162
}
163