Completed
Push — master ( f6db52...9cdeb8 )
by Dmitry
14:08
created

Aggregator::getLeafs()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
rs 9.2
cc 4
eloc 8
nc 4
nop 1
1
<?php
2
3
namespace Tarantool\Mapper\Plugin\Temporal;
4
5
use Tarantool\Mapper\Entity;
6
use Tarantool\Mapper\Plugin\Temporal;
7
8
class Aggregator
9
{
10
    private $temporal;
11
12
    public function __construct(Temporal $temporal)
13
    {
14
        $this->temporal = $temporal;
15
    }
16
17
    public function getLeafs($link)
18
    {
19
        if ($link->timestamp) {
20
            return [$link];
21
        }
22
23
        $leafs = [];
24
        foreach ($this->temporal->getMapper()->find('_temporal_link', ['parent' => $link->id]) as $child) {
25
            foreach ($this->getLeafs($child) as $leaf) {
26
                $leafs[] = $leaf;
27
            }
28
        }
29
        return $leafs;
30
    }
31
32
    public function updateReferenceState($entity, $id)
33
    {
34
        $mapper = $this->temporal->getMapper();
35
36
        $params = [
37
            'entity' => $this->temporal->entityNameToId($entity),
38
            'id'     => $id,
39
        ];
40
41
        // collect changes
42
43
        $states = [];
44
        $changeaxis = [];
45
        foreach ($mapper->find('_temporal_reference', $params) as $i => $reference) {
46
            if (property_exists($reference, 'idle') && $reference->idle) {
47
                continue;
48
            }
49
            if (!array_key_exists($reference->target, $changeaxis)) {
50
                $changeaxis[$reference->target] = [];
51
            }
52
            $changeaxis[$reference->target][] = get_object_vars($reference);
53
        }
54
55
        // calculate states
56
57
        $timeaxis = [];
0 ignored issues
show
Unused Code introduced by
$timeaxis is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
58
        foreach ($changeaxis as $target => $references) {
59
            $targetStates = [];
60
            foreach ($references as $reference) {
61
                foreach (['begin', 'end'] as $field) {
62
                    if (!array_key_exists($reference[$field], $targetStates)) {
63
                        $targetStates[$reference[$field]] = [
64
                            'begin'  => $reference[$field],
65
                            'end'    => $reference[$field],
66
                        ];
67
                    }
68
                }
69
            }
70
            ksort($targetStates);
71
72
            $nextSliceId = null;
73
            foreach (array_reverse(array_keys($targetStates)) as $timestamp) {
74
                if ($nextSliceId) {
75
                    $targetStates[$timestamp]['end'] = $nextSliceId;
76
                } else {
77
                    $targetStates[$timestamp]['end'] = 0;
78
                }
79
                $nextSliceId = $timestamp;
80
            }
81
82
            foreach ($targetStates as $state) {
83
                foreach ($changeaxis[$target] as $reference) {
84
                    if ($reference['begin'] > $state['begin']) {
85
                        // future reference
86
                        continue;
87
                    }
88
                    if ($reference['end'] && ($reference['end'] < $state['end'] || !$state['end'])) {
89
                        // complete reference
90
                        continue;
91
                    }
92
                    $state['targetId'] = $reference['targetId'];
93
                }
94
                if (array_key_exists('targetId', $state)) {
95
                    $states[] = array_merge($state, $params, ['target' => $target]);
96
                }
97
            }
98
        }
99
100
        // merge reference states
101
102
        $clean = false;
103
        while (!$clean) {
104
            $clean = true;
105
            foreach ($states as $i => $state) {
106
                if (array_key_exists($i+1, $states)) {
107
                    $next = $states[$i+1];
108
                    if ($state['target'] == $next['target'] && $state['targetId'] == $next['targetId']) {
109
                        $states[$i]['end'] = $next['end'];
110
                        unset($states[$i+1]);
111
                        $states = array_values($states);
112
                        $clean = false;
113
                        break;
114
                    }
115
                }
116
            }
117
        }
118
119
        // update states in database
120
121
        $affected = [];
122
        foreach ($mapper->find('_temporal_reference_state', $params) as $state) {
123
            $mapper->remove($state);
124
        }
125
        foreach ($states as $state) {
126
            if (!in_array([$state['target'], $state['targetId']], $affected)) {
127
                $affected[] = [$state['target'], $state['targetId']];
128
            }
129
            $mapper->create('_temporal_reference_state', $state);
130
        }
131
132
133
        // update reference aggregation for affected targets
134
        foreach ($affected as [$entity, $entityId]) {
135
            // collect changes
136
            $changeaxis = $mapper->find('_temporal_reference_state', [
137
                'target' => $entity,
138
                'targetId' => $entityId,
0 ignored issues
show
Bug introduced by
The variable $entityId does not exist. Did you mean $entity?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
139
                'entity' => $params['entity'],
140
            ]);
141
            $changeaxis = array_map('get_object_vars', $changeaxis);
142
            // generate states
143
            $targetStates = [];
144
            foreach ($changeaxis as $state) {
145
                foreach (['begin', 'end'] as $field) {
146
                    if (!array_key_exists($state[$field], $targetStates)) {
147
                        $targetStates[$state[$field]] = [
148
                            'begin' => $state[$field],
149
                            'end'   => $state[$field],
150
                            'data'  => [],
151
                        ];
152
                    }
153
                }
154
            }
155
            ksort($targetStates);
156
            $nextSliceId = null;
157
            foreach (array_reverse(array_keys($targetStates)) as $timestamp) {
158
                if ($nextSliceId) {
159
                    $targetStates[$timestamp]['end'] = $nextSliceId;
160
                } else {
161
                    $targetStates[$timestamp]['end'] = 0;
162
                }
163
                $nextSliceId = $timestamp;
164
            }
165
166
            $aggregateParams = [
167
                'entity' => $entity,
168
                'id' => $entityId,
0 ignored issues
show
Bug introduced by
The variable $entityId does not exist. Did you mean $entity?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
169
                'source' => $params['entity']
170
            ];
171
172
            // calculate aggregates
173
            $aggregates = [];
174
            foreach ($targetStates as $state) {
175
                foreach ($changeaxis as $reference) {
176
                    if ($reference['begin'] > $state['begin']) {
177
                        // future reference
178
                        continue;
179
                    }
180
                    if ($reference['end'] && ($reference['end'] < $state['end'] || !$state['end'])) {
181
                        // complete reference
182
                        continue;
183
                    }
184
                    if (!in_array($reference['id'], $state['data'])) {
185
                        $state['data'][] = $reference['id'];
186
                    }
187
                }
188
                if (count($state['data'])) {
189
                    $aggregates[] = array_merge($state, $aggregateParams);
190
                }
191
            }
192
193
            // sync aggregates
194
            foreach ($mapper->find('_temporal_reference_aggregate', $aggregateParams) as $aggregate) {
195
                $mapper->remove($aggregate);
196
            }
197
198
            foreach ($aggregates as $aggregate) {
199
                $mapper->create('_temporal_reference_aggregate', $aggregate);
200
            }
201
        }
202
    }
203
204
205
    public function updateLinkAggregation(Entity $node)
206
    {
207
        $todo = [
208
            $this->temporal->entityIdToName($node->entity) => $node->entityId,
0 ignored issues
show
Bug introduced by
The property entity does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Bug introduced by
The property entityId does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
209
        ];
210
211
        $current = $node;
212
        while ($current->parent) {
213
            $current = $this->temporal->getMapper()->findOne('_temporal_link', ['id' => $current->parent]);
214
            $todo[$this->temporal->entityIdToName($current->entity)] = $current->entityId;
215
        }
216
217
        foreach ($todo as $entity => $id) {
218
            $spaceId = $this->temporal->entityNameToId($entity);
219
            $source = $this->temporal->getMapper()->find('_temporal_link', [
220
                'entity'   => $spaceId,
221
                'entityId' => $id,
222
            ]);
223
224
            $leafs = [];
225
            foreach ($source as $node) {
226
                foreach ($this->getLeafs($node) as $detail) {
227
                    $leafs[] = $detail;
228
                }
229
            }
230
231
            $changeaxis = [];
232
233
            foreach ($leafs as $leaf) {
234
                $current = $leaf;
235
                $ref = [];
236
237
                if (property_exists($leaf, 'idle') && $leaf->idle) {
238
                    continue;
239
                }
240
241
                while ($current) {
242
                    if ($current->entity != $spaceId) {
243
                        $ref[$current->entity] = $current->entityId;
244
                    }
245
                    if ($current->parent) {
246
                        $current = $this->temporal->getMapper()->findOne('_temporal_link', $current->parent);
247
                    } else {
248
                        $current = null;
249
                    }
250
                }
251
252
                $data = [$ref];
253
                if (property_exists($leaf, 'data') && $leaf->data) {
254
                    $data[] = $leaf->data;
255
                }
256
257
                if (!array_key_exists($leaf->timestamp, $changeaxis)) {
258
                    $changeaxis[$leaf->timestamp] = [];
259
                }
260
                $changeaxis[$leaf->timestamp][] = [
261
                    'begin' => $leaf->begin,
262
                    'end' => $leaf->end,
263
                    'data' => $data
264
                ];
265
            }
266
267
            $params = [
268
                'entity' => $spaceId,
269
                'id'     => $id,
270
            ];
271
272
            $this->updateAggregation('link', $params, $changeaxis);
273
        }
274
    }
275
276
    public function updateOverrideAggregation($entity, $id)
277
    {
278
        $params = [
279
            'entity' => $this->temporal->entityNameToId($entity),
280
            'id'     => $id,
281
        ];
282
283
        $changeaxis = [];
284
285
        foreach ($this->temporal->getMapper()->find('_temporal_override', $params) as $i => $override) {
286
            if (property_exists($override, 'idle') && $override->idle) {
287
                continue;
288
            }
289
            if (!array_key_exists($override->begin, $changeaxis)) {
290
                $changeaxis[$override->begin] = [];
291
            }
292
            $changeaxis[$override->begin][] = [
293
                'begin' => $override->begin,
294
                'end' => $override->end,
295
                'data' => $override->data,
296
            ];
297
        }
298
299
        $this->updateAggregation('override', $params, $changeaxis);
300
    }
301
302
    public function updateAggregation($type, $params, $changeaxis)
303
    {
304
        $isLink = $type === 'link';
305
        $space = $isLink ? '_temporal_link_aggregate' : '_temporal_override_aggregate';
306
307
        $timeaxis = [];
308
        foreach ($changeaxis as $timestamp => $changes) {
309
            foreach ($changes as $change) {
310
                foreach (['begin', 'end'] as $field) {
311
                    if (!array_key_exists($change[$field], $timeaxis)) {
312
                        $timeaxis[$change[$field]] = [
313
                            'begin' => $change[$field],
314
                            'end'   => $change[$field],
315
                            'data'  => [],
316
                        ];
317
                    }
318
                }
319
            }
320
        }
321
322
        ksort($changeaxis);
323
        ksort($timeaxis);
324
325
        $nextSliceId = null;
326
        foreach (array_reverse(array_keys($timeaxis)) as $timestamp) {
327
            if ($nextSliceId) {
328
                $timeaxis[$timestamp]['end'] = $nextSliceId;
329
            } else {
330
                $timeaxis[$timestamp]['end'] = 0;
331
            }
332
            $nextSliceId = $timestamp;
333
        }
334
335
        foreach ($this->temporal->getMapper()->find($space, $params) as $state) {
336
            $this->temporal->getMapper()->remove($state);
337
        }
338
339
        $states = [];
340
        foreach ($timeaxis as $state) {
341
            foreach ($changeaxis as $changes) {
342
                foreach ($changes as $change) {
343
                    if ($change['begin'] > $state['begin']) {
344
                        // future override
345
                        continue;
346
                    }
347
                    if ($change['end'] && ($change['end'] < $state['end'] || !$state['end'])) {
348
                        // complete override
349
                        continue;
350
                    }
351
                    if ($isLink) {
352
                        $state['data'][] = $change['data'];
353
                    } else {
354
                        $state['data'] = array_merge($state['data'], $change['data']);
355
                    }
356
                }
357
            }
358
            if (count($state['data'])) {
359
                $states[] = array_merge($state, $params);
360
            }
361
        }
362
363
        // merge states
364
        $clean = $isLink;
365
        while (!$clean) {
366
            $clean = true;
367
            foreach ($states as $i => $state) {
368
                if (array_key_exists($i+1, $states)) {
369
                    $next = $states[$i+1];
370
                    if (json_encode($state['data']) == json_encode($next['data'])) {
371
                        $states[$i]['end'] = $next['end'];
372
                        unset($states[$i+1]);
373
                        $states = array_values($states);
374
                        $clean = false;
375
                        break;
376
                    }
377
                }
378
            }
379
        }
380
381
        foreach ($states as $state) {
382
            $this->temporal->getMapper()->create($space, $state);
383
        }
384
    }
385
}
386