Completed
Push — master ( 48ad47...c8e5f0 )
by BENOIT
03:00
created

PropertyChangeset::diff()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace BenTools\DoctrineWatcher\Changeset;
4
5
final class PropertyChangeset
6
{
7
    public const INSERT = 'insert';
8
    public const UPDATE = 'update';
9
10
    /**
11
     * @var mixed
12
     */
13
    protected $newValue;
14
15
    /**
16
     * @var mixed
17
     */
18
    protected $oldValue;
19
20
    /**
21
     * @var array
22
     */
23
    protected $additions;
24
25
    /**
26
     * @var array
27
     */
28
    protected $removals;
29
30
    /**
31
     * PropertyChangeset constructor.
32
     *
33
     * @param mixed $newValue
34
     * @param mixed $oldValue
35
     */
36
    public function __construct($oldValue = null, $newValue = null)
37
    {
38
        $this->newValue = $newValue;
39
        $this->oldValue = $oldValue;
40
    }
41
42
    /**
43
     * @return mixed
44
     */
45
    public function getOldValue()
46
    {
47
        return $this->oldValue;
48
    }
49
50
    /**
51
     * @return mixed
52
     */
53
    public function getNewValue()
54
    {
55
        return $this->newValue;
56
    }
57
58
    /**
59
     * @return bool
60
     */
61
    public function hasChanges(): bool
62
    {
63
        if ($this->canBeComparedAsIterables()) {
64
            return $this->hasAdditions() || $this->hasRemovals();
65
        }
66
67
        return $this->oldValue !== $this->newValue;
68
    }
69
70
    /**
71
     * @return iterable
72
     */
73
    public function getAdditions(): iterable
74
    {
75
        if (!$this->canBeComparedAsIterables()) {
76
            throw new \RuntimeException(sprintf('%s can only be called on iterable properties changesets.', __METHOD__));
77
        }
78
79
        $this->computeAdditionsAndRemovals();
80
81
        return $this->additions;
82
    }
83
84
    /**
85
     * @return iterable
86
     */
87
    public function getRemovals(): iterable
88
    {
89
        if (!$this->canBeComparedAsIterables()) {
90
            throw new \RuntimeException(sprintf('%s can only be called on iterable properties changesets.', __METHOD__));
91
        }
92
93
        $this->computeAdditionsAndRemovals();
94
95
        return $this->removals;
96
    }
97
98
    /**
99
     * @return bool
100
     */
101
    public function hasAdditions(): bool
102
    {
103
        if (!$this->canBeComparedAsIterables()) {
104
            throw new \RuntimeException(sprintf('%s can only be called on iterable properties changesets.', __METHOD__));
105
        }
106
107
        $this->computeAdditionsAndRemovals();
108
109
        return [] !== $this->additions;
110
    }
111
112
    /**
113
     * @return bool
114
     */
115
    public function hasRemovals(): bool
116
    {
117
        if (!$this->canBeComparedAsIterables()) {
118
            throw new \RuntimeException(sprintf('%s can only be called on iterable properties changesets.', __METHOD__));
119
        }
120
121
        $this->computeAdditionsAndRemovals();
122
123
        return [] !== $this->removals;
124
    }
125
126
    /**
127
     * @param $value
128
     * @return bool
129
     */
130
    private function isNullOrIterable($value): bool
131
    {
132
        return null === $value || \is_iterable($value);
133
    }
134
135
    /**
136
     * @param $oldValue
137
     * @param $newValue
138
     * @return bool
139
     */
140
    private function canBeComparedAsIterables(): bool
141
    {
142
        return $this->isNullOrIterable($this->oldValue) && $this->isNullOrIterable($this->newValue);
143
    }
144
145
    /**
146
     *
147
     */
148
    private function computeAdditionsAndRemovals(): void
149
    {
150
        if (null !== $this->additions) {
151
            return;
152
        }
153
154
        $old = iterable_to_array($this->oldValue ?? []);
155
        $new = iterable_to_array($this->newValue ?? []);
156
157
        if (!$this->isSequential($old) && !$this->isSequential($new)) {
158
            $this->additions = $this->diff($new, $old);
159
            $this->removals = $this->diff($old, $new);
160
            return;
161
        }
162
        $this->additions = \array_values($this->diff($new, $old));
163
        $this->removals = \array_values($this->diff($old, $new));
164
    }
165
166
    /**
167
     * @param array $array
168
     * @return bool
169
     */
170
    private function isSequential(array $array): bool
171
    {
172
        return isset($array[0]) && \array_keys($array) === \range(0, \count($array) - 1);
173
    }
174
175
    /**
176
     * @param array $a
177
     * @param array $b
178
     * @return array
179
     */
180
    private function diff(array $a, array $b): array
181
    {
182
        return \array_filter($a, function ($item) use ($b) {
183
            return !\in_array($item, $b, true);
184
        });
185
    }
186
}
187