Issues (1369)

classes/General/VisitMerger.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * Created by Gorlum 17.10.2017 10:20
4
 */
5
6
namespace General;
7
8
use Common\EmptyCountableIterator;
9
use Core\GlobalContainer;
10
use Common\Interfaces\ICountableIterator;
11
12
/**
13
 * Class VisitMerger
14
 *
15
 * Class merges supplied visits with provided criterias
16
 *
17
 * @package General
18
 */
19
abstract class VisitMerger {
20
  const PLAYER_ACTIVITY_MAX_INTERVAL = PERIOD_MINUTE_15;
21
22
  public $maxInterval = self::PLAYER_ACTIVITY_MAX_INTERVAL;
23
  /**
24
   * Time to extend PHP execution time each loop
25
   *
26
   * @var int $_extendTime
27
   */
28
  protected $_extendTime = 30;
29
30
31
  /**
32
   * @var \Core\GlobalContainer $gc
33
   */
34
  protected $gc;
35
36
  /**
37
   * @var VisitAccumulator[] $data
38
   */
39
  protected $data = [];
40
  /**
41
   * List of IDs of log record that was merged with upper ones
42
   *
43
   * @var array $mergedIds
44
   */
45
  protected $mergedIds = [];
46
47
  /**
48
   * @var ICountableIterator|null
49
   */
50
  protected $iterator = null;
51
52
  protected $prevBatchStart = 0;
53
  protected $prevBatchEnd = 0;
54
  protected $batchStart = 0;
55
  protected $batchEnd = 0;
56
  /**
57
   * Records processed in current batch - read from DB
58
   *
59
   * @var int $batchProcessed
60
   */
61
  protected $batchProcessed = 0;
62
63
  /**
64
   * General constructor.
65
   *
66
   * @param \Core\GlobalContainer $gc
67
   */
68
  public function __construct(GlobalContainer $gc) {
69
    $this->gc = $gc;
70
    $this->iterator = new EmptyCountableIterator();
71
  }
72
73
  /**
74
   * @param ICountableIterator $iterator
75
   */
76
  public function setIterator(ICountableIterator $iterator) {
77
    $this->iterator = $iterator;
78
  }
79
80
  /**
81
   * Is supplied log record could be merged to data array, indicated by $sign
82
   *
83
   * Can (and should!) be overrode by successors to provide different groupping mechanic
84
   *
85
   * @param string           $sign
86
   * @param VisitAccumulator $logRecord
87
   *
88
   * @return bool
89
   */
90
  protected function isSameVisit($sign, $logRecord) {
91
    return $this->data[$sign]->time + $this->data[$sign]->length + $this->maxInterval >= $logRecord->time;
92
  }
93
94
  /**
95
   * Class entry point
96
   *
97
   * @param bool $cutTails - should we cut tails?
98
   *
99
   * @return array - []|['STATUS' => (int)errorLevel, 'MESSAGE' => (string)message]
100
   */
101
  public function process($cutTails = true) {
102
    $this->batchProcessed = 0;
103
104
    $this->prevBatchStart = $this->batchStart;
105
    $this->prevBatchEnd = $this->batchEnd;
106
107
    if ($this->iterator->valid()) {
0 ignored issues
show
The method valid() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
    if ($this->iterator->/** @scrutinizer ignore-call */ valid()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
108
      $logRecord = VisitAccumulator::build($this->iterator->current());
109
      $this->batchStart = $logRecord->counterId;
110
    }
111
112
    foreach ($this->iterator as $row) {
113
      $this->addMoreTime();
114
      $logRecord = VisitAccumulator::build($row);
115
      $this->processRecord($logRecord);
116
      $this->batchProcessed++;
117
    }
118
    $this->batchEnd = isset($logRecord) ? $logRecord->counterId : $this->batchStart;
119
120
    if ($cutTails) {
121
      $this->cutTails();
122
    }
123
124
    return [];
125
  }
126
127
  /**
128
   * @param VisitAccumulator $logRecord
129
   */
130
  protected function processRecord($logRecord) {
131
    $sign = $this->calcSignature($logRecord);
132
133
    if (empty($this->data[$sign])) {
134
      $this->newVisit($sign, $logRecord);
135
    } else {
136
      if ($this->isSameVisit($sign, $logRecord)) {
137
        $this->extendVisit($sign, $logRecord);
138
      } else {
139
        $this->resetVisit($sign, $logRecord);
140
      }
141
    }
142
  }
143
144
  /**
145
   * Cutting tales - working on opened visits after all rows processed
146
   */
147
  protected function cutTails() {
148
    foreach ($this->data as $sign => $tails) {
149
      $this->addMoreTime();
150
      $this->resetVisit($sign, null);
151
    }
152
  }
153
154
  /**
155
   * Getting signature (name of group to which this visit can belong) from logged visit
156
   *
157
   * Signature made from user ID, device ID, browser ID and IPs
158
   *
159
   * @param VisitAccumulator $logRecord
160
   *
161
   * @return string
162
   */
163
  protected function calcSignature($logRecord) {
164
//    return
165
//      $logRecord->userId . '_' .
166
//      $logRecord->deviceId . '_' .
167
//      $logRecord->browserId . '_' .
168
//      $logRecord->ip . '_' .
169
//      $logRecord->proxies;
170
    return $logRecord->userId . '_'  . $logRecord->playerEntryId;
171
  }
172
173
  /**
174
   * Starting new visit from supplied log record
175
   *
176
   * @param string           $sign
177
   * @param VisitAccumulator $logRecord
178
   */
179
  protected function newVisit($sign, $logRecord) {
180
    $this->data[$sign] = $logRecord;
181
    $this->mergedIds[$sign] = [];
182
  }
183
184
185
  /**
186
   * Extending current visit with supplied log record
187
   *
188
   * @param string           $sign
189
   * @param VisitAccumulator $logRecord
190
   */
191
  protected function extendVisit($sign, $logRecord) {
192
    // Adjusting current visit length
193
    $this->data[$sign]->length = max(
194
    // Newly calculated visit length
195
      $logRecord->time - $this->data[$sign]->time + $logRecord->length,
196
      // Fallback to current visit length logged record start later but is shorter then already calculated visit
197
      // Should never happen, honestly
198
      $this->data[$sign]->length
199
    );
200
201
    // Adding hit count
202
    $this->data[$sign]->hits += $logRecord->hits;
203
204
    // Marking current log record for deletion
205
    $this->mergedIds[$sign][] = $logRecord->counterId;
206
  }
207
208
  /**
209
   * Called in resetVisit to perform necessary storage operations for current visit - if any
210
   *
211
   * Doing nothing here - just a callback to easy override in successors
212
   *
213
   * @param string $sign
214
   */
215
  protected function flushVisit($sign) { }
216
217
  /**
218
   * Closing previous visit and starting new one
219
   *
220
   * @param string                $sign
221
   * @param VisitAccumulator|null $logRecord
222
   */
223
  protected function resetVisit($sign, $logRecord) {
224
    // Making any necessary operations on current visit data before they went to thrash
225
    $this->flushVisit($sign);
226
227
    unset($this->mergedIds[$sign]);
228
    // Current row now is a visit start
229
    if ($logRecord === null) {
230
      unset($this->data[$sign]);
231
    } else {
232
      $this->data[$sign] = $logRecord;
233
    }
234
  }
235
236
  protected function addMoreTime() {
237
    $this->_extendTime ? set_time_limit($this->_extendTime) : false;
238
  }
239
240
  protected function resetArrays() {
241
    $this->data = [];
242
    $this->mergedIds = [];
243
  }
244
245
}
246