Completed
Push — master ( 40b4ea...c1dc36 )
by Dmitry
02:42
created

Temporal::setReferenceIdle()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.1111
c 0
b 0
f 0
cc 6
nc 4
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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