Completed
Push — work-fleets ( f81083...2b6e2a )
by SuperNova.WS
06:05
created

SnDbCachedOperator   C

Complexity

Total Complexity 61

Size/Duplication

Total Lines 456
Duplicated Lines 7.46 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 14
Bugs 0 Features 0
Metric Value
c 14
b 0
f 0
dl 34
loc 456
rs 6.018
ccs 0
cts 194
cp 0
wmc 61
lcom 1
cbo 4

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A db_del_record_by_id() 0 18 4
A db_get_record_by_id() 0 6 3
B db_del_record_list() 0 18 5
D db_get_record_list() 0 112 26
B db_upd_record_by_id() 0 33 6
B db_upd_record_list() 0 24 5
A db_upd_record_list_DANGER() 0 3 1
A db_ins_record() 17 17 3
A db_ins_field_set() 17 17 3
A db_lock_tables() 0 6 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SnDbCachedOperator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SnDbCachedOperator, and based on these observations, apply Extract Interface, too.

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->doDeleteRow($table_name, array($id_field => $safe_record_id,))
129
    ) {
130
      // Обновляем данные только если ряд был затронут
131
      if ($this->db->db_affected_rows()) {
132
        $this->snCache->cache_unset($location_type, $safe_record_id);
133
      }
134
    }
135
136
    return $result;
137
  }
138
139
  /**
140
   * @param int   $location_type
141
   * @param array $condition
142
   *
143
   * @return array|bool|mysqli_result|null
144
   */
145
  public function db_del_record_list($location_type, $condition) {
146
    if (!is_array($condition) || empty($condition)) {
147
      return false;
148
    }
149
150
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
151
152
    if ($result = $this->db->doDeleteWhere($table_name, $condition)) {
153
      // Обновляем данные только если ряд был затронут
154
      if ($this->db->db_affected_rows()) {
155
        // Обнуление кэша, потому что непонятно, что поменялось
156
        // TODO - когда будет структурированный $condition можно будет делать только cache_unset по нужным записям
157
        $this->snCache->cache_clear($location_type);
158
      }
159
    }
160
161
    return $result;
162
  }
163
164
  /**
165
   * Возвращает информацию о записи по её ID
166
   *
167
   * @param int       $location_type
168
   * @param int|array $record_id_unsafe
169
   *    <p>int - ID записи</p>
170
   *    <p>array - запись пользователя с установленным полем P_ID</p>
171
   * @param bool      $for_update @deprecated
172
   * @param string    $fields @deprecated список полей или '*'/'' для всех полей
173
   * @param bool      $skip_lock Указывает на то, что не нужно блокировать запись //TODO и не нужно сохранять в кэше
174
   *
175
   * @return array|false
176
   *    <p>false - Нет записи с указанным ID</p>
177
   *    <p>array - запись</p>
178
   */
179
  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...
180
    $id_field = static::$location_info[$location_type][P_ID];
181
    $record_id_safe = idval(is_array($record_id_unsafe) && isset($record_id_unsafe[$id_field]) ? $record_id_unsafe[$id_field] : $record_id_unsafe);
182
183
    return $this->db_get_record_list($location_type, "`{$id_field}` = {$record_id_safe}", true, false);
184
  }
185
186
  /**
187
   * @param              $location_type
188
   * @param string|array $filter
189
   * @param bool         $fetch
190
   * @param bool         $no_return
191
   *
192
   * @return bool|mixed
193
   */
194
  // TODO - Change $filter to only array class
195
  public function db_get_record_list($location_type, $filter = '', $fetch = false, $no_return = false) {
196
    if(is_array($filter)) {
197
      // TODO - TEMPORARY
198
      $filterString = array();
199
      if(!empty($filter)) {
200
        foreach ($filter as $key => $value) {
201
          if(!is_int($key)) {
202
            $value = "`$key` = '" . $this->db->db_escape($value). "'";
203
          }
204
          $filterString[$key] = $value;
205
        }
206
      }
207
208
      $filterString = implode(',', $filterString);
209
    } else {
210
      $filterString =  $filter;
211
    }
212
213
214
    if ($this->snCache->isQueryCacheByLocationAndFilterEmpty($location_type, $filterString)) {
215
      $this->snCache->queryCacheResetByLocationAndFilter($location_type, $filterString);
216
217
      $location_info = &static::$location_info[$location_type];
218
      $id_field = $location_info[P_ID];
219
220
      if ($this->db->getTransaction()->check(false)) {
221
        // Проходим по всем родителям данной записи
222
        foreach ($location_info[P_OWNER_INFO] as $owner_data) {
223
          $owner_location_type = $owner_data[P_LOCATION];
224
          $parent_id_list = array();
225
          // Выбираем родителей данного типа и соответствующие ИД текущего типа
226
          // TODO - сводить в одну таблицу всех родителей и только потом делать SELECT!!!
227
          if (!empty($filter) && is_array($filter)) {
228
            $query = $this->db->doSelectDanger(
229
              $location_info[P_TABLE_NAME],
230
              array("distinct({{{$location_info[P_TABLE_NAME]}}}.{$owner_data[P_OWNER_FIELD]}) AS parent_id"),
231
              $filter,
232
              // Always selecting all records for correctly perform FILTER locks
233
               $fetch ? DB_RECORD_ONE : DB_RECORDS_ALL,
234
//              DB_RECORDS_ALL,
235
              DB_SELECT_PLAIN
236
            );
237
          } else {
238
            $query = $this->db->doSelect(
239
              "SELECT
240
              distinct({{{$location_info[P_TABLE_NAME]}}}.{$owner_data[P_OWNER_FIELD]}) AS parent_id
241
            FROM {{{$location_info[P_TABLE_NAME]}}}" .
242
              ($filter ? ' WHERE ' . $filter : '') .
243
              ($fetch ? ' LIMIT 1' : ''));
244
          }
245
          while ($row = db_fetch($query)) {
246
            // Исключаем из списка родительских ИД уже заблокированные записи
247
            if (!$this->snCache->cache_lock_get($owner_location_type, $row['parent_id'])) {
248
              $parent_id_list[$row['parent_id']] = $row['parent_id'];
249
            }
250
          }
251
252
          // Если все-таки какие-то записи еще не заблокированы - вынимаем текущие версии из базы
253
          if (!empty($parent_id_list)) {
254
            $indexes_str = implode(',', $parent_id_list);
255
            $parent_id_field = static::$location_info[$owner_location_type][P_ID];
256
            $this->db_get_record_list($owner_location_type,
257
              $parent_id_field . (
258
                count($parent_id_list) > 1
259
                ? " IN ({$indexes_str})"
260
                : " = {$indexes_str}"
261
              ),
262
              $fetch,
263
              true
264
            );
265
          }
266
        }
267
      }
268
269
      // TODO - REWRITE
270
      if (!empty($filter) && is_array($filter)) {
271
        $query = $this->db->doSelectDanger(
272
          $location_info[P_TABLE_NAME],
273
          array('*'),
274
          $filter,
275
          DB_RECORDS_ALL,
276
          DB_SELECT_FOR_UPDATE
277
        );
278
      } else {
279
        $query = $this->db->doSelect(
280
          "SELECT * FROM {{{$location_info[P_TABLE_NAME]}}}" .
281
          (($filter = trim($filter)) ? " WHERE {$filter}" : '')
282
          . " FOR UPDATE"
283
        );
284
      }
285
      while ($row = db_fetch($query)) {
286
        // Caching record in row cache
287
        $this->snCache->cache_set($location_type, $row);
288
        // Making ref to cached record in query cache
289
        $this->snCache->queryCacheSetByFilter($location_type, $filterString, $row[$id_field]);
290
      }
291
    }
292
293
    if ($no_return) {
294
      return true;
295
    } else {
296
      $result = false;
297
      foreach ($this->snCache->getQueriesByLocationAndFilter($location_type, $filterString) as $key => $value) {
298
        $result[$key] = $value;
299
        if ($fetch) {
300
          break;
301
        }
302
      }
303
304
      return $fetch ? (is_array($result) ? reset($result) : false) : $result;
305
    }
306
  }
307
308
  /**
309
   * @param int   $location_type
310
   * @param int   $record_id
311
   * @param array $set - SQL SET structure
312
   * @param array $adjust - SQL ADJUST structure
313
   *
314
   * @return array|bool|mysqli_result|null
315
   */
316
  public function db_upd_record_by_id($location_type, $record_id, $set, $adjust) {
317
    if (!($record_id = idval($record_id)) || (empty($set) && empty($adjust))) {
318
      return false;
319
    }
320
321
    $id_field = static::$location_info[$location_type][P_ID];
322
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
323
    // TODO Как-то вернуть может быть LIMIT 1 ?
324
    $result = $this->db->doUpdateRowAdjust(
325
      $table_name,
326
      $set,
327
      $adjust,
328
      array(
329
        $id_field => $record_id
330
      )
331
    );
332
333
    if ($result) {
334
      if ($this->db->db_affected_rows()) {
335
        // Обновляем данные только если ряд был затронут
336
        // TODO - переделать под работу со структурированными $set
337
338
        // Тут именно так, а не cache_unset - что бы в кэшах автоматически обновилась запись. Будет нужно на будущее
339
        //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...
340
        $this->snCache->cacheUnsetElement($location_type, $record_id);
341
        // Вытаскиваем обновленную запись
342
        $this->db_get_record_by_id($location_type, $record_id);
343
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
344
      }
345
    }
346
347
    return $result;
348
  }
349
350
351
  /**
352
   * @param int   $location_type
353
   * @param array $set
354
   * @param array $adjust
355
   *
356
   * @param array $where
357
   *
358
   * @return array|bool|mysqli_result|null
359
   */
360
  public function db_upd_record_list($location_type, $set, $adjust, $where, $whereDanger = array()) {
361
    if (empty($set) && empty($adjust)) {
362
      return false;
363
    }
364
365
    $result = $this->db->doUpdateTableAdjust(
366
      static::$location_info[$location_type][P_TABLE_NAME],
367
      $set,
368
      $adjust,
369
      $where,
370
      $whereDanger
371
    );
372
373
    if ($result) {
374
      // Обновляем данные только если ряд был затронут
375
      if ($this->db->db_affected_rows()) {
376
        // Поскольку нам неизвестно, что и как обновилось - сбрасываем кэш этого типа полностью
377
        // TODO - когда будет структурированный $condition и $set - перепаковывать данные
378
        $this->snCache->cache_clear($location_type, true);
379
      }
380
    }
381
382
    return $result;
383
  }
384
385
  /**
386
   * This calls is DANGER 'cause $condition contains direct variable injections and should be rewrote
387
   *
388
   * This call just a proxy to easier to locate code for rewrite
389
   *
390
   * @param int   $location_type
391
   * @param array $set
392
   * @param array $adjust
393
   * @param array $where
394
   * @param array $whereDanger
395
   *
396
   * @return array|bool|mysqli_result|null
397
   * @deprecated
398
   */
399
  public function db_upd_record_list_DANGER($location_type, $set, $adjust, $where, $whereDanger) {
400
    return $this->db_upd_record_list($location_type, $set, $adjust, $where, $whereDanger);
401
  }
402
403
404
  /**
405
   * @param int   $location_type
406
   * @param array $set
407
   *
408
   * @return array|bool|false|mysqli_result|null
409
   */
410 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...
411
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
412
    $result = $this->db->doInsertSet($table_name, $set);
413
    if ($result) {
414
      if ($this->db->db_affected_rows()) // Обновляем данные только если ряд был затронут
415
      {
416
        $record_id = $this->db->db_insert_id();
417
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
418
        $result = $this->db_get_record_by_id($location_type, $record_id);
419
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
420
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
421
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
422
      }
423
    }
424
425
    return $result;
426
  }
427
428
429 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...
430
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
431
    $result = $this->db->doInsertSet($table_name, $field_set);
432
    if ($result) {
433
      if ($this->db->db_affected_rows()) {
434
        // Обновляем данные только если ряд был затронут
435
        $record_id = $this->db->db_insert_id();
436
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
437
        $result = $this->db_get_record_by_id($location_type, $record_id);
438
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
439
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
440
        $this->snCache->cache_clear($location_type, false); // Мягкий сброс - только $queries
441
      }
442
    }
443
444
    return $result;
445
  }
446
447
448
  /**
449
   * Блокирует указанные таблицу/список таблиц
450
   *
451
   * @param string|array $tables Таблица/список таблиц для блокировки. Названия таблиц - без префиксов
452
   * <p>string - название таблицы для блокировки</p>
453
   * <p>array - массив, где ключ - имя таблицы, а значение - условия блокировки элементов</p>
454
   */
455
  public function db_lock_tables($tables) {
456
    $tables = is_array($tables) ? $tables : array($tables => '');
457
    foreach ($tables as $table_name => $condition) {
458
      $this->db->doSelect("SELECT 1 FROM {{{$table_name}}}" . ($condition ? ' WHERE ' . $condition : ''));
459
    }
460
  }
461
}
462