Completed
Push — master ( f6db52...9cdeb8 )
by Dmitry
14:08
created

Temporal::getReference()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 17

Duplication

Lines 25
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 25
loc 25
rs 8.439
cc 5
eloc 17
nc 4
nop 4
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
        $reference['target'] = $this->entityNameToId(array_keys($reference['data'])[0]);
101
        $reference['targetId'] = array_values($reference['data'])[0];
102
103
104
        // set entity id
105
        $entityName = $reference['entity'];
106
        $reference['entity'] = $this->entityNameToId($entityName);
107
        $reference['actor'] = $this->actor;
108
        $reference['timestamp'] = Carbon::now()->timestamp;
109
110
        $this->schema->init('reference');
111
        $this->mapper->create('_temporal_reference', $reference);
112
113
        $this->aggregator->updateReferenceState($entityName, $reference['id']);
114
    }
115
116
    public function getLinksLog($entity, $entityId, $filter = [])
117
    {
118
        $this->schema->init('link');
119
120
        $entity = $this->entityNameToId($entity);
121
122
        $nodes = $this->mapper->find('_temporal_link', [
123
            'entity' => $entity,
124
            'entityId' => $entityId,
125
        ]);
126
127
        $links = [];
128
129
        foreach ($nodes as $node) {
130
            foreach ($this->aggregator->getLeafs($node) as $leaf) {
131
                $entityName = $this->entityIdToName($leaf->entity);
132
                $link = [
133
                    $entityName => $leaf->entityId,
134
                    'id'        => $leaf->id,
135
                    'begin'     => $leaf->begin,
136
                    'end'       => $leaf->end,
137
                    'timestamp' => $leaf->timestamp,
138
                    'actor'     => $leaf->actor,
139
                    'idle'      => property_exists($leaf, 'idle') ? $leaf->idle : 0,
140
                ];
141
142
                $current = $leaf;
143
                while ($current->parent) {
144
                    $current = $this->mapper->findOne('_temporal_link', $current->parent);
145
                    $entityName = $this->entityIdToName($current->entity);
146
                    $link[$entityName] = $current->entityId;
147
                }
148
149
                if (count($filter)) {
150
                    foreach ($filter as $required) {
151
                        if (!array_key_exists($required, $link)) {
152
                            continue 2;
153
                        }
154
                    }
155
                }
156
                $links[] = $link;
157
            }
158
        }
159
160
        return $links;
161
    }
162
163
    public function getLinks($entity, $id, $date)
164
    {
165
        $this->schema->init('link');
166
167
        $links = $this->getData($entity, $id, $date, '_temporal_link_aggregate');
168
        foreach ($links as $i => $source) {
169
            $link = array_key_exists(1, $source) ? ['data' => $source[1]] : [];
170
            foreach ($source[0] as $spaceId => $entityId) {
171
                $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...
172
                $link[$spaceName] = $entityId;
173
            }
174
            $links[$i] = $link;
175
        }
176
        return $links;
177
    }
178
179
    public function getState($entity, $id, $date)
180
    {
181
        $this->schema->init('override');
182
183
        return $this->getData($entity, $id, $date, '_temporal_override_aggregate');
184
    }
185
186
    private function getData($entity, $id, $date, $space)
187
    {
188
        $entity = $this->entityNameToId($entity);
189
        $date = $this->getTimestamp($date);
190
191
        $rows = $this->mapper->getClient()->getSpace($space)
192
            ->select([$entity, $id, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
193
            ->getData();
194
195
        if (count($rows) && $rows[0][0] == $entity && $rows[0][1] == $id) {
196
            $state = $this->mapper->findOne($space, [
197
                'entity' => $entity,
198
                'id' => $id,
199
                'begin' => $rows[0][2]
200
            ]);
201
            if (!$state->end || $state->end >= $date) {
202
                return $state->data;
203
            }
204
        }
205
206
        return [];
207
    }
208
209
    public function getOverrides($entityName, $id)
210
    {
211
        return $this->mapper->find('_temporal_override', [
212
            'entity' => $this->entityNameToId($entityName),
213
            'id' => $id,
214
        ]);
215
    }
216
217
    public function override(array $override)
218
    {
219
        $override = $this->parseConfig($override);
220
221 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...
222
            if (!in_array($k, ['entity', 'id', 'begin', 'end', 'data'])) {
223
                $override['entity'] = $k;
224
                $override['id'] = $v;
225
                unset($override[$k]);
226
            }
227
        }
228
229
        if (!array_key_exists('entity', $override)) {
230
            throw new Exception("no entity defined");
231
        }
232
233
        // set entity id
234
        $entityName = $override['entity'];
235
        $override['entity'] = $this->entityNameToId($entityName);
236
        $override['actor'] = $this->actor;
237
        $override['timestamp'] = Carbon::now()->timestamp;
238
239
        $this->schema->init('override');
240
        $this->mapper->create('_temporal_override', $override);
241
242
        $this->aggregator->updateOverrideAggregation($entityName, $override['id']);
243
    }
244
245
    public function setLinkIdle($id, $flag)
246
    {
247
        $link = $this->mapper->findOrFail('_temporal_link', $id);
248
249
        $idled = property_exists($link, 'idle') && $link->idle > 0;
250
        if ($idled && !$flag || !$idled && $flag) {
251
            return $this->toggleLinkIdle($link);
252
        }
253
    }
254
255
    public function toggleLinkIdle(Entity $link)
256
    {
257
        if (property_exists($link, 'idle') && $link->idle) {
258
            $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...
259
        } else {
260
            $link->idle = time();
261
        }
262
        $link->save();
263
264
        $this->aggregator->updateLinkAggregation($link);
265
    }
266
267
    public function setOverrideIdle($entity, $id, $begin, $actor, $timestamp, $flag)
268
    {
269
        $override = $this->mapper->findOrFail('_temporal_override', [
270
            'entity' => $this->entityNameToId($entity),
271
            'id' => $id,
272
            'begin' => $begin,
273
            'actor' => $actor,
274
            'timestamp' => $timestamp,
275
        ]);
276
        $idled = property_exists($override, 'idle') && $override->idle > 0;
277
        if ($idled && !$flag || !$idled && $flag) {
278
            return $this->toggleOverrideIdle($entity, $id, $begin, $actor, $timestamp);
279
        }
280
    }
281
282
    public function toggleOverrideIdle($entity, $id, $begin, $actor, $timestamp)
283
    {
284
        $override = $this->mapper->findOrFail('_temporal_override', [
285
            'entity' => $this->entityNameToId($entity),
286
            'id' => $id,
287
            'begin' => $begin,
288
            'actor' => $actor,
289
            'timestamp' => $timestamp,
290
        ]);
291
292
        if (property_exists($override, 'idle') && $override->idle) {
293
            $override->idle = 0;
294
        } else {
295
            $override->idle = time();
296
        }
297
        $override->save();
298
299
        $this->aggregator->updateOverrideAggregation($entity, $id);
300
    }
301
302
303
    public function link(array $link)
304
    {
305
        $link = $this->parseConfig($link);
306
307
        $this->schema->init('link');
308
309
        $config = [];
310
        foreach ($link as $entity => $id) {
311
            if (!in_array($entity, ['begin', 'end', 'data'])) {
312
                $config[$entity] = $id;
313
            }
314
        }
315
316
        ksort($config);
317
        $node = null;
318
319
        foreach (array_keys($config) as $i => $entity) {
320
            $id = $config[$entity];
321
            $spaceId = $this->entityNameToId($entity);
322
            $params = [
323
                'entity'   => $spaceId,
324
                'entityId' => $id,
325
                'parent'   => $node ? $node->id : 0,
326
            ];
327
            if (count($config) == $i+1) {
328
                $params['begin'] = $link['begin'];
329
                $params['timestamp'] = 0;
330
            }
331
            $node = $this->mapper->findOrCreate('_temporal_link', $params);
332
        }
333
334
        if (!$node || !$node->parent) {
335
            throw new Exception("Invalid link configuration");
336
        }
337
338
        $node->begin = $link['begin'];
339
        $node->end = $link['end'];
340
        $node->actor = $this->actor;
341
        $node->timestamp = Carbon::now()->timestamp;
342
        if (array_key_exists('data', $link)) {
343
            $node->data = $link['data'];
344
        }
345
346
        $node->save();
347
348
        $this->aggregator->updateLinkAggregation($node);
349
    }
350
351
    public function setActor($actor)
352
    {
353
        $this->actor = $actor;
354
        return $this;
355
    }
356
357
    private function getTimestamp($string)
358
    {
359
        if (!array_key_exists($string, $this->timestamps)) {
360
            if (strlen($string) == 8 && is_numeric($string)) {
361
                $this->timestamps[$string] = Carbon::createFromFormat('Ymd', $string)->setTime(0, 0, 0)->timestamp;
362
            } else {
363
                $this->timestamps[$string] = Carbon::parse($string)->timestamp;
364
            }
365
        }
366
        return $this->timestamps[$string];
367
    }
368
369
    private function parseConfig(array $data)
370
    {
371
        if (!$this->actor) {
372
            throw new Exception("actor is undefined");
373
        }
374
375
        if (array_key_exists('actor', $data)) {
376
            throw new Exception("actor is defined");
377
        }
378
379
        if (array_key_exists('timestamp', $data)) {
380
            throw new Exception("timestamp is defined");
381
        }
382
383
        foreach (['begin', 'end'] as $field) {
384
            if (array_key_exists($field, $data) && strlen($data[$field])) {
385
                if (strlen($data[$field]) == 8 || is_string($data[$field])) {
386
                    $data[$field] = $this->getTimestamp($data[$field]);
387
                }
388
            } else {
389
                $data[$field] = 0;
390
            }
391
        }
392
393
        return $data;
394
    }
395
396
    public function entityNameToId($name)
397
    {
398
        if (!$this->mapper->hasPlugin(Sequence::class)) {
399
            $this->mapper->addPlugin(Sequence::class);
400
        }
401
402
        $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...
403
            $this->mapper->getSchema()
404
                ->createSpace('_temporal_entity', [
405
                    'id'   => 'unsigned',
406
                    'name' => 'str',
407
                ])
408
                ->addIndex(['id'])
409
                ->addIndex(['name']);
410
        });
411
412
        return $this->mapper->findOrCreate('_temporal_entity', compact('name'))->id;
413
    }
414
415
    public function entityIdToName($id)
416
    {
417
        return $this->mapper->findOne('_temporal_entity', compact('id'))->name;
418
    }
419
}
420