Passed
Push — 2.x ( 0b5227...cb81b7 )
by butschster
16:17
created

Node::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Heap;
6
7
use Cycle\Database\Injection\ValueInterface;
8
use Cycle\ORM\Heap\Traits\RelationTrait;
9
use Cycle\ORM\Reference\ReferenceInterface;
10
use Cycle\ORM\RelationMap;
11
use DateTimeImmutable;
12
use DateTimeInterface;
13
use JetBrains\PhpStorm\ExpectedValues;
14
use Stringable;
15
16
use const FILTER_NULL_ON_FAILURE;
17
use const FILTER_VALIDATE_BOOLEAN;
18
use const SORT_STRING;
19
20
/**
21
 * Node (metadata) carries meta information about entity state, changes forwards data to other points through
22
 * inner states.
23
 */
24
final class Node
25
{
26
    use RelationTrait;
27
28
    // Different entity states in a pool
29
    public const NEW = 1;
30
    public const MANAGED = 2;
31
    public const SCHEDULED_INSERT = 3;
32
    public const SCHEDULED_UPDATE = 4;
33
    public const SCHEDULED_DELETE = 5;
34
    public const DELETED = 6;
35
36
    private ?State $state = null;
37
    private array $rawData = [];
38
39
    /**
40
     * @param array<string, mixed> $data
41
     */
42 5450
    public function __construct(
43
        #[ExpectedValues(valuesFromClass: self::class)]
44
        private int $status,
45
        private array $data,
46
        private string $role
47
    ) {
48 5450
        $this->updateRawData();
49
    }
50
51
    /**
52
     * Reset state.
53
     */
54 5449
    public function __destruct()
55
    {
56 5449
        unset($this->data, $this->rawData, $this->state, $this->relations);
57
    }
58
59 5450
    public function getRole(): string
60
    {
61 5450
        return $this->role;
62
    }
63
64
    /**
65
     * @internal
66
     */
67 2940
    public function createState(): State
68
    {
69 2940
        return $this->state = new State($this->status, $this->data, $this->rawData);
70
    }
71
72
    /**
73
     * @internal
74
     */
75 2704
    public function setState(State $state): self
76
    {
77 2704
        $this->state = $state;
78 2704
        return $this;
79
    }
80
81
    /**
82
     * Current point state (set of changes).
83
     *
84
     * @internal
85
     */
86 2820
    public function getState(): ?State
87
    {
88 2820
        return $this->state;
89
    }
90
91
    /**
92
     * @internal
93
     */
94 5450
    public function hasState(): bool
95
    {
96 5450
        return $this->state !== null;
97
    }
98
99
    /**
100
     * Reset point state and flush all the changes.
101
     *
102
     * @internal
103
     */
104 168
    public function resetState(): void
105
    {
106 168
        $this->state = null;
107
    }
108
109
    /**
110
     * Get current state.
111
     */
112 2900
    public function getStatus(): int
113
    {
114 2900
        return $this->state?->getStatus() ?? $this->status;
115
    }
116
117
    /**
118
     * The intial (post-load) node date. Does not change during the transaction.
119
     */
120 5450
    public function getData(): array
121
    {
122 5450
        return $this->data;
123
    }
124
125
    /**
126
     * Sync the point state and return data diff.
127
     */
128 2660
    public function syncState(RelationMap $relMap, State $state): array
129
    {
130 2660
        $changes = array_udiff_assoc($state->getTransactionData(), $this->data, [self::class, 'compare']);
131
132 2660
        $relations = $relMap->getRelations();
133 2660
        foreach ($state->getRelations() as $name => $relation) {
134 2114
            if ($relation instanceof ReferenceInterface
135 2114
                && isset($relations[$name])
136 2114
                && (isset($this->relations[$name]) xor isset($relation))
137
            ) {
138 24
                $changes[$name] = $relation->hasValue() ? $relation->getValue() : $relation;
139
            }
140 2114
            $this->setRelation($name, $relation);
141
        }
142
143
        // DELETE handled separately
144 2660
        $this->status = self::MANAGED;
145 2660
        $this->data = $state->getTransactionData();
146 2660
        $this->updateRawData();
147 2660
        $this->state = null;
148
149 2660
        return $changes;
150
    }
151
152
    /**
153
     * @internal
154
     */
155 278
    public static function convertToSolid(mixed $value): mixed
156
    {
157 278
        if (!\is_object($value)) {
158 8
            return $value;
159
        }
160 278
        if ($value instanceof DateTimeInterface) {
161 172
            return $value instanceof DateTimeImmutable ? $value : DateTimeImmutable::createFromInterface($value);
162
        }
163 142
        return $value instanceof Stringable ? $value->__toString() : $value;
164
    }
165
166 2654
    public static function compare(mixed $a, mixed $b): int
167
    {
168 2654
        if ($a === $b) {
169 2540
            return 0;
170
        }
171 1096
        if ($a === null xor $b === null) {
172 314
            return 1;
173
        }
174
175 838
        $ta = [\gettype($a), \gettype($b)];
176
177
        // array, boolean, double, integer, object, string
178 838
        \sort($ta, SORT_STRING);
179
180 838
        if ($ta[0] === 'object' || $ta[1] === 'object') {
181
            // Both are objects
182 122
            if ($ta[0] === $ta[1]) {
183 78
                if ($a instanceof DateTimeInterface && $b instanceof DateTimeInterface) {
184 48
                    return $a <=> $b;
185
                }
186 30
                if ($a instanceof ValueInterface && $b instanceof ValueInterface) {
187 12
                    return $a->rawValue() <=> $b->rawValue();
188
                }
189 18
                if ($a instanceof Stringable && $b instanceof Stringable) {
190 10
                    return $a->__toString() <=> $b->__toString();
191
                }
192 8
                return (int)($a::class !== $b::class || (array)$a !== (array)$b);
193
            }
194
            // Object and string/int
195 52
            if ($ta[1] === 'string' || $ta[0] === 'integer') {
196 52
                $a = $a instanceof Stringable ? $a->__toString() : (string)$a;
197 52
                $b = $b instanceof Stringable ? $b->__toString() : (string)$b;
198 52
                return $a <=> $b;
199
            }
200
            return -1;
201
        }
202
203 746
        if ($ta[1] === 'string') {
204 518
            if ($a === '' || $b === '') {
205 4
                return -1;
206
            }
207 514
            if ($ta[0] === 'integer') {
208 52
                return \is_numeric($a) && \is_numeric($b) ? (int)((string)$a !== (string)$b) : -1;
209
            }
210 462
            if ($ta[0] === 'double') {
211 10
                return \is_numeric($a) && \is_numeric($b) ? (int)((float)$a !== (float)$b) : -1;
212
            }
213
        }
214
215 680
        if ($ta[0] === 'boolean') {
216 24
            $a = \filter_var($a, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
217 24
            $b = \filter_var($b, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
218 24
            return (int)($a !== $b);
219
        }
220
221 656
        if ($ta === ['double', 'integer']) {
222 20
            return (int)((float)$a !== (float)$b);
223
        }
224
225 636
        return 1;
226
    }
227
228 5450
    private function updateRawData(): void
229
    {
230 5450
        $this->rawData = [];
231 5450
        foreach ($this->data as $field => $value) {
232 5282
            if (!\is_object($value)) {
233 5282
                continue;
234
            }
235 278
            $this->rawData[$field] = self::convertToSolid($value);
236
        }
237
    }
238
}
239