Completed
Push — master ( 6f9f53...8db1ca )
by Dmitry
02:05
created

Temporal::toggleReferenceIdle()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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