Completed
Push — master ( d77101...a546f3 )
by Dmitry
01:41
created

Temporal::setReferenceIdle()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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