Passed
Push — master ( 22e738...3e694d )
by Aleksei
03:32 queued 01:20
created

Node::compare()   D

Complexity

Conditions 28
Paths 38

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 32
nc 38
nop 2
dl 0
loc 57
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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