Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.72% covered (warning)
87.72%
150 / 171
88.24% covered (warning)
88.24%
15 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Exchange
87.72% covered (warning)
87.72%
150 / 171
88.24% covered (warning)
88.24%
15 / 17
105.00
0.00% covered (danger)
0.00%
0 / 1
 getDefaults
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setPeriod
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addDictionaryEntry
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 addDataSet
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 withLessData
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getPositionByName
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 withCalculatedTotals
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 withMaxByHour
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
8
 withRequestsSum
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 withAverage
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
 withUncapturedRequestRowSortedLast
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
9
 withMaxAndAverageFromWaitingTime
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
10
 getCalculatedTotals
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 toHashed
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getHashData
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
 toGrouped
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getGroupedHashSet
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3namespace BO\Zmsentities;
4
5/**
6 * @SuppressWarnings(Complexity)
7 * @SuppressWarnings(PublicMethod)
8 *
9 */
10class Exchange extends Schema\Entity
11{
12    public const PRIMARY = 'firstDay';
13
14    /**
15     * Statistik CASE labels (must match warehouse SQL in ExchangeRequest* queries).
16     */
17    public const REQUEST_STAT_NAME_UNCATEGORIZED = 'uncategorized';
18
19    public const REQUEST_STAT_NAME_NONEXISTENT = 'nonexistent';
20
21    public static $schema = "exchange.json";
22
23    #[\Override]
24    public function getDefaults()
25    {
26        return [
27            'firstDay' => new Day(),
28            'lastDay' => new Day(),
29            'period' => 'day',
30            'dictionary' => [ ],
31            'data' => [ ]
32        ];
33    }
34
35    public function setPeriod(\DateTimeInterface $firstDay, \DateTimeInterface $lastDay, $period = 'day')
36    {
37        $this->firstDay = (new Day())->setDateTime($firstDay);
38        $this->lastDay = (new Day())->setDateTime($lastDay);
39        $this->period = $period;
40        return $this;
41    }
42
43    public function addDictionaryEntry($variable, $type = 'string', $description = '', $reference = '')
44    {
45        $position = count($this['dictionary']);
46        $this['dictionary'][$position] = [
47            'position' => $position,
48            'variable' => $variable,
49            'type' => $type,
50            'description' => $description,
51            'reference' => $reference
52        ];
53        return $this;
54    }
55
56    public function addDataSet($values)
57    {
58        if (!is_array($values) && !$values instanceof \Traversable) {
59            throw new \Exception("Values have to be of type array");
60        }
61        if (count($this->dictionary) != count($values)) {
62            throw new \Exception("Mismatching dictionary settings for values (count mismatch)");
63        }
64        $this->data[] = $values;
65    }
66
67    #[\Override]
68    public function withLessData()
69    {
70        $entity = clone $this;
71        if (isset($entity['firstDay'])) {
72            unset($entity['firstDay']);
73        }
74        if (isset($entity['lastDay'])) {
75            unset($entity['lastDay']);
76        }
77        if (isset($entity['period'])) {
78            unset($entity['period']);
79        }
80        return $entity;
81    }
82
83    public function getPositionByName($name)
84    {
85        if (isset($this->dictionary)) {
86            foreach ($this->dictionary as $entry) {
87                if (isset($entry['variable']) && $entry['variable'] == $name) {
88                    return $entry['position'];
89                }
90            }
91        }
92        return false;
93    }
94
95    public function withCalculatedTotals(array $keysToCalculate = ['count'], $dateName = 'name')
96    {
97        $entity = clone $this;
98        $namePosition = $this->getPositionByName($dateName);
99        if ($namePosition) {
100            $totals = array_fill(0, count($entity->data[0]), 0);
101            $totals[$namePosition] = 'totals';
102            foreach ($keysToCalculate as $name) {
103                $calculatePosition = $this->getPositionByName($name);
104                foreach ($this->data as $item) {
105                    foreach ($item as $position => $data) {
106                        if (is_numeric($data) && $calculatePosition == $position) {
107                            $totals[$position] += $data;
108                        }
109                    }
110                }
111            }
112            $entity->addDataSet($totals);
113        }
114        return $entity;
115    }
116
117    public function withMaxByHour(array $keysToCalculate = ['count'])
118    {
119        $entity = clone $this;
120        $maxima = [];
121        foreach ($entity->data as $dateItems) {
122            foreach ($dateItems as $hour => $hourItems) {
123                foreach ($hourItems as $key => $value) {
124                    if (is_numeric($value) && in_array($key, $keysToCalculate)) {
125                        $maxima[$hour][$key] = (
126                          isset($maxima[$hour][$key]) && $maxima[$hour][$key] > $value
127                        ) ? $maxima[$hour][$key] : $value;
128                    }
129                }
130            }
131            $entity->data['max'] = $maxima;
132        }
133        return $entity;
134    }
135
136    public function withRequestsSum($keysToCalculate = ['requestscount'])
137    {
138        $entity = clone $this;
139        $sum = [];
140        foreach ($entity->data as $name => $entry) {
141            $sum[$name] = 0;
142            foreach ($entry as $dateItem) {
143                foreach ($dateItem as $key => $value) {
144                    if (is_numeric($value) && in_array($key, $keysToCalculate)) {
145                        $sum[$name] += $value;
146                    }
147                }
148            }
149        }
150        $entity->data['sum'] = $sum;
151        return $entity;
152    }
153
154    public function withAverage($keyToCalculate)
155    {
156        $entity = clone $this;
157        $average = [];
158
159        foreach ($entity->data as $name => $entry) {
160            if (!is_array($entry) && !($entry instanceof \Traversable)) {
161                // Skip or handle non-iterable $entry appropriately.
162                continue;
163            }
164
165            $average[$name . '_sum'] = 0;
166            $average[$name . '_count'] = 0;
167
168            foreach ($entry as $dateItem) {
169                if (!is_array($dateItem) && !($dateItem instanceof \Traversable)) {
170                    // Skip or handle non-iterable $dateItem appropriately.
171                    continue;
172                }
173
174                foreach ($dateItem as $key => $value) {
175                    if (!is_numeric($value) || $key !== $keyToCalculate) {
176                        // Skip non-numeric values or when the key doesn't match.
177                        continue;
178                    }
179
180                    $average[$name . '_sum'] += $value;
181                    $average[$name . '_count']++;
182                }
183            }
184
185            $average[$name] = $average[$name . '_count'] > 0
186                ? round($average[$name . '_sum'] / $average[$name . '_count'], 2)
187                : null;
188        }
189
190        $entity->data['average_' . $keyToCalculate] = $average;
191        return $entity;
192    }
193
194    /**
195     * Sort service rows alphabetically; place synthetic stat rows last
196     * (before aggregated keys sum / average_*).
197     */
198    public function withUncapturedRequestRowSortedLast(): self
199    {
200        $entity = clone $this;
201        if (!is_array($entity->data)) {
202            return $entity;
203        }
204
205        $reserved = ['sum', 'average_processingtime'];
206        $tailStatNames = [
207            self::REQUEST_STAT_NAME_UNCATEGORIZED,
208            self::REQUEST_STAT_NAME_NONEXISTENT,
209        ];
210        $serviceRows = [];
211        foreach ($entity->data as $key => $value) {
212            if (in_array($key, $reserved, true) || in_array($key, $tailStatNames, true)) {
213                continue;
214            }
215            $serviceRows[$key] = $value;
216        }
217
218        uksort($serviceRows, static function ($a, $b) {
219            return strnatcasecmp((string) $a, (string) $b);
220        });
221
222        $ordered = $serviceRows;
223        foreach ($tailStatNames as $tailKey) {
224            if (array_key_exists($tailKey, $entity->data)) {
225                $ordered[$tailKey] = $entity->data[$tailKey];
226            }
227        }
228        foreach ($reserved as $key) {
229            if (array_key_exists($key, $entity->data)) {
230                $ordered[$key] = $entity->data[$key];
231            }
232        }
233        $entity->data = $ordered;
234        return $entity;
235    }
236
237    public function withMaxAndAverageFromWaitingTime()
238    {
239        $entity = clone $this;
240        foreach ($entity->data as $date => $dateItems) {
241            $maxima = 0;
242            $total = 0;
243            $count = 0;
244            foreach ($dateItems as $hourItems) {
245                foreach ($hourItems as $key => $value) {
246                    if (is_numeric($value) && 'waitingtime' == $key && 0 < $value) {
247                        $total += $value;
248                        $count += 1;
249                        $maxima = ($maxima > $value) ? $maxima : $value;
250                    }
251                }
252            }
253            $entity->data[$date]['max'] = $maxima;
254            $entity->data[$date]['average'] = (! $total || ! $count) ? 0 : floor($total / $count);
255        }
256        return $entity;
257    }
258
259    public function getCalculatedTotals()
260    {
261        foreach (array_reverse($this->data) as $item) {
262            foreach ($item as $data) {
263                if ($data == 'totals') {
264                    return $item;
265                }
266            }
267        }
268        return null;
269    }
270
271    public function toHashed(array $hashfields = [])
272    {
273        $entity = clone $this;
274        $entity->data = $this->getHashData($hashfields);
275        unset($entity->dictionary);
276        return $entity;
277    }
278
279    public function getHashData(array $hashfields = [], $first = false)
280    {
281        $hash = [];
282        foreach ($this->dictionary as $entry) {
283            foreach ($this->data as $key => $item) {
284                foreach ($item as $position => $data) {
285                    if ($entry['position'] == $position) {
286                        if (count($hashfields) && in_array($entry['variable'], $hashfields)) {
287                            $hash[$key][$entry['variable']] = $data;
288                        } else {
289                            $hash[$key][$entry['variable']] = $data;
290                        }
291                    }
292                }
293            }
294        }
295        return ($first) ? reset($hash) : $hash;
296    }
297
298    public function toGrouped(array $fields, array $hashfields)
299    {
300        $entity = clone $this;
301        $entity->data = $this->getGroupedHashSet($fields, $hashfields);
302        unset($entity->dictionary);
303        return $entity;
304    }
305
306    public function getGroupedHashSet(array $fields, array $hashfields)
307    {
308        $list = [];
309        if (count($fields)) {
310            $field = array_shift($fields);
311            $fieldposition = $this->getPositionByName($field);
312
313            $requestscountPosition = $this->getPositionByName('requestscount');
314
315
316            foreach ($this->data as $element) {
317                if (isset($element[$fieldposition])) {
318                    if (!isset($list[$element[$fieldposition]])) {
319                        $list[$element[$fieldposition]] = clone $this;
320                        $list[$element[$fieldposition]]->data = [];
321                    }
322                    if ($requestscountPosition !== false && isset($element[$requestscountPosition])) {
323                        $element[$requestscountPosition] = (int) $element[$requestscountPosition];
324                    }
325
326                    $list[$element[$fieldposition]]->data[] = $element;
327                }
328            }
329
330            foreach ($list as $key => $row) {
331                if ($row instanceof Exchange) {
332                    $list[$key] = $row->getGroupedHashSet($fields, $hashfields);
333                }
334            }
335        } else {
336            return $this->getHashData($hashfields, true);
337        }
338        return $list;
339    }
340}