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

Node::isStringable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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