RecordableHistory   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 261
Duplicated Lines 31.42 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 86.14%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 4
dl 82
loc 261
ccs 87
cts 101
cp 0.8614
rs 8.48
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A fromInitialAndFinalAndDiffs() 0 7 1
A __construct() 0 4 1
A getCompactionRate() 0 19 5
A getInitialRecord() 12 12 4
A getFinalRecord() 11 11 4
A getRecords() 0 4 1
A getDiffs() 0 8 2
B calculateDiffs() 0 32 6
A calculateDiff() 0 12 1
B diffRecursiveFromBeforeToAfter() 29 29 9
B diffRecursiveFromAfterToBefore() 30 30 9
B convertToString() 0 25 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RecordableHistory 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 RecordableHistory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PeekAndPoke\Component\Slumber\Data\Addon\Journal\DomainModel;
4
5
/**
6
 * @author Karsten J. Gerber <[email protected]>
7
 */
8
class RecordableHistory
9
{
10
    /** @var Record[] */
11
    private $records;
12
    /** @var RecordDiff[] */
13
    private $diffs;
14
15
    /**
16
     * @param Record       $initial
17
     * @param Record       $final
18
     * @param RecordDiff[] $diffs
19
     *
20
     * @return RecordableHistory
21
     */
22 2
    public static function fromInitialAndFinalAndDiffs(Record $initial, Record $final, $diffs)
23
    {
24 2
        $ret        = new RecordableHistory([$initial, $final]);
25 2
        $ret->diffs = $diffs;
26
27 2
        return $ret;
28
    }
29
30
    /**
31
     * @param Record[] $records
32
     */
33 3
    public function __construct($records)
34
    {
35 3
        $this->records = $records;
36 3
    }
37
38
    /**
39
     * @return float
40
     */
41
    public function getCompactionRate()
42
    {
43
        $compacted    = 0;
44
        $notCompacted = 0;
45
46
        foreach ($this->records as $record) {
47
            if ($record->getIsCompacted() === false || $record->getCompactedHistory() === null) {
48
                $notCompacted++;
49
            } else {
50
                $compacted += \count($record->getCompactedHistory()->getDiffs());
51
            }
52
        }
53
54
        if ($compacted + $notCompacted === 0) {
55
            return 0;
56
        }
57
58
        return $compacted / ($compacted + $notCompacted);
59
    }
60
61
    /**
62
     * @return Record
63
     */
64 3 View Code Duplication
    public function getInitialRecord()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
65
    {
66 3
        $record = \count($this->records) > 0 ? $this->records[0] : new NullRecord();
67
68 3
        if ($record->getIsCompacted() &&
69 3
            $record->getCompactedHistory() !== null
70
        ) {
71 2
            return $record->getCompactedHistory()->getInitialRecord();
72
        }
73
74 3
        return $record;
75
    }
76
77
    /**
78
     * @return Record
79
     */
80 3 View Code Duplication
    public function getFinalRecord()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
    {
82 3
        $record = \count($this->records) > 0 ? $this->records[\count($this->records) - 1] : new NullRecord();
83
84 3
        if ($record->getIsCompacted() &&
85 3
            $record->getCompactedHistory() !== null) {
86 2
            return $record->getCompactedHistory()->getFinalRecord();
87
        }
88
89 3
        return $record;
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 3
    public function getRecords()
96
    {
97 3
        return $this->records;
98
    }
99
100
    /**
101
     * @return RecordDiff[]
102
     */
103 3
    public function getDiffs()
104
    {
105 3
        if ($this->diffs === null) {
106 3
            $this->calculateDiffs();
107
        }
108
109 3
        return $this->diffs;
110
    }
111
112 3
    protected function calculateDiffs()
113
    {
114 3
        $this->diffs = [];
115
116 3
        $previous = new NullRecord();
117
118 3
        foreach ($this->records as $current) {
119
120 3
            if ($current->getIsCompacted() === false) {
121 3
                $diff = $this->calculateDiff($previous, $current);
122
123 3
                if ($diff->getChangesCount() > 0) {
124
125 3
                    $this->diffs[] = $diff;
126
                }
127
128 3
                $previous = $current;
129
130
            } else {
131
132 2
                $history = $current->getCompactedHistory();
133
134 2
                if ($history) {
135 2
                    foreach ($history->getDiffs() as $diff) {
136 2
                        $this->diffs[] = $diff;
137
                    }
138
                }
139
140 3
                $previous = $history->getFinalRecord();
141
            }
142
        }
143 3
    }
144
145
    /**
146
     * @param Record $before
147
     * @param Record $after
148
     *
149
     * @return RecordDiff
150
     */
151 3
    protected function calculateDiff(Record $before, Record $after)
152
    {
153 3
        $diff = new RecordDiff($after->getChangeDate(), $after->getChangedBy());
154
155 3
        $beforeData = (array) $before->getRecordData();
156 3
        $afterData  = (array) $after->getRecordData();
157
158 3
        $this->diffRecursiveFromBeforeToAfter($diff, '', $beforeData, $afterData);
159 3
        $this->diffRecursiveFromAfterToBefore($diff, '', $beforeData, $afterData);
160
161 3
        return $diff;
162
    }
163
164
    /**
165
     * @param RecordDiff $diff
166
     * @param string     $pathInArray
167
     * @param mixed[]    $before
168
     * @param mixed[]    $after
169
     */
170 3 View Code Duplication
    private function diffRecursiveFromBeforeToAfter(RecordDiff $diff, $pathInArray, $before, $after)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
    {
172 3
        if (! \is_array($before) && ! $before instanceof \Traversable) {
173
174 3
            $beforeValue = $this->convertToString($before);
175 3
            $afterValue  = $this->convertToString($after);
176
177 3
            if ($beforeValue !== $afterValue) {
178 3
                $diff->addChange(new RecordDiffEntry($pathInArray, $beforeValue, $afterValue));
179
            }
180
        } else {
181
182 3
            foreach ($before as $key => $beforeValue) {
183
184 3
                $afterValue = null;
185
186 3
                if ((\is_array($after) || $after instanceof \ArrayAccess) && isset($after[$key])) {
187 3
                    $afterValue = $after[$key];
188
                }
189
190 3
                $this->diffRecursiveFromBeforeToAfter(
191 3
                    $diff,
192 3
                    empty($pathInArray) ? $key : $pathInArray . '.' . $key,
193 3
                    $beforeValue,
194 3
                    $afterValue
195
                );
196
            }
197
        }
198 3
    }
199
200
    /**
201
     * @param RecordDiff $diff
202
     * @param string     $pathInArray
203
     * @param array      $before
204
     * @param array      $after
205
     */
206 3 View Code Duplication
    private function diffRecursiveFromAfterToBefore(RecordDiff $diff, $pathInArray, $before, $after)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
    {
208 3
        if (! \is_array($after) && ! $after instanceof \Traversable) {
209
210 3
            $beforeValue = $this->convertToString($before);
211 3
            $afterValue  = $this->convertToString($after);
212
213 3
            if ($beforeValue !== $afterValue) {
214 3
                $diff->addChange(new RecordDiffEntry($pathInArray, $beforeValue, $afterValue));
215
            }
216
        } else {
217
218
            // first compare from before to after
219 3
            foreach ($after as $key => $afterValue) {
220
221 3
                $beforeValue = null;
222
223 3
                if ((\is_array($before) || $before instanceof \ArrayAccess) && isset($before[$key])) {
224 3
                    $beforeValue = $before[$key];
225
                }
226
227 3
                $this->diffRecursiveFromAfterToBefore(
228 3
                    $diff,
229 3
                    empty($pathInArray) ? $key : $pathInArray . '.' . $key,
230 3
                    $beforeValue,
231 3
                    $afterValue
232
                );
233
            }
234
        }
235 3
    }
236
237
    /**
238
     * @param        $value
239
     * @param string $fallback
240
     *
241
     * @return string
242
     */
243 3
    private function convertToString($value, $fallback = 'N/A')
244
    {
245
        try {
246 3
            if ($value === null) {
247 3
                return '';
248
            }
249
250 3
            if (is_scalar($value)) {
251 3
                return (string) $value;
252
            }
253
254 3
            if (\is_array($value)) {
255
                return json_encode($value, JSON_PRETTY_PRINT);
256
            }
257
258 3
            if ($value instanceof \DateTime) {
259
                return $value->format('c');
260
            }
261
262 3
            return (string) $fallback;
263
264
        } catch (\Exception $e) {
265
            return $fallback;
266
        }
267
    }
268
}
269