Completed
Push — master ( 46327d...588faf )
by Dmitry
01:47
created

Temporal::initSchema()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 67
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 67
rs 9.2815
c 0
b 0
f 0
cc 3
eloc 52
nc 3
nop 1

How to fix   Long Method   

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
        $entityName = $override['entity'];
132
        $override['entity'] = $this->entityNameToId($entityName);
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
        $this->updateOverrideAggregation($entityName, $override['id']);
140
    }
141
142
    public function toggleOverrideIdle($entity, $id, $begin, $actor, $timestamp)
143
    {
144
        $override = $this->mapper->findOrFail('_temporal_override', [
145
            'entity' => $this->entityNameToId($entity),
146
            'id' => $id,
147
            'begin' => $begin,
148
            'actor' => $actor,
149
            'timestamp' => $timestamp,
150
        ]);
151
152
        if (property_exists($override, 'idle') && $override->idle) {
153
            $override->idle = 0;
154
        } else {
155
            $override->idle = time();
156
        }
157
        $override->save();
158
159
        $this->updateOverrideAggregation($entity, $id);
160
    }
161
162
    public function updateOverrideAggregation($entity, $id)
163
    {
164
        $params = [
165
            'entity' => $this->entityNameToId($entity),
166
            'id'     => $id,
167
        ];
168
169
        $changeaxis = [];
170
171
        foreach ($this->mapper->find('_temporal_override', $params) as $i => $override) {
172
            if (property_exists($override, 'idle') && $override->idle) {
173
                continue;
174
            }
175
            if (!array_key_exists($override->timestamp, $changeaxis)) {
176
                $changeaxis[$override->timestamp] = [];
177
            }
178
            $changeaxis[$override->timestamp][] = [
179
                'begin' => $override->begin,
180
                'end' => $override->end,
181
                'data' => $override->data,
182
            ];
183
        }
184
185
        $this->updateAggregation('override', $params, $changeaxis);
186
    }
187
188
    public function link(array $link)
189
    {
190
        $link = $this->parseConfig($link);
191
192
        $this->initSchema('link');
193
194
        ksort($link);
195
        $node = null;
196
        foreach ($link as $entity => $id) {
197
            if (in_array($entity, ['begin', 'end', 'data'])) {
198
                continue;
199
            }
200
            $spaceId = $this->entityNameToId($entity);
201
202
            $params = [
203
                'entity'   => $spaceId,
204
                'entityId' => $id,
205
                'parent'   => $node ? $node->id : 0,
206
            ];
207
            $node = $this->mapper->findOrCreate('_temporal_link', $params);
208
        }
209
210
        if (!$node || !$node->parent) {
211
            throw new Exception("Invalid link configuration");
212
        }
213
214
        $node->begin = $link['begin'];
215
        $node->end = $link['end'];
216
        $node->actor = $this->actor;
217
        $node->timestamp = Carbon::now()->timestamp;
218
        if (array_key_exists('data', $link)) {
219
            $node->data = $link['data'];
220
        }
221
222
        $node->save();
223
224
        foreach ($link as $entity => $id) {
225
            if (in_array($entity, ['begin', 'end', 'data'])) {
226
                continue;
227
            }
228
            $spaceId = $this->entityNameToId($entity);
229
            $source = $this->mapper->find('_temporal_link', [
230
                'entity'   => $spaceId,
231
                'entityId' => $id,
232
            ]);
233
234
            $leafs = [];
235
            foreach ($source as $node) {
236
                foreach ($this->getLeafs($node) as $detail) {
237
                    $leafs[] = $detail;
238
                }
239
            }
240
241
            $changeaxis = [];
242
243
            foreach ($leafs as $leaf) {
244
                $current = $leaf;
245
                $ref = [];
246
247
                while ($current) {
248
                    if ($current->entity != $spaceId) {
249
                        $ref[$current->entity] = $current->entityId;
250
                    }
251
                    if ($current->parent) {
252
                        $current = $this->mapper->findOne('_temporal_link', $current->parent);
253
                    } else {
254
                        $current = null;
255
                    }
256
                }
257
258
                $data = [$ref];
259
                if (property_exists($leaf, 'data') && $leaf->data) {
260
                    $data[] = $leaf->data;
261
                }
262
263
                if (!array_key_exists($leaf->timestamp, $changeaxis)) {
264
                    $changeaxis[$leaf->timestamp] = [];
265
                }
266
                $changeaxis[$leaf->timestamp][] = [
267
                    'begin' => $leaf->begin,
268
                    'end' => $leaf->end,
269
                    'data' => $data
270
                ];
271
            }
272
273
            $params = [
274
                'entity' => $spaceId,
275
                'id'     => $id,
276
            ];
277
278
            $this->updateAggregation('link', $params, $changeaxis);
279
        }
280
    }
281
282
    public function setActor($actor)
283
    {
284
        $this->actor = $actor;
285
        return $this;
286
    }
287
288
    private function getLeafs($link)
289
    {
290
        if ($link->timestamp) {
291
            return [$link];
292
        }
293
294
        $leafs = [];
295
        foreach ($this->mapper->find('_temporal_link', ['parent' => $link->id]) as $child) {
296
            foreach ($this->getLeafs($child) as $leaf) {
297
                $leafs[] = $leaf;
298
            }
299
        }
300
        return $leafs;
301
    }
302
303
    private function getTimestamp($string)
304
    {
305
        if (!array_key_exists($string, $this->timestamps)) {
306
            if (strlen($string) == 8 && is_numeric($string)) {
307
                $this->timestamps[$string] = Carbon::createFromFormat('Ymd', $string)->timestamp;
308
            } else {
309
                $this->timestamps[$string] = Carbon::parse($string)->timestamp;
310
            }
311
        }
312
        return $this->timestamps[$string];
313
    }
314
315
    private function parseConfig(array $data)
316
    {
317
        if (!$this->actor) {
318
            throw new Exception("actor is undefined");
319
        }
320
321
        if (array_key_exists('actor', $data)) {
322
            throw new Exception("actor is defined");
323
        }
324
325
        if (array_key_exists('timestamp', $data)) {
326
            throw new Exception("timestamp is defined");
327
        }
328
329
        foreach (['begin', 'end'] as $field) {
330
            if (array_key_exists($field, $data)) {
331
                if (strlen($data[$field]) == 8 || is_string($data[$field])) {
332
                    $data[$field] = $this->getTimestamp($data[$field]);
333
                }
334
            } else {
335
                $data[$field] = 0;
336
            }
337
        }
338
339
        return $data;
340
    }
341
342
    private function updateAggregation($type, $params, $changeaxis)
343
    {
344
        $isLink = $type === 'link';
345
        $space = $isLink ? '_temporal_link_aggregate' : '_temporal_override_aggregate';
346
347
        $timeaxis = [];
348
        foreach ($changeaxis as $timestamp => $changes) {
349
            foreach ($changes as $change) {
350
                foreach (['begin', 'end'] as $field) {
351
                    if (!array_key_exists($change[$field], $timeaxis)) {
352
                        $timeaxis[$change[$field]] = [
353
                            'begin' => $change[$field],
354
                            'end'   => $change[$field],
355
                            'data'  => [],
356
                        ];
357
                    }
358
                }
359
            }
360
        }
361
362
        ksort($changeaxis);
363
        ksort($timeaxis);
364
365
        $nextSliceId = null;
366
        foreach (array_reverse(array_keys($timeaxis)) as $timestamp) {
367
            if ($nextSliceId) {
368
                $timeaxis[$timestamp]['end'] = $nextSliceId;
369
            } else {
370
                $timeaxis[$timestamp]['end'] = 0;
371
            }
372
            $nextSliceId = $timestamp;
373
        }
374
375
        foreach ($this->mapper->find($space, $params) as $state) {
376
            $this->mapper->remove($state);
377
        }
378
379
        $states = [];
380
        foreach ($timeaxis as $state) {
381
            foreach ($changeaxis as $changes) {
382
                foreach ($changes as $change) {
383
                    if ($change['begin'] > $state['begin']) {
384
                        // future override
385
                        continue;
386
                    }
387
                    if ($change['end'] && ($change['end'] < $state['end'] || !$state['end'])) {
388
                        // complete override
389
                        continue;
390
                    }
391
                    if ($isLink) {
392
                        $state['data'][] = $change['data'];
393
                    } else {
394
                        $state['data'] = array_merge($state['data'], $change['data']);
395
                    }
396
                }
397
            }
398
            if (count($state['data'])) {
399
                $states[] = array_merge($state, $params);
400
            }
401
        }
402
403
        // merge states
404
        $clean = $isLink;
405
        while (!$clean) {
406
            $clean = true;
407
            foreach ($states as $i => $state) {
408
                if (array_key_exists($i+1, $states)) {
409
                    $next = $states[$i+1];
410
                    if (!count(array_diff_assoc($state['data'], $next['data']))) {
411
                        $states[$i]['end'] = $next['end'];
412
                        unset($states[$i+1]);
413
                        $states = array_values($states);
414
                        $clean = false;
415
                        break;
416
                    }
417
                }
418
            }
419
        }
420
421
        foreach ($states as $state) {
422
            $this->mapper->create($space, $state);
423
        }
424
    }
425
426
    private function entityNameToId($name)
427
    {
428
        if (!$this->mapper->hasPlugin(Sequence::class)) {
429
            $this->mapper->addPlugin(Sequence::class);
430
        }
431
432
        $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...
433
            $this->mapper->getSchema()
434
                ->createSpace('_temporal_entity', [
435
                    'id'   => 'unsigned',
436
                    'name' => 'str',
437
                ])
438
                ->addIndex(['id'])
439
                ->addIndex(['name']);
440
        });
441
442
        return $this->mapper->findOrCreate('_temporal_entity', compact('name'))->id;
443
    }
444
445
    private function entityIdToName($id)
446
    {
447
        return $this->mapper->findOne('_temporal_entity', compact('id'))->name;
448
    }
449
450
    private function initSchema($name)
451
    {
452
        switch ($name) {
453
            case 'override':
454
                $this->mapper->getSchema()->once(__CLASS__.'@states', function (Mapper $mapper) {
455
                    $mapper->getSchema()
456
                        ->createSpace('_temporal_override', [
457
                            'entity'     => 'unsigned',
458
                            'id'         => 'unsigned',
459
                            'begin'      => 'unsigned',
460
                            'end'        => 'unsigned',
461
                            'timestamp'  => 'unsigned',
462
                            'actor'      => 'unsigned',
463
                            'data'       => '*',
464
                        ])
465
                        ->addIndex(['entity', 'id', 'begin', 'timestamp', 'actor']);
466
467
                    $mapper->getSchema()
468
                        ->createSpace('_temporal_override_aggregate', [
469
                            'entity'     => 'unsigned',
470
                            'id'         => 'unsigned',
471
                            'begin'      => 'unsigned',
472
                            'end'        => 'unsigned',
473
                            'data'       => '*',
474
                        ])
475
                        ->addIndex(['entity', 'id', 'begin']);
476
                });
477
                $this->mapper->getSchema()->once(__CLASS__.'@override-idle', function (Mapper $mapper) {
478
                    $mapper->getSchema()->getSpace('_temporal_override')->addProperty('idle', 'unsigned');
479
                });
480
                return;
481
482
            case 'link':
483
                return $this->mapper->getSchema()->once(__CLASS__.'@link', function (Mapper $mapper) {
484
                    $mapper->getSchema()
485
                        ->createSpace('_temporal_link', [
486
                            'id'        => 'unsigned',
487
                            'parent'    => 'unsigned',
488
                            'entity'    => 'unsigned',
489
                            'entityId'  => 'unsigned',
490
                            'begin'     => 'unsigned',
491
                            'end'       => 'unsigned',
492
                            'timestamp' => 'unsigned',
493
                            'actor'     => 'unsigned',
494
                            'data'      => '*',
495
                        ])
496
                        ->addIndex(['id'])
497
                        ->addIndex(['entity', 'entityId', 'parent', 'begin', 'timestamp', 'actor'])
498
                        ->addIndex([
499
                            'fields' => 'parent',
500
                            'unique' => false,
501
                        ]);
502
503
                    $mapper->getSchema()
504
                        ->createSpace('_temporal_link_aggregate', [
505
                            'entity' => 'unsigned',
506
                            'id'     => 'unsigned',
507
                            'begin'  => 'unsigned',
508
                            'end'    => 'unsigned',
509
                            'data'   => '*',
510
                        ])
511
                        ->addIndex(['entity', 'id', 'begin']);
512
                });
513
        }
514
515
        throw new Exception("Invalid schema $name");
516
    }
517
}
518