Issues (1369)

classes/General/LogCounterShrinker.php (4 issues)

1
<?php
2
/**
3
 * Created by Gorlum 17.10.2017 8:36
4
 */
5
6
namespace General;
7
8
use Core\GlobalContainer;
9
use \DBAL\DbMysqliResultIterator;
0 ignored issues
show
The type \DBAL\DbMysqliResultIterator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
11
/**
12
 * Class LogCounterShrinker
13
 *
14
 * This class shrinking user visit log (table `counter`) by merging conclusive records with same signature
15
 *
16
 * @package General
17
 */
18
class LogCounterShrinker extends VisitMerger {
19
  const RESERVE_WEEKS = 0; // How much weeks of logs left unshrinked
20
  const BATCH_DELETE_PER_LOOP = 10000;
21
  const BATCH_UPDATE_PER_LOOP = 10000;
22
  const BATCH_DELETE_PER_QUERY = 25;
23
24
  protected $batchSize = 10000;
25
26
  /**
27
   * @var array
28
   */
29
  protected $batchDelete = [];
30
  /**
31
   * @var VisitAccumulator[] $batchUpdate
32
   */
33
  protected $batchUpdate = [];
34
35
  /**
36
   * Records skipped in current batch - not changed during process
37
   *
38
   * @var int $batchSkipped
39
   */
40
  protected $batchSkipped = 0;
41
  protected $totalProcessed = 0;
42
43
44
  public function __construct(GlobalContainer $gc) {
45
    parent::__construct($gc);
46
  }
47
48
  /**
49
   * @param string           $sign
50
   * @param VisitAccumulator $logRecord
51
   *
52
   * @return bool
53
   */
54
55
  /**
56
   * @return DbMysqliResultIterator
57
   */
58
  protected function buildIterator() {
59
    return $this->gc->db->selectIterator(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->gc->db->se...'.$this->batchSize.';') returns the type DBAL\DbMysqliResultIterator which is incompatible with the documented return type \DBAL\DbMysqliResultIterator.
Loading history...
60
      "SELECT * 
61
        FROM `{{counter}}`
62
        WHERE `visit_time` < DATE_SUB(NOW(), INTERVAL " . static::RESERVE_WEEKS . " WEEK)
63
        AND `counter_id` > {$this->batchEnd} 
64
        ORDER BY `visit_time`, `counter_id` 
65
        LIMIT {$this->batchSize};"
66
    );
67
  }
68
69
70
  /**
71
   * @inheritdoc
72
   */
73
  public function process($cutTails = true) {
74
    ini_set('memory_limit', '1000M');
75
76
    $result = [];
77
78
    while (($iter = $this->buildIterator()) && $iter->count()) {
79
      $this->setIterator($iter);
80
81
      parent::process(false);
82
83
      $this->batchSave();
84
85
      $this->totalProcessed += $this->batchProcessed;
86
87
      if ($this->prevBatchStart == $this->batchStart && $this->prevBatchEnd == $this->batchEnd) {
88
        $result = [
89
          'STATUS'  => ERR_WARNING,
90
          'MESSAGE' => "{Зациклились с размером блока {$this->batchSize} - [{$this->batchStart},{$this->batchEnd}].",
91
        ];
92
        break;
93
      }
94
95
      if ($this->batchProcessed < $this->batchSize) {
96
        $result = [
97
          'STATUS'  => ERR_WARNING,
98
          'MESSAGE' => "{Размер текущего блока {$this->batchProcessed} меньше максимального {$this->batchSize} между ID [{$this->batchStart},{$this->batchEnd}].",
99
        ];
100
        break;
101
      }
102
    }
103
104
    $this->cutTails();
105
    $this->batchSave(true);
106
107
    if (is_array($result)) {
0 ignored issues
show
The condition is_array($result) is always true.
Loading history...
108
      $result['MESSAGE'] .= " {Обработано} {$this->totalProcessed}, {пропущено} {$this->batchSkipped}";
109
    }
110
111
    return $result;
112
  }
113
114
  protected function batchSave($forceSave = false) {
115
    $this->gc->db->transactionStart();
116
117
    if (count($this->batchDelete) >= static::BATCH_DELETE_PER_LOOP || $forceSave) {
118
      $this->deleteMergedRecords($this->batchDelete);
119
      $this->batchDelete = [];
120
    }
121
122
    if (count($this->batchUpdate) >= static::BATCH_UPDATE_PER_LOOP || $forceSave) {
123
      foreach ($this->batchUpdate as $record) {
124
        if (!$record->isChanged()) {
125
          $this->batchSkipped++;
126
          continue;
127
        }
128
129
        $this->addMoreTime();
130
        $this->gc->db->doQueryFast(
131
          'UPDATE `{{counter}}`
132
          SET ' .
133
          '`visit_length` = ' . $record->length . ',' .
134
          '`hits` = ' . $record->hits .
135
          ' WHERE `counter_id` = ' . $record->counterId . ';'
136
        );
137
      }
138
      $this->batchUpdate = [];
139
    }
140
141
    $this->gc->db->transactionCommit();
142
  }
143
144
  protected function flushVisit($sign) {
145
    if (!empty($this->data[$sign])) {
146
      $this->batchUpdate[] = $this->data[$sign];
147
    }
148
149
    if (!empty($this->mergedIds[$sign]) && is_array($this->mergedIds[$sign])) {
150
      $this->batchDelete = array_merge($this->batchDelete, $this->mergedIds[$sign]);
151
    }
152
  }
153
154
155
  /**
156
   * @param array $array
157
   */
158
  protected function deleteMergedRecords($array) {
159
    if (!is_array($array) || empty($array)) {
0 ignored issues
show
The condition is_array($array) is always true.
Loading history...
160
      return;
161
    }
162
163
    // Batch delete
164
    $i = 0;
165
    $tempArray = [];
166
    foreach ($array as $recordDeleteId) {
167
      $tempArray[] = $recordDeleteId;
168
      if ($i++ > static::BATCH_DELETE_PER_QUERY) {
169
        $this->addMoreTime();
170
        $this->dbDeleteExecute($tempArray);
171
        $i = 0;
172
      }
173
    }
174
175
    // Emptying possible tails
176
    if (!empty($tempArray)) {
177
      $this->dbDeleteExecute($tempArray);
178
    }
179
  }
180
181
  /**
182
   * @param array $toDeleteArray
183
   */
184
  protected function dbDeleteExecute(&$toDeleteArray) {
185
    $this->gc->db->doQueryFast('DELETE FROM `{{counter}}` WHERE `counter_id` IN (' . implode(',', $toDeleteArray) . ')');
186
    $toDeleteArray = [];
187
  }
188
189
}
190