Completed
Push — master ( f17ce5...6988c6 )
by Dmitry
02:00
created

Temporal::getLinks()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 6
nop 3
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\Entity;
9
use Tarantool\Mapper\Plugin;
10
use Tarantool\Mapper\Plugin\Temporal\Aggregator;
11
use Tarantool\Mapper\Plugin\Temporal\Schema;
12
13
class Temporal extends Plugin
14
{
15
    private $actor;
16
    private $timestamps = [];
17
    private $aggregator;
18
19
    public function __construct(Mapper $mapper)
20
    {
21
        $this->mapper = $mapper;
22
        $this->schema = new Schema($mapper);
0 ignored issues
show
Bug introduced by
The property schema does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
23
        $this->aggregator = new Aggregator($this);
24
    }
25
26 View Code Duplication
    public function getReference($entity, $id, $target, $date)
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...
27
    {
28
        if (!$this->mapper->getSchema()->hasSpace('_temporal_reference_state')) {
29
            return;
30
        }
31
32
        $entity = $this->entityNameToId($entity);
33
        $target = $this->entityNameToId($target);
34
        $date = $this->getTimestamp($date);
35
36
        $rows = $this->mapper->getClient()->getSpace('_temporal_reference_state')
37
            ->select([$entity, $id, $target, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
38
            ->getData();
39
40
        if (count($rows)) {
41
            $row = $rows[0];
42
            if ([$entity, $id, $target] == [$row[0], $row[1], $row[2]]) {
43
                $state = $this->mapper->findOne('_temporal_reference_state', [
44
                    'entity' => $entity,
45
                    'id' => $id,
46
                    'target' => $target,
47
                    'begin' => $row[3]
48
                ]);
49
                if (!$state->end || $state->end >= $date) {
50
                    return $state->targetId;
51
                }
52
            }
53
        }
54
    }
55
56
    public function getReferenceLog($entity, $id, $target)
57
    {
58
        if (!$this->mapper->getSchema()->hasSpace('_temporal_reference')) {
59
            return [];
60
        }
61
        $log = [];
62
        $params = [
63
            'entity' => $this->entityNameToId($entity),
64
            'id' => $id,
65
            'target' => $this->entityNameToId($target),
66
        ];
67
        foreach ($this->mapper->find('_temporal_reference', $params) as $reference) {
68
            $log[] = $reference;
69
        }
70
        return $log;
71
    }
72
73 View Code Duplication
    public function getReferences($target, $targetId, $source, $date)
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...
74
    {
75
        if (!$this->mapper->getSchema()->hasSpace('_temporal_reference_aggregate')) {
76
            return [];
77
        }
78
79
        $target = $this->entityNameToId($target);
80
        $source = $this->entityNameToId($source);
81
        $date = $this->getTimestamp($date);
82
83
        $rows = $this->mapper->getClient()->getSpace('_temporal_reference_aggregate')
84
            ->select([$target, $targetId, $source, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
85
            ->getData();
86
87
        if (count($rows)) {
88
            $row = $rows[0];
89
            if ([$target, $targetId, $source] == [$row[0], $row[1], $row[2]]) {
90
                $state = $this->mapper->findOne('_temporal_reference_aggregate', [
91
                    'entity' => $target,
92
                    'id'     => $targetId,
93
                    'source' => $source,
94
                    'begin'  => $row[3]
95
                ]);
96
97
                if (!$state->end || $state->end > $date) {
98
                    return $state->data;
99
                }
100
            }
101
        }
102
        return [];
103
    }
104
105
    public function reference(array $reference)
106
    {
107
        $reference = $this->parseConfig($reference);
108
109 View Code Duplication
        foreach ($reference as $k => $v) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
110
            if (!in_array($k, ['entity', 'id', 'begin', 'end', 'data'])) {
111
                $reference['entity'] = $k;
112
                $reference['id'] = $v;
113
                unset($reference[$k]);
114
            }
115
        }
116
117
        if (!array_key_exists('entity', $reference)) {
118
            throw new Exception("no entity defined");
119
        }
120
121
        if (count($reference['data']) != 1) {
122
            throw new Exception("Invalid reference configuration");
123
        }
124
125
        $targetName = array_keys($reference['data'])[0];
126
        $reference['target'] = $this->entityNameToId($targetName);
127
        $reference['targetId'] = $reference['data'][$targetName];
128
129
130
        // set entity id
131
        $entityName = $reference['entity'];
132
        $reference['entity'] = $this->entityNameToId($entityName);
133
        $reference['actor'] = $this->actor;
134
        $reference['timestamp'] = Carbon::now()->timestamp;
135
136
        $this->schema->init('reference');
137
        $this->mapper->create('_temporal_reference', $reference);
138
139
        $this->aggregator->updateReferenceState($entityName, $reference['id'], $targetName);
140
    }
141
142
    public function getLinksLog($entity, $entityId, $filter = [])
143
    {
144
        if (!$this->mapper->getSchema()->hasSpace('_temporal_link')) {
145
            return [];
146
        }
147
148
        $entity = $this->entityNameToId($entity);
149
150
        $nodes = $this->mapper->find('_temporal_link', [
151
            'entity' => $entity,
152
            'entityId' => $entityId,
153
        ]);
154
155
        $links = [];
156
157
        foreach ($nodes as $node) {
158
            foreach ($this->aggregator->getLeafs($node) as $leaf) {
159
                $entityName = $this->entityIdToName($leaf->entity);
160
                $link = [
161
                    $entityName => $leaf->entityId,
162
                    'id'        => $leaf->id,
163
                    'begin'     => $leaf->begin,
164
                    'end'       => $leaf->end,
165
                    'timestamp' => $leaf->timestamp,
166
                    'actor'     => $leaf->actor,
167
                    'idle'      => property_exists($leaf, 'idle') ? $leaf->idle : 0,
168
                ];
169
170
                $current = $leaf;
171
                while ($current->parent) {
172
                    $current = $this->mapper->findOne('_temporal_link', $current->parent);
173
                    $entityName = $this->entityIdToName($current->entity);
174
                    $link[$entityName] = $current->entityId;
175
                }
176
177
                if (count($filter)) {
178
                    foreach ($filter as $required) {
179
                        if (!array_key_exists($required, $link)) {
180
                            continue 2;
181
                        }
182
                    }
183
                }
184
                $links[] = $link;
185
            }
186
        }
187
188
        return $links;
189
    }
190
191
    public function getLinks($entity, $id, $date)
192
    {
193
        if (!$this->mapper->getSchema()->hasSpace('_temporal_link_aggregate')) {
194
            return [];
195
        }
196
197
        $links = $this->getData($entity, $id, $date, '_temporal_link_aggregate');
198
        foreach ($links as $i => $source) {
199
            $link = array_key_exists(1, $source) ? ['data' => $source[1]] : [];
200
            foreach ($source[0] as $spaceId => $entityId) {
201
                $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...
202
                $link[$spaceName] = $entityId;
203
            }
204
            $links[$i] = $link;
205
        }
206
        return $links;
207
    }
208
209
    public function getState($entity, $id, $date)
210
    {
211
        if (!$this->mapper->getSchema()->hasSpace('_temporal_override_aggregate')) {
212
            return [];
213
        }
214
215
        return $this->getData($entity, $id, $date, '_temporal_override_aggregate');
216
    }
217
218
    private function getData($entity, $id, $date, $space)
219
    {
220
        $entity = $this->entityNameToId($entity);
221
        $date = $this->getTimestamp($date);
222
223
        $rows = $this->mapper->getClient()->getSpace($space)
224
            ->select([$entity, $id, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
225
            ->getData();
226
227
        if (count($rows) && $rows[0][0] == $entity && $rows[0][1] == $id) {
228
            $state = $this->mapper->findOne($space, [
229
                'entity' => $entity,
230
                'id' => $id,
231
                'begin' => $rows[0][2]
232
            ]);
233
            if (!$state->end || $state->end >= $date) {
234
                return $state->data;
235
            }
236
        }
237
238
        return [];
239
    }
240
241
    public function getOverrides($entityName, $id)
242
    {
243
        if (!$this->mapper->getSchema()->hasSpace('_temporal_override')) {
244
            return [];
245
        }
246
247
        return $this->mapper->find('_temporal_override', [
248
            'entity' => $this->entityNameToId($entityName),
249
            'id' => $id,
250
        ]);
251
    }
252
253
    public function override(array $override)
254
    {
255
        $override = $this->parseConfig($override);
256
257 View Code Duplication
        foreach ($override as $k => $v) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
258
            if (!in_array($k, ['entity', 'id', 'begin', 'end', 'data'])) {
259
                $override['entity'] = $k;
260
                $override['id'] = $v;
261
                unset($override[$k]);
262
            }
263
        }
264
265
        if (!array_key_exists('entity', $override)) {
266
            throw new Exception("no entity defined");
267
        }
268
269
        // set entity id
270
        $entityName = $override['entity'];
271
        $override['entity'] = $this->entityNameToId($entityName);
272
        $override['actor'] = $this->actor;
273
        $override['timestamp'] = Carbon::now()->timestamp;
274
275
        $this->schema->init('override');
276
        $this->mapper->create('_temporal_override', $override);
277
278
        $this->aggregator->updateOverrideAggregation($entityName, $override['id']);
279
    }
280
281
    public function setLinkIdle($id, $flag)
282
    {
283
        $link = $this->mapper->findOrFail('_temporal_link', $id);
284
285
        $idled = property_exists($link, 'idle') && $link->idle > 0;
286
        if ($idled && !$flag || !$idled && $flag) {
287
            return $this->toggleLinkIdle($link);
288
        }
289
    }
290
291
    public function toggleLinkIdle(Entity $link)
292
    {
293
        if (property_exists($link, 'idle') && $link->idle) {
294
            $link->idle = 0;
0 ignored issues
show
Bug introduced by
The property idle 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...
295
        } else {
296
            $link->idle = time();
297
        }
298
        $link->save();
299
300
        $this->aggregator->updateLinkAggregation($link);
301
    }
302
303
    public function setReferenceIdle($entity, $id, $target, $targetId, $begin, $actor, $timestamp, $flag)
304
    {
305
        $reference = $this->mapper->findOrFail('_temporal_reference', [
306
            'entity' => $this->entityNameToId($entity),
307
            'id' => $id,
308
            'target' => $this->entityNameToId($target),
309
            'targetId' => $targetId,
310
            'begin' => $begin,
311
            'actor' => $actor,
312
            'timestamp' => $timestamp,
313
        ]);
314
        $idled = property_exists($reference, 'idle') && $reference->idle > 0;
315
        if ($idled && !$flag || !$idled && $flag) {
316
            return $this->toggleReferenceIdle($entity, $id, $target, $targetId, $begin, $actor, $timestamp);
317
        }
318
    }
319
320
    public function toggleReferenceIdle($entity, $id, $target, $targetId, $begin, $actor, $timestamp)
321
    {
322
        $reference = $this->mapper->findOrFail('_temporal_reference', [
323
            'entity' => $this->entityNameToId($entity),
324
            'id' => $id,
325
            'target' => $this->entityNameToId($target),
326
            'targetId' => $targetId,
327
            'begin' => $begin,
328
            'actor' => $actor,
329
            'timestamp' => $timestamp,
330
        ]);
331
332
        if (property_exists($reference, 'idle') && $reference->idle) {
333
            $reference->idle = 0;
334
        } else {
335
            $reference->idle = time();
336
        }
337
        $reference->save();
338
339
        $this->aggregator->updateReferenceState($entity, $id, $target);
340
    }
341
342
    public function setOverrideIdle($entity, $id, $begin, $actor, $timestamp, $flag)
343
    {
344
        $override = $this->mapper->findOrFail('_temporal_override', [
345
            'entity' => $this->entityNameToId($entity),
346
            'id' => $id,
347
            'begin' => $begin,
348
            'actor' => $actor,
349
            'timestamp' => $timestamp,
350
        ]);
351
        $idled = property_exists($override, 'idle') && $override->idle > 0;
352
        if ($idled && !$flag || !$idled && $flag) {
353
            return $this->toggleOverrideIdle($entity, $id, $begin, $actor, $timestamp);
354
        }
355
    }
356
357
    public function toggleOverrideIdle($entity, $id, $begin, $actor, $timestamp)
358
    {
359
        $override = $this->mapper->findOrFail('_temporal_override', [
360
            'entity' => $this->entityNameToId($entity),
361
            'id' => $id,
362
            'begin' => $begin,
363
            'actor' => $actor,
364
            'timestamp' => $timestamp,
365
        ]);
366
367
        if (property_exists($override, 'idle') && $override->idle) {
368
            $override->idle = 0;
369
        } else {
370
            $override->idle = time();
371
        }
372
        $override->save();
373
374
        $this->aggregator->updateOverrideAggregation($entity, $id);
375
    }
376
377
378
    public function link(array $link)
379
    {
380
        $link = $this->parseConfig($link);
381
382
        $this->schema->init('link');
383
384
        $config = [];
385
        foreach ($link as $entity => $id) {
386
            if (!in_array($entity, ['begin', 'end', 'data'])) {
387
                $config[$entity] = $id;
388
            }
389
        }
390
391
        ksort($config);
392
        $node = null;
393
394
        foreach (array_keys($config) as $i => $entity) {
395
            $id = $config[$entity];
396
            $spaceId = $this->entityNameToId($entity);
397
            $params = [
398
                'entity'   => $spaceId,
399
                'entityId' => $id,
400
                'parent'   => $node ? $node->id : 0,
401
            ];
402
            if (count($config) == $i+1) {
403
                $params['begin'] = $link['begin'];
404
                $params['timestamp'] = 0;
405
            }
406
            $node = $this->mapper->findOrCreate('_temporal_link', $params);
407
        }
408
409
        if (!$node || !$node->parent) {
410
            throw new Exception("Invalid link configuration");
411
        }
412
413
        $node->begin = $link['begin'];
414
        $node->end = $link['end'];
415
        $node->actor = $this->actor;
416
        $node->timestamp = Carbon::now()->timestamp;
417
        if (array_key_exists('data', $link)) {
418
            $node->data = $link['data'];
419
        }
420
421
        $node->save();
422
423
        $this->aggregator->updateLinkAggregation($node);
424
    }
425
426
    public function getActor()
427
    {
428
        return $this->actor;
429
    }
430
431
    public function setActor($actor)
432
    {
433
        $this->actor = $actor;
434
        return $this;
435
    }
436
437
    private function getTimestamp($string)
438
    {
439
        if (Carbon::hasTestNow() || !array_key_exists($string, $this->timestamps)) {
440
            if (strlen($string) == 8 && is_numeric($string)) {
441
                $value = Carbon::createFromFormat('Ymd', $string)->setTime(0, 0, 0)->timestamp;
442
            } else {
443
                $value = Carbon::parse($string)->timestamp;
444
            }
445
            if (Carbon::hasTestNow()) {
446
                return $value;
447
            }
448
            $this->timestamps[$string] = $value;
449
        }
450
        return $this->timestamps[$string];
451
    }
452
453
    private function parseConfig(array $data)
454
    {
455
        if (!$this->actor) {
456
            throw new Exception("actor is undefined");
457
        }
458
459
        if (array_key_exists('actor', $data)) {
460
            throw new Exception("actor is defined");
461
        }
462
463
        if (array_key_exists('timestamp', $data)) {
464
            throw new Exception("timestamp is defined");
465
        }
466
467
        foreach (['begin', 'end'] as $field) {
468
            if (array_key_exists($field, $data) && strlen($data[$field])) {
469
                if (strlen($data[$field]) == 8 || is_string($data[$field])) {
470
                    $data[$field] = $this->getTimestamp($data[$field]);
471
                }
472
            } else {
473
                $data[$field] = 0;
474
            }
475
        }
476
477
        return $data;
478
    }
479
480
    public function entityNameToId($name)
481
    {
482
        if (!$this->mapper->hasPlugin(Sequence::class)) {
483
            $this->mapper->getPlugin(Sequence::class);
484
        }
485
486
        $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...
487
            $this->mapper->getSchema()
488
                ->createSpace('_temporal_entity', [
489
                    'id'   => 'unsigned',
490
                    'name' => 'string',
491
                ])
492
                ->addIndex(['id'])
493
                ->addIndex(['name']);
494
        });
495
496
        return $this->mapper->findOrCreate('_temporal_entity', compact('name'))->id;
497
    }
498
499
    public function entityIdToName($id)
500
    {
501
        return $this->mapper->findOne('_temporal_entity', compact('id'))->name;
502
    }
503
}
504