Passed
Pull Request — master (#234)
by
unknown
02:27
created

Node::getInitialData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Cycle DataMapper ORM
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\ORM\Heap;
13
14
use Cycle\ORM\Context\ConsumerInterface;
15
use Cycle\ORM\Context\ProducerInterface;
16
use Cycle\ORM\Heap\Traits\RelationTrait;
17
use DateTime;
18
use DateTimeImmutable;
19
20
use function is_object;
21
use function is_string;
22
23
/**
24
 * Node (metadata) carries meta information about entity state, changes forwards data to other points through
25
 * inner states.
26
 */
27
final class Node implements ProducerInterface, ConsumerInterface
28
{
29
    use RelationTrait;
30
31
    // Different entity states in a pool
32
    public const PROMISED = 0;
33
    public const NEW = 1;
34
    public const MANAGED = 2;
35
    public const SCHEDULED_INSERT = 3;
36
    public const SCHEDULED_UPDATE = 4;
37
    public const SCHEDULED_DELETE = 5;
38
    public const DELETED = 6;
39
40
    /** @var string */
41
    private $role;
42
43
    /** @var int */
44
    private $status;
45
46
    /** @var array */
47
    private $data;
48
49
    /** @var State|null */
50
    private $state;
51
52
    private $dataObjectsState = [];
53
54
    /**
55
     * @param int    $status
56
     * @param array  $data
57
     * @param string $role
58
     */
59
    public function __construct(int $status, array $data, string $role)
60
    {
61
        $this->status = $status;
62
        $this->data = $data;
63
        $this->role = $role;
64
65
        $this->setObjectsState($data);
66
    }
67
68
    /**
69
     * Reset state.
70
     */
71
    public function __destruct()
72
    {
73
        $this->data = [];
74
        $this->state = null;
75
        $this->relations = [];
76
    }
77
78
    /**
79
     * @return string
80
     */
81
    public function getRole(): string
82
    {
83
        return $this->role;
84
    }
85
86
    /**
87
     * Current point state (set of changes).
88
     *
89
     * @return State
90
     */
91
    public function getState(): State
92
    {
93
        if ($this->state === null) {
94
            $this->state = new State($this->status, $this->data);
95
        }
96
97
        return $this->state;
98
    }
99
100
    public function hasState(): bool
101
    {
102
        return $this->state !== null;
103
    }
104
105
    /**
106
     * Set new state value.
107
     *
108
     * @param int $state
109
     */
110
    public function setStatus(int $state): void
111
    {
112
        $this->getState()->setStatus($state);
113
    }
114
115
    /**
116
     * Get current state.
117
     *
118
     * @return int
119
     */
120
    public function getStatus(): int
121
    {
122
        if ($this->state !== null) {
123
            return $this->state->getStatus();
124
        }
125
126
        return $this->status;
127
    }
128
129
    /**
130
     * Set new state data (will trigger state handlers).
131
     *
132
     * @param array $data
133
     */
134
    public function setData(array $data): void
135
    {
136
        $this->getState()->setData($data);
137
    }
138
139
    /**
140
     * Get current state data. Mutalbe inside the transaction.
141
     *
142
     * @return array
143
     */
144
    public function getData(): array
145
    {
146
        if ($this->state !== null) {
147
            return $this->state->getData();
148
        }
149
150
        return $this->data;
151
    }
152
153
    /**
154
     * The intial (post-load) node date. Does not change during the transaction.
155
     *
156
     * @return array
157
     */
158
    public function getInitialData(): array
159
    {
160
        return $this->data;
161
    }
162
163
    /**
164
     * @inheritdoc
165
     */
166
    public function forward(
167
        string $key,
168
        ConsumerInterface $consumer,
169
        string $target,
170
        bool $trigger = false,
171
        int $stream = self::DATA
172
    ): void {
173
        $this->getState()->forward($key, $consumer, $target, $trigger, $stream);
174
    }
175
176
    /**
177
     * @inheritdoc
178
     */
179
    public function register(string $key, $value, bool $fresh = false, int $stream = self::DATA): void
180
    {
181
        $this->getState()->register($key, $value, $fresh, $stream);
182
    }
183
184
    /**
185
     * Sync the point state and return data diff.
186
     *
187
     * @return array
188
     */
189
    public function syncState(): array
190
    {
191
        if ($this->state === null) {
192
            return [];
193
        }
194
195
        $changes = $this->getChanges($this->state->getData(), $this->data);
196
        foreach ($this->state->getRelations() as $name => $relation) {
197
            $this->setRelation($name, $relation);
198
        }
199
200
        // DELETE handled separately
201
        $this->status = self::MANAGED;
202
        $this->data = $this->state->getData();
203
        $this->state = null;
204
205
        return $changes;
206
    }
207
208
    /**
209
     * Reset point state and flush all the changes.
210
     */
211
    public function resetState(): void
212
    {
213
        $this->state = null;
214
    }
215
216
    /**
217
     * @param mixed $a
218
     * @param mixed $b
219
     *
220
     * @return int
221
     */
222
    public static function compare($a, $b): int
223
    {
224
        if ($a == $b) {
225
            if (($a === null) !== ($b === null)) {
226
                return 1;
227
            }
228
229
            return 0;
230
        }
231
232
        return ($a > $b) ? 1 : -1;
233
    }
234
235
    public function getChanges(array $current, array $from): array
236
    {
237
        foreach ($this->dataObjectsState as $field => $value) {
238
            if (is_string($value) && $this->isStringable($current[$field])) {
239
                if ((string) $current[$field] !== $value) {
240
                    unset($from[$field]);
241
                }
242
                continue;
243
            }
244
            if ($value instanceof DateTimeImmutable && ($value <=> $current[$field]) !== 0) {
245
                unset($from[$field]);
246
            }
247
        }
248
249
        // in a future mapper must support solid states
250
        return array_udiff_assoc($current, $from, [self::class, 'compare']);
251
    }
252
253
    protected function setObjectsState(array $data): void
254
    {
255
        foreach ($data as $field => $value) {
256
            if ($this->isStringable($value)) {
257
                $this->dataObjectsState[$field] = (string) $value;
258
                continue;
259
            }
260
            if ($value instanceof DateTime) {
261
                $this->dataObjectsState[$field] = DateTimeImmutable::createFromMutable($value);
262
            }
263
        }
264
    }
265
266
    protected function isStringable($value): bool
267
    {
268
        return is_object($value) && method_exists($value, '__toString');
269
    }
270
}
271