Completed
Push — master ( bf7b4f...c8b0a5 )
by Dmitry
01:45
created

Temporal::setActor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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\Plugin;
9
10
class Temporal extends Plugin
11
{
12
    private $actor;
13
    private $timestamps = [];
14
15
    public function __construct(Mapper $mapper)
16
    {
17
        $this->mapper = $mapper;
18
19
        $this->mapper->getSchema()->once(__CLASS__.'_states', function (Mapper $mapper) {
20
            $mapper->getSchema()
21
                ->createSpace('_override', [
22
                    'entity'     => 'unsigned',
23
                    'id'         => 'unsigned',
24
                    'begin'      => 'unsigned',
25
                    'end'        => 'unsigned',
26
                    'timestamp'  => 'unsigned',
27
                    'actor'      => 'unsigned',
28
                    'data'       => '*',
29
                ])->addIndex([
30
                    'fields' => ['entity', 'id', 'begin', 'timestamp', 'actor']
31
                ]);
32
33
            $mapper->getSchema()
34
                ->createSpace('_override_aggregate', [
35
                    'entity'     => 'unsigned',
36
                    'id'         => 'unsigned',
37
                    'begin'      => 'unsigned',
38
                    'end'        => 'unsigned',
39
                    'data'       => '*',
40
                ])
41
                ->addIndex([
42
                    'fields' => ['entity', 'id', 'begin'],
43
                ]);
44
        });
45
    }
46
47
    public function override(array $override)
48
    {
49
        if (!$this->actor) {
50
            throw new Exception("actor is undefined");
51
        }
52
53
        if (array_key_exists('actor', $override)) {
54
            throw new Exception("actor override is defined");
55
        }
56
57
        if (array_key_exists('timestamp', $override)) {
58
            throw new Exception("timestamp override is defined");
59
        }
60
61
        foreach (['begin', 'end'] as $field) {
62
            if (array_key_exists($field, $override)) {
63
                if (is_string($override[$field])) {
64
                    $override[$field] = $this->getTimestamp($override[$field]);
65
                }
66
            } else {
67
                $override[$field] = 0;
68
            }
69
        }
70
71
        foreach ($override as $k => $v) {
72
            if (!in_array($k, ['entity', 'id', 'begin', 'end', 'data'])) {
73
                $override['entity'] = $k;
74
                $override['id'] = $v;
75
                unset($override[$k]);
76
            }
77
        }
78
79
        if (!array_key_exists('entity', $override)) {
80
            throw new Exception("no entity defined");
81
        }
82
83
        if (!$this->mapper->getSchema()->hasSpace($override['entity'])) {
84
            throw new Exception("invalid entity " . $override['entity']);
85
        }
86
87
        $space = $this->mapper->getSchema()->getSpace($override['entity']);
88
89
        // set entity id
90
        $override['entity'] = $space->getId();
91
92
        $override['actor'] = $this->actor;
93
        $override['timestamp'] = Carbon::now()->timestamp;
94
95
        $this->mapper->create('_override', $override);
96
        $this->updateState($override);
97
    }
98
99
    public function setActor($actor)
100
    {
101
        $this->actor = $actor;
102
        return $this;
103
    }
104
105
    public function state($entity, $id, $date)
106
    {
107
        if (!$this->mapper->getSchema()->hasSpace($entity)) {
108
            throw new Exception("invalid entity: " . $entity);
109
        }
110
111
        $entity = $this->mapper->getSchema()->getSpace($entity)->getId();
112
        $date = $this->getTimestamp($date);
113
114
        $rows = $this->mapper->getClient()->getSpace('_override_aggregate')
115
            ->select([$entity, $id, $date], 0, 1, 0, 4) // [key, index, limit, offset, iterator = LE]
116
            ->getData();
117
118
        if (count($rows)) {
119
            $state = $this->mapper->findOne('_override_aggregate', [
120
                'entity' => $entity,
121
                'id' => $id,
122
                'begin' => $rows[0][2]
123
            ]);
124
            if (!$state->end || $state->end >= $date) {
125
                return $state->data;
126
            }
127
        }
128
129
        return [];
130
    }
131
132
    private function updateState($params)
133
    {
134
        $params = [
135
            'entity' => $params['entity'],
136
            'id'     => $params['id'],
137
        ];
138
139
        $timeaxis = [];
140
        $changeaxis = [];
141
142
        foreach ($this->mapper->find('_override', $params) as $i => $override) {
143
            foreach (['begin', 'end'] as $field) {
144
                if (!array_key_exists($override->$field, $timeaxis)) {
145
                    $timeaxis[$override->$field] = [
146
                        'begin' => $override->$field,
147
                        'end'   => $override->$field,
148
                        'data'  => [],
149
                    ];
150
                }
151
            }
152
153
            if (!array_key_exists($override->timestamp, $changeaxis)) {
154
                $changeaxis[$override->timestamp] = [];
155
            }
156
            $changeaxis[$override->timestamp][] = $override;
157
        }
158
159
        ksort($changeaxis);
160
        ksort($timeaxis);
161
162
        $nextSliceId = null;
163
        foreach (array_reverse(array_keys($timeaxis)) as $timestamp) {
164
            if ($nextSliceId) {
165
                $timeaxis[$timestamp]['end'] = $nextSliceId;
166
            } else {
167
                $timeaxis[$timestamp]['end'] = 0;
168
            }
169
            $nextSliceId = $timestamp;
170
        }
171
172
        foreach ($this->mapper->find('_override_aggregate', $params) as $state) {
173
            $this->mapper->remove($state);
174
        }
175
176
        $states = [];
177
        foreach ($timeaxis as $state) {
178
            foreach ($changeaxis as $overrides) {
179
                foreach ($overrides as $override) {
180
                    if ($override->begin > $state['begin']) {
181
                        // future override
182
                        continue;
183
                    }
184
                    if ($override->end && ($override->end < $state['end'] || !$state['end'])) {
185
                        // complete override
186
                        continue;
187
                    }
188
189
                    $state['data'] = array_merge($state['data'], $override->data);
190
                }
191
            }
192
            if (count($state['data'])) {
193
                $states[] = array_merge($state, $params);
194
            }
195
        }
196
197
        // merge states
198
        $clean = false;
199
        while (!$clean) {
200
            $clean = true;
201
            foreach ($states as $i => $state) {
202
                if (array_key_exists($i+1, $states)) {
203
                    $next = $states[$i+1];
204
                    if (!count(array_diff_assoc($state['data'], $next['data']))) {
205
                        $states[$i]['end'] = $next['end'];
206
                        unset($states[$i+1]);
207
                        $states = array_values($states);
208
                        $clean = false;
209
                        break;
210
                    }
211
                }
212
            }
213
        }
214
215
        foreach ($states as $state) {
216
            $this->mapper->create('_override_aggregate', $state);
217
        }
218
    }
219
220
    private function getTimestamp($string)
221
    {
222
        if (!array_key_exists($string, $this->timestamps)) {
223
            $this->timestamps[$string] = Carbon::parse($string)->timestamp;
224
        }
225
        return $this->timestamps[$string];
226
    }
227
}
228