Completed
Push — work-fleets ( 1920ae...cfff9f )
by SuperNova.WS
07:02
created

SnDbCachedOperator::db_ins_field_set()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 17
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 3
b 0
f 0
nc 3
nop 2
dl 17
loc 17
rs 9.4285
ccs 0
cts 12
cp 0
crap 12
1
<?php
2
3
/**
4
 * Created by Gorlum 01.08.2016 21:40
5
 */
6
class SnDbCachedOperator {
7
  // Кэш индексов - ключ MD5-строка от суммы ключевых строк через | - менять | на что-то другое перед поиском и назад - после поиска
8
  // Так же в индексах могут быть двойные вхождения - например, названия планет да и вообще
9
  // Придумать спецсимвол для NULL
10
11
  /*
12
  TODO Кэш:
13
  1. Всегда дешевле использовать процессор, чем локальную память
14
  2. Всегда дешевле использовать локальную память, чем общую память всех процессов
15
  3. Всегда дешевле использовать общую память всех процессов, чем обращаться к БД
16
17
  Кэш - многоуровневый: локальная память-общая память-БД
18
  БД может быть сверхкэширующей - см. HyperNova. Это реализуется на уровне СН-драйвера БД
19
  Предусмотреть вариант, когда уровни кэширования совпадают, например когда нет xcache и используется общая память
20
  */
21
22
  // TODO Автоматически заполнять эту таблицу. В случае кэша в памяти - делать show table при обращении к таблице
23
  public static $location_info = array(
24
    LOC_USER => array(
25
      P_TABLE_NAME => 'users',
26
      P_ID         => 'id',
27
      P_OWNER_INFO => array(),
28
    ),
29
30
    LOC_PLANET => array(
31
      P_TABLE_NAME => 'planets',
32
      P_ID         => 'id',
33
      P_OWNER_INFO => array(
34
        LOC_USER => array(
35
          P_LOCATION    => LOC_USER,
36
          P_OWNER_FIELD => 'id_owner',
37
        ),
38
      ),
39
    ),
40
41
    LOC_UNIT => array(
42
      P_TABLE_NAME => 'unit',
43
      P_ID         => 'unit_id',
44
      P_OWNER_INFO => array(
45
        LOC_USER => array(
46
          P_LOCATION    => LOC_USER,
47
          P_OWNER_FIELD => 'unit_player_id',
48
        ),
49
      ),
50
    ),
51
52
    LOC_QUE => array(
53
      P_TABLE_NAME => 'que',
54
      P_ID         => 'que_id',
55
      P_OWNER_INFO => array(
56
        array(
57
          P_LOCATION    => LOC_USER,
58
          P_OWNER_FIELD => 'que_player_id',
59
        ),
60
61
        array(
62
          P_LOCATION    => LOC_PLANET,
63
          P_OWNER_FIELD => 'que_planet_id_origin',
64
        ),
65
66
        array(
67
          P_LOCATION    => LOC_PLANET,
68
          P_OWNER_FIELD => 'que_planet_id',
69
        ),
70
      ),
71
    ),
72
73
    LOC_FLEET => array(
74
      P_TABLE_NAME => 'fleets',
75
      P_ID         => 'fleet_id',
76
      P_OWNER_INFO => array(
77
        array(
78
          P_LOCATION    => LOC_USER,
79
          P_OWNER_FIELD => 'fleet_owner',
80
        ),
81
82
        array(
83
          P_LOCATION    => LOC_USER,
84
          P_OWNER_FIELD => 'fleet_target_owner',
85
        ),
86
87
        array(
88
          P_LOCATION    => LOC_PLANET,
89
          P_OWNER_FIELD => 'fleet_start_planet_id',
90
        ),
91
92
        array(
93
          P_LOCATION    => LOC_PLANET,
94
          P_OWNER_FIELD => 'fleet_end_planet_id',
95
        ),
96
      ),
97
    ),
98
  );
99
100
  /**
101
   * @var db_mysql $db
102
   */
103
  protected $db;
104
105
  /**
106
   * @var \SnCache $snCache
107
   */
108
  protected $snCache;
109
110
  /**
111
   * SnDbCachedOperator constructor.
112
   *
113
   * @param \Common\GlobalContainer $gc
114
   */
115
  public function __construct($gc) {
116
    $this->db = $gc->db;
0 ignored issues
show
Documentation Bug introduced by
It seems like $gc->db can also be of type object<Closure>. However, the property $db is declared as type object<db_mysql>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
117
    $this->snCache = $gc->snCache;
0 ignored issues
show
Documentation Bug introduced by
It seems like $gc->snCache can also be of type object<Closure>. However, the property $snCache is declared as type object<SnCache>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
118
  }
119
120
  public function db_del_record_by_id($location_type, $safe_record_id) {
121
    if (!($safe_record_id = idval($safe_record_id))) {
122
      return false;
123
    }
124
125
    $id_field = static::$location_info[$location_type][P_ID];
126
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
127
    // TODO - lock value in cache
128
    if ($result = $this->db->doDeleteRowWhere($table_name, array($id_field => $safe_record_id))) {
129
      // Обновляем данные только если ряд был затронут
130
      if ($this->db->db_affected_rows()) {
131
        $this->snCache->cache_unset($location_type, $safe_record_id);
132
      }
133
    }
134
135
    return $result;
136
  }
137
138
  /**
139
   * @param int   $location_type
140
   * @param array $condition
141
   *
142
   * @return array|bool|mysqli_result|null
143
   */
144
  public function db_del_record_list($location_type, $condition) {
145
    if (!is_array($condition) || empty($condition)) {
146
      return false;
147
    }
148
149
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
150
151
    if ($result = $this->db->doDeleteWhere($table_name, $condition)) {
152
      // Обновляем данные только если ряд был затронут
153
      if ($this->db->db_affected_rows()) {
154
        // Обнуление кэша, потому что непонятно, что поменялось
155
        // TODO - когда будет структурированный $condition можно будет делать только cache_unset по нужным записям
156
        $this->snCache->cache_clear($location_type);
157
      }
158
    }
159
160
    return $result;
161
  }
162
163
  /**
164
   * Возвращает информацию о записи по её ID
165
   *
166
   * @param int       $location_type
167
   * @param int|array $record_id_unsafe
168
   *    <p>int - ID записи</p>
169
   *    <p>array - запись пользователя с установленным полем P_ID</p>
170
   * @param bool      $for_update @deprecated
171
   * @param string    $fields @deprecated список полей или '*'/'' для всех полей
172
   * @param bool      $skip_lock Указывает на то, что не нужно блокировать запись //TODO и не нужно сохранять в кэше
173
   *
174
   * @return array|false
175
   *    <p>false - Нет записи с указанным ID</p>
176
   *    <p>array - запись</p>
177
   */
178
  public function db_get_record_by_id($location_type, $record_id_unsafe, $for_update = false, $fields = '*', $skip_lock = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $for_update 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...
Unused Code introduced by
The parameter $fields 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...
Unused Code introduced by
The parameter $skip_lock 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...
179
    $id_field = static::$location_info[$location_type][P_ID];
180
    $record_id_safe = idval(is_array($record_id_unsafe) && isset($record_id_unsafe[$id_field]) ? $record_id_unsafe[$id_field] : $record_id_unsafe);
181
182
    return classSupernova::$gc->cacheOperator->db_get_record_list($location_type, "`{$id_field}` = {$record_id_safe}", true, false);
0 ignored issues
show
Bug introduced by
The method db_get_record_list does only exist in SnDbCachedOperator, but not in Closure.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
183
  }
184
185
  public function db_get_record_list($location_type, $filter = '', $fetch = false, $no_return = false) {
186
    if ($this->snCache->isQueryCacheByLocationAndFilterEmpty($location_type, $filter)) {
187
      $this->snCache->queryCacheResetByLocationAndFilter($location_type, $filter);
188
189
      $location_info = &static::$location_info[$location_type];
190
      $id_field = $location_info[P_ID];
191
192
      if ($this->db->getTransaction()->check(false)) {
193
        // Проходим по всем родителям данной записи
194
        foreach ($location_info[P_OWNER_INFO] as $owner_data) {
195
          $owner_location_type = $owner_data[P_LOCATION];
196
          $parent_id_list = array();
197
          // Выбираем родителей данного типа и соответствующие ИД текущего типа
198
          $query = $this->db->doSelect(
199
            "SELECT
200
              distinct({{{$location_info[P_TABLE_NAME]}}}.{$owner_data[P_OWNER_FIELD]}) AS parent_id
201
            FROM {{{$location_info[P_TABLE_NAME]}}}" .
202
            ($filter ? ' WHERE ' . $filter : '') .
203
            ($fetch ? ' LIMIT 1' : ''));
204
          while($row = db_fetch($query)) {
205
            // Исключаем из списка родительских ИД уже заблокированные записи
206
            if (!$this->snCache->cache_lock_get($owner_location_type, $row['parent_id'])) {
207
              $parent_id_list[$row['parent_id']] = $row['parent_id'];
208
            }
209
          }
210
211
          // Если все-таки какие-то записи еще не заблокированы - вынимаем текущие версии из базы
212
          if ($indexes_str = implode(',', $parent_id_list)) {
213
            $parent_id_field = static::$location_info[$owner_location_type][P_ID];
214
            classSupernova::$gc->cacheOperator->db_get_record_list($owner_location_type,
0 ignored issues
show
Bug introduced by
The method db_get_record_list does only exist in SnDbCachedOperator, but not in Closure.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
215
              $parent_id_field . (count($parent_id_list) > 1 ? " IN ({$indexes_str})" : " = {$indexes_str}"), $fetch, true);
216
          }
217
        }
218
      }
219
220
      $query = $this->db->doSelect(
221
        "SELECT * FROM {{{$location_info[P_TABLE_NAME]}}}" .
222
        (($filter = trim($filter)) ? " WHERE {$filter}" : '')
223
        . " FOR UPDATE"
224
      );
225
      while($row = db_fetch($query)) {
226
        // Caching record in row cache
227
        $this->snCache->cache_set($location_type, $row);
228
        // Making ref to cached record in query cache
229
        $this->snCache->queryCacheSetByFilter($location_type, $filter, $row[$id_field]);
230
      }
231
    }
232
233
    if ($no_return) {
234
      return true;
235
    } else {
236
      $result = false;
237
      foreach ($this->snCache->getQueriesByLocationAndFilter($location_type, $filter) as $key => $value) {
238
        $result[$key] = $value;
239
        if ($fetch) {
240
          break;
241
        }
242
      }
243
244
      return $fetch ? (is_array($result) ? reset($result) : false) : $result;
245
    }
246
  }
247
248
  /**
249
   * @param int    $location_type
250
   * @param int    $record_id
251
   * @param string $set - SQL SET structure
252
   *
253
   * @return array|bool|mysqli_result|null
254
   */
255
  public function db_upd_record_by_id($location_type, $record_id, $set) {
256
    if (!($record_id = idval($record_id)) || !($set = trim($set))) {
257
      return false;
258
    }
259
260
    $id_field = static::$location_info[$location_type][P_ID];
261
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
262
    // TODO Как-то вернуть может быть LIMIT 1 ?
263
    if ($result = $this->db->doUpdateComplex("UPDATE {{{$table_name}}} SET {$set} WHERE `{$id_field}` = {$record_id}")) {
264
      if ($this->db->db_affected_rows()) {
265
        // Обновляем данные только если ряд был затронут
266
        // TODO - переделать под работу со структурированными $set
267
268
        // Тут именно так, а не cache_unset - что бы в кэшах автоматически обновилась запись. Будет нужно на будущее
269
        //static::$data[$location_type][$record_id] = null;
0 ignored issues
show
Unused Code Comprehensibility introduced by
79% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270
        $this->snCache->cacheUnsetElement($location_type, $record_id);
271
        // Вытаскиваем обновленную запись
272
        $this->db_get_record_by_id($location_type, $record_id);
273
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
274
      }
275
    }
276
277
    return $result;
278
  }
279
280
  /**
281
   * @param int    $location_type
282
   * @param string $set
283
   * @param string $condition
284
   *
285
   * @return array|bool|mysqli_result|null
286
   */
287
  public function db_upd_record_list($location_type, $set, $condition) {
288
    if (!($set = trim($set))) {
289
      return false;
290
    }
291
292
    $condition = trim($condition);
293
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
294
295
    if ($result = $this->db->doUpdateComplex("UPDATE {{{$table_name}}} SET " . $set . ($condition ? ' WHERE ' . $condition : ''))) {
296
297
      if ($this->db->db_affected_rows()) { // Обновляем данные только если ряд был затронут
298
        // Поскольку нам неизвестно, что и как обновилось - сбрасываем кэш этого типа полностью
299
        // TODO - когда будет структурированный $condition и $set - перепаковывать данные
300
        $this->snCache->cache_clear($location_type, true);
301
      }
302
    }
303
304
    return $result;
305
  }
306
307
  /**
308
   * @param int   $location_type
309
   * @param array $set
310
   *
311
   * @return array|bool|false|mysqli_result|null
312
   */
313 View Code Duplication
  public function db_ins_record($location_type, $set) {
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...
314
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
315
    $result = $this->db->doInsertSet($table_name, $set);
316
    if ($result) {
317
      if ($this->db->db_affected_rows()) // Обновляем данные только если ряд был затронут
318
      {
319
        $record_id = $this->db->db_insert_id();
320
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
321
        $result = $this->db_get_record_by_id($location_type, $record_id);
322
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
323
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
324
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
325
      }
326
    }
327
328
    return $result;
329
  }
330
331
332 View Code Duplication
  public function db_ins_field_set($location_type, $field_set) {
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...
333
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
334
    $result = $this->db->doInsertSet($table_name, $field_set);
335
    if ($result) {
336
      if ($this->db->db_affected_rows()) {
337
        // Обновляем данные только если ряд был затронут
338
        $record_id = $this->db->db_insert_id();
339
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
340
        $result = $this->db_get_record_by_id($location_type, $record_id);
341
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
342
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
343
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
344
      }
345
    }
346
347
    return $result;
348
  }
349
350
351
  /**
352
   * Блокирует указанные таблицу/список таблиц
353
   *
354
   * @param string|array $tables Таблица/список таблиц для блокировки. Названия таблиц - без префиксов
355
   * <p>string - название таблицы для блокировки</p>
356
   * <p>array - массив, где ключ - имя таблицы, а значение - условия блокировки элементов</p>
357
   */
358
  public function db_lock_tables($tables) {
359
    $tables = is_array($tables) ? $tables : array($tables => '');
360
    foreach ($tables as $table_name => $condition) {
361
      $this->db->doSelect("SELECT 1 FROM {{{$table_name}}}" . ($condition ? ' WHERE ' . $condition : ''));
362
    }
363
  }
364
}
365