Completed
Push — master ( 981189...b6c8f8 )
by Dmitry
01:53
created

Temporal::updateAggregation()   F

Complexity

Conditions 23
Paths 7800

Size

Total Lines 83
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 83
rs 2.148
c 0
b 0
f 0
cc 23
eloc 51
nc 7800
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Tarantool\Mapper\Plugin;
4
5
use Carbon\Carbon;
6
use Exception;
7
use Tarantool\Mapper\Mapper;
8
use Tarantool\Mapper\Plugin;
9
10
class Temporal extends Plugin
11
{
12
    private $actor;
13
    private $timestamps = [];
14
15
    public function getLinksLog($entity, $entityId, $filter = [])
16
    {
17
        $this->initSchema('link');
18
19
        $entity = $this->entityNameToId($entity);
20
21
        $nodes = $this->mapper->find('_temporal_link', [
22
            'entity' => $entity,
23
            'entityId' => $entityId,
24
        ]);
25
26
        $links = [];
27
28
        foreach ($nodes as $node) {
29
            foreach ($this->getLeafs($node) as $leaf) {
30
                $entityName = $this->entityIdToName($leaf->entity);
31
                $link = [
32
                    $entityName => $leaf->entityId,
33
                    'begin'     => $leaf->begin,
34
                    'end'       => $leaf->end,
35
                    'timestamp' => $leaf->timestamp,
36
                    'actor'     => $leaf->actor,
37
                ];
38
39
                $current = $leaf;
40
                while ($current->parent) {
41
                    $current = $this->mapper->findOne('_temporal_link', $current->parent);
42
                    $entityName = $this->entityIdToName($current->entity);
43
                    $link[$entityName] = $current->entityId;
44
                }
45
46
                if (count($filter)) {
47
                    foreach ($filter as $required) {
48
                        if (!array_key_exists($required, $link)) {
49
                            continue 2;
50
                        }
51
                    }
52
                }
53
                $links[] = $link;
54
            }
55
        }
56
57
        return $links;
58
    }
59
60
    public function getLinks($entity, $id, $date)
61
    {
62
        $this->initSchema('link');
63
64
        $links = $this->getData($entity, $id, $date, '_temporal_link_aggregate');
65
        foreach ($links as $i => $source) {
66
            $link = array_key_exists(1, $source) ? ['data' => $source[1]] : [];
67
            foreach ($source[0] as $spaceId => $entityId) {
68
                $spaceName = $this->mapper->findOne('_temporal_entity', $spaceId)->name;
0 ignored issues
show
Documentation introduced by
$spaceId is of type integer|string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
69
                $link[$spaceName] = $entityId;
70
            }
71
            $links[$i] = $link;
72
        }
73
        return $links;
74
    }
75
76
    public function getState($entity, $id, $date)
77
    {
78
        $this->initSchema('override');
79
80
        return $this->getData($entity, $id, $date, '_temporal_override_aggregate');
81
    }
82
83
    private function getData($entity, $id, $date, $space)
84
    {
85
        $entity = $this->entityNameToId($entity);
86
        $date = $this->getTimestamp($date);
87
88
        $rows = $this->mapper->getClient()->getSpace($space)
89
            ->select([$entity, $id, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
90
            ->getData();
91
92
        if (count($rows) && $rows[0][0] == $entity && $rows[0][1] == $id) {
93
            $state = $this->mapper->findOne($space, [
94
                'entity' => $entity,
95
                'id' => $id,
96
                'begin' => $rows[0][2]
97
            ]);
98
            if (!$state->end || $state->end >= $date) {
99
                return $state->data;
100
            }
101
        }
102
103
        return [];
104
    }
105
106
    public function getOverrides($entityName, $id)
107
    {
108
        return $this->mapper->find('_temporal_override', [
109
            'entity' => $this->entityNameToId($entityName),
110
            'id' => $id,
111
        ]);
112
    }
113
114
    public function override(array $override)
115
    {
116
        $override = $this->parseConfig($override);
117
118
        foreach ($override as $k => $v) {
119
            if (!in_array($k, ['entity', 'id', 'begin', 'end', 'data'])) {
120
                $override['entity'] = $k;
121
                $override['id'] = $v;
122
                unset($override[$k]);
123
            }
124
        }
125
126
        if (!array_key_exists('entity', $override)) {
127
            throw new Exception("no entity defined");
128
        }
129
130
        // set entity id
131
        $override['entity'] = $this->entityNameToId($override['entity']);
132
133
        $override['actor'] = $this->actor;
134
        $override['timestamp'] = Carbon::now()->timestamp;
135
136
        $this->initSchema('override');
137
        $this->mapper->create('_temporal_override', $override);
138
139
        $params = [
140
            'entity' => $override['entity'],
141
            'id'     => $override['id'],
142
        ];
143
144
        $changeaxis = [];
145
146
        foreach ($this->mapper->find('_temporal_override', $params) as $i => $override) {
147
            if (!array_key_exists($override->timestamp, $changeaxis)) {
148
                $changeaxis[$override->timestamp] = [];
149
            }
150
            $changeaxis[$override->timestamp][] = [
151
                'begin' => $override->begin,
152
                'end' => $override->end,
153
                'data' => $override->data,
154
            ];
155
        }
156
157
        $this->updateAggregation('override', $params, $changeaxis);
158
    }
159
160
    public function link(array $link)
161
    {
162
        $link = $this->parseConfig($link);
163
164
        $this->initSchema('link');
165
166
        ksort($link);
167
        $node = null;
168
        foreach ($link as $entity => $id) {
169
            if (in_array($entity, ['begin', 'end', 'data'])) {
170
                continue;
171
            }
172
            $spaceId = $this->entityNameToId($entity);
173
174
            $params = [
175
                'entity'   => $spaceId,
176
                'entityId' => $id,
177
                'parent'   => $node ? $node->id : 0,
178
            ];
179
            $node = $this->mapper->findOrCreate('_temporal_link', $params);
180
        }
181
182
        if (!$node || !$node->parent) {
183
            throw new Exception("Invalid link configuration");
184
        }
185
186
        $node->begin = $link['begin'];
187
        $node->end = $link['end'];
188
        $node->actor = $this->actor;
189
        $node->timestamp = Carbon::now()->timestamp;
190
        if (array_key_exists('data', $link)) {
191
            $node->data = $link['data'];
192
        }
193
194
        $node->save();
195
196
        foreach ($link as $entity => $id) {
197
            if (in_array($entity, ['begin', 'end', 'data'])) {
198
                continue;
199
            }
200
            $spaceId = $this->entityNameToId($entity);
201
            $source = $this->mapper->find('_temporal_link', [
202
                'entity'   => $spaceId,
203
                'entityId' => $id,
204
            ]);
205
206
            $leafs = [];
207
            foreach ($source as $node) {
208
                foreach ($this->getLeafs($node) as $detail) {
209
                    $leafs[] = $detail;
210
                }
211
            }
212
213
            $changeaxis = [];
214
215
            foreach ($leafs as $leaf) {
216
                $current = $leaf;
217
                $ref = [];
218
219
                while ($current) {
220
                    if ($current->entity != $spaceId) {
221
                        $ref[$current->entity] = $current->entityId;
222
                    }
223
                    if ($current->parent) {
224
                        $current = $this->mapper->findOne('_temporal_link', $current->parent);
225
                    } else {
226
                        $current = null;
227
                    }
228
                }
229
230
                $data = [$ref];
231
                if (property_exists($leaf, 'data') && $leaf->data) {
232
                    $data[] = $leaf->data;
233
                }
234
235
                if (!array_key_exists($leaf->timestamp, $changeaxis)) {
236
                    $changeaxis[$leaf->timestamp] = [];
237
                }
238
                $changeaxis[$leaf->timestamp][] = [
239
                    'begin' => $leaf->begin,
240
                    'end' => $leaf->end,
241
                    'data' => $data
242
                ];
243
            }
244
245
            $params = [
246
                'entity' => $spaceId,
247
                'id'     => $id,
248
            ];
249
250
            $this->updateAggregation('link', $params, $changeaxis);
251
        }
252
    }
253
254
    public function setActor($actor)
255
    {
256
        $this->actor = $actor;
257
        return $this;
258
    }
259
260
    private function getLeafs($link)
261
    {
262
        if ($link->timestamp) {
263
            return [$link];
264
        }
265
266
        $leafs = [];
267
        foreach ($this->mapper->find('_temporal_link', ['parent' => $link->id]) as $child) {
268
            foreach ($this->getLeafs($child) as $leaf) {
269
                $leafs[] = $leaf;
270
            }
271
        }
272
        return $leafs;
273
    }
274
275
    private function getTimestamp($string)
276
    {
277
        if (!array_key_exists($string, $this->timestamps)) {
278
            $this->timestamps[$string] = Carbon::parse($string)->timestamp;
279
        }
280
        return $this->timestamps[$string];
281
    }
282
283
    private function parseConfig(array $data)
284
    {
285
        if (!$this->actor) {
286
            throw new Exception("actor is undefined");
287
        }
288
289
        if (array_key_exists('actor', $data)) {
290
            throw new Exception("actor is defined");
291
        }
292
293
        if (array_key_exists('timestamp', $data)) {
294
            throw new Exception("timestamp is defined");
295
        }
296
297
        foreach (['begin', 'end'] as $field) {
298
            if (array_key_exists($field, $data)) {
299
                if (is_string($data[$field])) {
300
                    $data[$field] = $this->getTimestamp($data[$field]);
301
                }
302
            } else {
303
                $data[$field] = 0;
304
            }
305
        }
306
307
        return $data;
308
    }
309
310
    private function updateAggregation($type, $params, $changeaxis)
311
    {
312
        $isLink = $type === 'link';
313
        $space = $isLink ? '_temporal_link_aggregate' : '_temporal_override_aggregate';
314
315
        $timeaxis = [];
316
        foreach ($changeaxis as $timestamp => $changes) {
317
            foreach ($changes as $change) {
318
                foreach (['begin', 'end'] as $field) {
319
                    if (!array_key_exists($change[$field], $timeaxis)) {
320
                        $timeaxis[$change[$field]] = [
321
                            'begin' => $change[$field],
322
                            'end'   => $change[$field],
323
                            'data'  => [],
324
                        ];
325
                    }
326
                }
327
            }
328
        }
329
330
        ksort($changeaxis);
331
        ksort($timeaxis);
332
333
        $nextSliceId = null;
334
        foreach (array_reverse(array_keys($timeaxis)) as $timestamp) {
335
            if ($nextSliceId) {
336
                $timeaxis[$timestamp]['end'] = $nextSliceId;
337
            } else {
338
                $timeaxis[$timestamp]['end'] = 0;
339
            }
340
            $nextSliceId = $timestamp;
341
        }
342
343
        foreach ($this->mapper->find($space, $params) as $state) {
344
            $this->mapper->remove($state);
345
        }
346
347
        $states = [];
348
        foreach ($timeaxis as $state) {
349
            foreach ($changeaxis as $changes) {
350
                foreach ($changes as $change) {
351
                    if ($change['begin'] > $state['begin']) {
352
                        // future override
353
                        continue;
354
                    }
355
                    if ($change['end'] && ($change['end'] < $state['end'] || !$state['end'])) {
356
                        // complete override
357
                        continue;
358
                    }
359
                    if ($isLink) {
360
                        $state['data'][] = $change['data'];
361
                    } else {
362
                        $state['data'] = array_merge($state['data'], $change['data']);
363
                    }
364
                }
365
            }
366
            if (count($state['data'])) {
367
                $states[] = array_merge($state, $params);
368
            }
369
        }
370
371
        // merge states
372
        $clean = $isLink;
373
        while (!$clean) {
374
            $clean = true;
375
            foreach ($states as $i => $state) {
376
                if (array_key_exists($i+1, $states)) {
377
                    $next = $states[$i+1];
378
                    if (!count(array_diff_assoc($state['data'], $next['data']))) {
379
                        $states[$i]['end'] = $next['end'];
380
                        unset($states[$i+1]);
381
                        $states = array_values($states);
382
                        $clean = false;
383
                        break;
384
                    }
385
                }
386
            }
387
        }
388
389
        foreach ($states as $state) {
390
            $this->mapper->create($space, $state);
391
        }
392
    }
393
394
    private function entityNameToId($name)
395
    {
396
        if (!$this->mapper->hasPlugin(Sequence::class)) {
397
            $this->mapper->addPlugin(Sequence::class);
398
        }
399
400
        $this->mapper->getSchema()->once(__CLASS__.'@entity', function (Mapper $mapper) {
0 ignored issues
show
Unused Code introduced by
The parameter $mapper is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
401
            $this->mapper->getSchema()
402
                ->createSpace('_temporal_entity', [
403
                    'id'   => 'unsigned',
404
                    'name' => 'str',
405
                ])
406
                ->addIndex(['id'])
407
                ->addIndex(['name']);
408
        });
409
410
        return $this->mapper->findOrCreate('_temporal_entity', compact('name'))->id;
411
    }
412
413
    private function entityIdToName($id)
414
    {
415
        return $this->mapper->findOne('_temporal_entity', compact('id'))->name;
416
    }
417
418
    private function initSchema($name)
419
    {
420
        switch ($name) {
421
            case 'override':
422
                return $this->mapper->getSchema()->once(__CLASS__.'@states', function (Mapper $mapper) {
423
                    $mapper->getSchema()
424
                        ->createSpace('_temporal_override', [
425
                            'entity'     => 'unsigned',
426
                            'id'         => 'unsigned',
427
                            'begin'      => 'unsigned',
428
                            'end'        => 'unsigned',
429
                            'timestamp'  => 'unsigned',
430
                            'actor'      => 'unsigned',
431
                            'data'       => '*',
432
                        ])
433
                        ->addIndex(['entity', 'id', 'begin', 'timestamp', 'actor']);
434
435
                    $mapper->getSchema()
436
                        ->createSpace('_temporal_override_aggregate', [
437
                            'entity'     => 'unsigned',
438
                            'id'         => 'unsigned',
439
                            'begin'      => 'unsigned',
440
                            'end'        => 'unsigned',
441
                            'data'       => '*',
442
                        ])
443
                        ->addIndex(['entity', 'id', 'begin']);
444
                });
445
446
            case 'link':
447
                return $this->mapper->getSchema()->once(__CLASS__.'@link', function (Mapper $mapper) {
448
                    $mapper->getSchema()
449
                        ->createSpace('_temporal_link', [
450
                            'id'        => 'unsigned',
451
                            'parent'    => 'unsigned',
452
                            'entity'    => 'unsigned',
453
                            'entityId'  => 'unsigned',
454
                            'begin'     => 'unsigned',
455
                            'end'       => 'unsigned',
456
                            'timestamp' => 'unsigned',
457
                            'actor'     => 'unsigned',
458
                            'data'      => '*',
459
                        ])
460
                        ->addIndex(['id'])
461
                        ->addIndex(['entity', 'entityId', 'parent', 'begin', 'timestamp', 'actor'])
462
                        ->addIndex([
463
                            'fields' => 'parent',
464
                            'unique' => false,
465
                        ]);
466
467
                    $mapper->getSchema()
468
                        ->createSpace('_temporal_link_aggregate', [
469
                            'entity' => 'unsigned',
470
                            'id'     => 'unsigned',
471
                            'begin'  => 'unsigned',
472
                            'end'    => 'unsigned',
473
                            'data'   => '*',
474
                        ])
475
                        ->addIndex(['entity', 'id', 'begin']);
476
                });
477
        }
478
479
        throw new Exception("Invalid schema $name");
480
    }
481
}
482