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