Set   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 27
Bugs 2 Features 8
Metric Value
wmc 32
c 27
b 2
f 8
lcom 1
cbo 4
dl 0
loc 195
rs 9.6

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 3
A getChange() 0 8 2
A hasChanged() 0 8 2
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A count() 0 8 2
A getIterator() 0 8 2
B compute() 0 24 6
C computeEntry() 0 46 9
A getRawData() 0 8 2
1
<?php
2
/**
3
 * This file is part of the Totem package
4
 *
5
 * For the full copyright and license information, please view the LICENSE file
6
 * that was distributed with this source code.
7
 *
8
 * @copyright Baptiste Clavié <[email protected]>
9
 * @license   http://www.opensource.org/licenses/MIT-License MIT License
10
 */
11
12
namespace Totem;
13
14
use Countable;
15
use ArrayAccess;
16
17
use ArrayIterator;
18
use IteratorAggregate;
19
20
use RuntimeException;
21
use OutOfBoundsException;
22
use BadMethodCallException;
23
24
use Totem\Change\Removal;
25
use Totem\Change\Addition;
26
use Totem\Change\Modification;
27
28
use Totem\SetInterface;
29
use Totem\AbstractSnapshot;
30
use Totem\Snapshot\CollectionSnapshot;
31
32
/**
33
 * Represents a changeset
34
 *
35
 * @author Rémy Gazelot <[email protected]>
36
 * @author Baptiste Clavié <[email protected]>
37
 */
38
class Set implements SetInterface, ArrayAccess, Countable, IteratorAggregate
39
{
40
    protected $changes = null;
41
42
    public function __construct(AbstractSnapshot $old = null, AbstractSnapshot $new = null)
43
    {
44
        if (null !== $old && null !== $new) {
45
            $this->compute($old, $new);
46
        }
47
    }
48
49
    /**
50
     * {@inheritDoc}
51
     *
52
     * @throws OutOfBoundsException The property doesn't exist or wasn't changed
53
     */
54
    public function getChange($property)
55
    {
56
        if (!$this->hasChanged($property)) {
57
            throw new OutOfBoundsException('This property doesn\'t exist or wasn\'t changed');
58
        }
59
60
        return $this->changes[$property];
61
    }
62
63
    /**
64
     * {@inheritDoc}
65
     *
66
     * @throws RuntimeException If the changeset was not computed yet
67
     */
68
    public function hasChanged($property)
69
    {
70
        if (null === $this->changes) {
71
            throw new RuntimeException('The changeset was not computed yet !');
72
        }
73
74
        return isset($this->changes[$property]);
75
    }
76
77
    /** {@inheritDoc} */
78
    public function offsetExists($offset)
79
    {
80
        return $this->hasChanged($offset);
81
    }
82
83
    /** {@inheritDoc} */
84
    public function offsetGet($offset)
85
    {
86
        return $this->getChange($offset);
87
    }
88
89
    /**
90
     * {@inheritDoc}
91
     *
92
     * @throws BadMethodCallException if a unset is tried on a snapshot property
93
     */
94
    public function offsetSet($offset, $value)
95
    {
96
        throw new BadMethodCallException('You cannot alter a changeset once it has been calculated');
97
    }
98
99
    /**
100
     * {@inheritDoc}
101
     *
102
     * @throws BadMethodCallException if a unset is tried on a snapshot property
103
     */
104
    public function offsetUnset($offset)
105
    {
106
        throw new BadMethodCallException('You cannot alter a changeset once it has been calculated');
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     *
112
     * @throws RuntimeException If the changeset was not computed yet
113
     */
114
    public function count()
115
    {
116
        if (null === $this->changes) {
117
            throw new RuntimeException('The changeset was not computed yet !');
118
        }
119
120
        return count($this->changes);
121
    }
122
123
    /**
124
     * {@inheritDoc}
125
     *
126
     * @throws RuntimeException If the changeset was not computed yet
127
     */
128
    public function getIterator()
129
    {
130
        if (null === $this->changes) {
131
            throw new RuntimeException('The changeset was not computed yet !');
132
        }
133
134
        return new ArrayIterator($this->changes);
135
    }
136
137
    /** {@inheritDoc} */
138
    public function compute(AbstractSnapshot $old, AbstractSnapshot $new)
139
    {
140
        if (null !== $this->changes) {
141
            return;
142
        }
143
144
        $this->changes = [];
145
146
        foreach (array_unique(array_merge(array_keys($old->getComparableData()), array_keys($new->getComparableData()))) as $key) {
147
            $result = $this->computeEntry($old, $new, $key);
148
149
            if (null !== $result) {
150
                $this->changes[$key] = $result;
151
            }
152
        }
153
154
        /*
155
         * Collection tricky case : each changes should not be represented by
156
         * its primary key, but by a numeric key. Because it is a collection, duh.
157
         */
158
        if ($old instanceof CollectionSnapshot && $new instanceof CollectionSnapshot) {
159
            $this->changes = array_values($this->changes);
160
        }
161
    }
162
163
    /**
164
     * Calculate the difference between two snapshots for a given key
165
     *
166
     * @param mixed $key Key to compare
167
     *
168
     * @return AbstractChange|null a change if a change was detected, null otherwise
0 ignored issues
show
Documentation introduced by
Should the return type not be Addition|Removal|Modification|Set|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
169
     * @internal
170
     */
171
    private function computeEntry(AbstractSnapshot $old, AbstractSnapshot $new, $key)
172
    {
173
        if (!isset($old[$key])) {
174
            return new Addition($this->getRawData($new[$key]));
175
        }
176
177
        if (!isset($new[$key])) {
178
            return new Removal($this->getRawData($old[$key]));
179
        }
180
181
        $values = ['old' => $this->getRawData($old[$key]),
182
                   'new' => $this->getRawData($new[$key])];
183
184
        switch (true) {
185
            // type verification
186
            case gettype($old[$key]) !== gettype($new[$key]):
187
                return new Modification($values['old'], $values['new']);
188
189
            // could we compare two snapshots ?
190
            case $old[$key] instanceof AbstractSnapshot:
191
                if (!$new[$key] instanceof AbstractSnapshot) {
192
                    return new Modification($values['old'], $values['new']);
193
                }
194
195
                if (!$old[$key]->isComparable($new[$key])) {
196
                    return new Modification($values['old'], $values['new']);
197
                }
198
199
                $set = new static;
200
                $set->compute($old[$key], $new[$key]);
201
202
                if (0 < count($set)) {
203
                    return $set;
204
                }
205
206
                return null;
207
208
            // unknown type : compare raw data
209
            case $values['old'] !== $values['new']:
210
                return new Modification($values['old'], $values['new']);
211
        // PHPUnit coverage wtf start
212
        // @codeCoverageIgnoreStart
213
        }
214
        // @codeCoverageIgnoreEnd
215
        // PHPUnit coverage wtf end
216
    }
217
218
    /**
219
     * Extracts the raw data for a given value
220
     *
221
     * @param mixed $value Value to extract
222
     * @return mixed value extracted
223
     */
224
    private function getRawData($value)
225
    {
226
        if ($value instanceof AbstractSnapshot) {
227
            return $value->getRawData();
228
        }
229
230
        return $value;
231
    }
232
}
233
234