Node   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 98.7%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 77
dl 0
loc 212
ccs 76
cts 77
cp 0.987
rs 7.92
c 1
b 1
f 0
wmc 51

14 Methods

Rating   Name   Duplication   Size   Complexity  
A hasState() 0 3 1
D compare() 0 60 28
A syncState() 0 22 5
A getData() 0 3 1
A updateRawData() 0 8 3
A getState() 0 3 1
A resetState() 0 3 1
A setState() 0 4 1
A getRole() 0 3 1
A getStatus() 0 3 1
A __construct() 0 7 1
A convertToSolid() 0 9 5
A __destruct() 0 3 1
A createState() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Node often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Node, and based on these observations, apply Extract Interface, too.

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
Unused Code introduced by
The parameter $relMap is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

190
    public function syncState(/** @scrutinizer ignore-unused */ RelationMap $relMap, State $state): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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