Completed
Push — work-fleets ( 489db6...8474eb )
by SuperNova.WS
08:27
created

SnDbCachedOperator   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 359
Duplicated Lines 13.65 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 49
loc 359
rs 8.5454
c 1
b 0
f 0
wmc 49
lcom 1
cbo 4

9 Methods

Rating   Name   Duplication   Size   Complexity  
A db_del_record_by_id() 6 16 4
A db_del_record_list() 8 18 4
A db_get_record_by_id() 0 6 3
C db_get_record_list() 0 62 17
B db_upd_record_by_id() 13 24 5
B db_upd_record_list() 0 19 5
A db_ins_record() 11 17 3
B db_ins_field_set() 11 22 4
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
7
class SnDbCachedOperator {
8
  // Кэш индексов - ключ MD5-строка от суммы ключевых строк через | - менять | на что-то другое перед поиском и назад - после поиска
9
  // Так же в индексах могут быть двойные вхождения - например, названия планет да и вообще
10
  // Придумать спецсимвол для NULL
11
12
  /*
13
  TODO Кэш:
14
  1. Всегда дешевле использовать процессор, чем локальную память
15
  2. Всегда дешевле использовать локальную память, чем общую память всех процессов
16
  3. Всегда дешевле использовать общую память всех процессов, чем обращаться к БД
17
18
  Кэш - многоуровневый: локальная память-общая память-БД
19
  БД может быть сверхкэширующей - см. HyperNova. Это реализуется на уровне СН-драйвера БД
20
  Предусмотреть вариант, когда уровни кэширования совпадают, например когда нет xcache и используется общая память
21
  */
22
23
  // TODO Автоматически заполнять эту таблицу. В случае кэша в памяти - делать show table при обращении к таблице
24
  public static $location_info = array(
25
    LOC_USER => array(
26
      P_TABLE_NAME => 'users',
27
      P_ID         => 'id',
28
      P_OWNER_INFO => array(),
29
    ),
30
31
    LOC_PLANET => array(
32
      P_TABLE_NAME => 'planets',
33
      P_ID         => 'id',
34
      P_OWNER_INFO => array(
35
        LOC_USER => array(
36
          P_LOCATION    => LOC_USER,
37
          P_OWNER_FIELD => 'id_owner',
38
        ),
39
      ),
40
    ),
41
42
    LOC_UNIT => array(
43
      P_TABLE_NAME => 'unit',
44
      P_ID         => 'unit_id',
45
      P_OWNER_INFO => array(
46
        LOC_USER => array(
47
          P_LOCATION    => LOC_USER,
48
          P_OWNER_FIELD => 'unit_player_id',
49
        ),
50
      ),
51
    ),
52
53
    LOC_QUE => array(
54
      P_TABLE_NAME => 'que',
55
      P_ID         => 'que_id',
56
      P_OWNER_INFO => array(
57
        array(
58
          P_LOCATION    => LOC_USER,
59
          P_OWNER_FIELD => 'que_player_id',
60
        ),
61
62
        array(
63
          P_LOCATION    => LOC_PLANET,
64
          P_OWNER_FIELD => 'que_planet_id_origin',
65
        ),
66
67
        array(
68
          P_LOCATION    => LOC_PLANET,
69
          P_OWNER_FIELD => 'que_planet_id',
70
        ),
71
      ),
72
    ),
73
74
    LOC_FLEET => array(
75
      P_TABLE_NAME => 'fleets',
76
      P_ID         => 'fleet_id',
77
      P_OWNER_INFO => array(
78
        array(
79
          P_LOCATION    => LOC_USER,
80
          P_OWNER_FIELD => 'fleet_owner',
81
        ),
82
83
        array(
84
          P_LOCATION    => LOC_USER,
85
          P_OWNER_FIELD => 'fleet_target_owner',
86
        ),
87
88
        array(
89
          P_LOCATION    => LOC_PLANET,
90
          P_OWNER_FIELD => 'fleet_start_planet_id',
91
        ),
92
93
        array(
94
          P_LOCATION    => LOC_PLANET,
95
          P_OWNER_FIELD => 'fleet_end_planet_id',
96
        ),
97
      ),
98
    ),
99
  );
100
101
  public static function db_del_record_by_id($location_type, $safe_record_id) {
102
    if (!($safe_record_id = idval($safe_record_id))) {
103
      return false;
104
    }
105
106
    $id_field = static::$location_info[$location_type][P_ID];
107
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
108 View Code Duplication
    if ($result = classSupernova::$db->doDelete("DELETE FROM `{{{$table_name}}}` WHERE `{$id_field}` = {$safe_record_id}")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
109
      // Обновляем данные только если ряд был затронут
110
      if (classSupernova::$db->db_affected_rows()) {
111
        SnCache::cache_unset($location_type, $safe_record_id);
112
      }
113
    }
114
115
    return $result;
116
  }
117
118
  public static function db_del_record_list($location_type, $condition) {
119
    if (!($condition = trim($condition))) {
120
      return false;
121
    }
122
123
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
124
125 View Code Duplication
    if ($result = classSupernova::$db->doDelete("DELETE FROM `{{{$table_name}}}` WHERE {$condition}")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
126
      // Обновляем данные только если ряд был затронут
127
      if (classSupernova::$db->db_affected_rows()) {
128
        // Обнуление кэша, потому что непонятно, что поменялось
129
        // TODO - когда будет структурированный $condition можно будет делать только cache_unset по нужным записям
130
        SnCache::cache_clear($location_type);
131
      }
132
    }
133
134
    return $result;
135
  }
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
  /**
151
   * Возвращает информацию о записи по её ID
152
   *
153
   * @param int       $location_type
154
   * @param int|array $record_id_unsafe
155
   *    <p>int - ID записи</p>
156
   *    <p>array - запись пользователя с установленным полем P_ID</p>
157
   * @param bool      $for_update @deprecated
158
   * @param string    $fields @deprecated список полей или '*'/'' для всех полей
159
   * @param bool      $skip_lock Указывает на то, что не нужно блокировать запись //TODO и не нужно сохранять в кэше
160
   *
161
   * @return array|false
162
   *    <p>false - Нет записи с указанным ID</p>
163
   *    <p>array - запись</p>
164
   */
165
  public static 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...
166
    $id_field = static::$location_info[$location_type][P_ID];
167
    $record_id_safe = idval(is_array($record_id_unsafe) && isset($record_id_unsafe[$id_field]) ? $record_id_unsafe[$id_field] : $record_id_unsafe);
168
169
    return static::db_get_record_list($location_type, "`{$id_field}` = {$record_id_safe}", true, false);
170
  }
171
172
  public static function db_get_record_list($location_type, $filter = '', $fetch = false, $no_return = false) {
173
    if (SnCache::isQueryCacheByLocationAndFilterEmpty($location_type, $filter)) {
174
      SnCache::queryCacheResetByLocationAndFilter($location_type, $filter);
175
176
      $location_info = &static::$location_info[$location_type];
177
      $id_field = $location_info[P_ID];
178
179
      if (classSupernova::$db->getTransaction()->check(false)) {
180
        // Проходим по всем родителям данной записи
181
        foreach ($location_info[P_OWNER_INFO] as $owner_data) {
182
          $owner_location_type = $owner_data[P_LOCATION];
183
          $parent_id_list = array();
184
          // Выбираем родителей данного типа и соответствующие ИД текущего типа
185
          $query = classSupernova::$db->doSelect(
186
            "SELECT
187
              distinct({{{$location_info[P_TABLE_NAME]}}}.{$owner_data[P_OWNER_FIELD]}) AS parent_id
188
            FROM {{{$location_info[P_TABLE_NAME]}}}" .
189
            ($filter ? ' WHERE ' . $filter : '') .
190
            ($fetch ? ' LIMIT 1' : ''));
191
          while ($row = db_fetch($query)) {
192
            // Исключаем из списка родительских ИД уже заблокированные записи
193
            if (!SnCache::cache_lock_get($owner_location_type, $row['parent_id'])) {
194
              $parent_id_list[$row['parent_id']] = $row['parent_id'];
195
            }
196
          }
197
198
          // Если все-таки какие-то записи еще не заблокированы - вынимаем текущие версии из базы
199
          if ($indexes_str = implode(',', $parent_id_list)) {
200
            $parent_id_field = static::$location_info[$owner_location_type][P_ID];
201
            static::db_get_record_list($owner_location_type,
202
              $parent_id_field . (count($parent_id_list) > 1 ? " IN ({$indexes_str})" : " = {$indexes_str}"), $fetch, true);
203
          }
204
        }
205
      }
206
207
      $query = classSupernova::$db->doSelect(
208
        "SELECT * FROM {{{$location_info[P_TABLE_NAME]}}}" .
209
        (($filter = trim($filter)) ? " WHERE {$filter}" : '')
210
        . " FOR UPDATE"
211
      );
212
      while ($row = db_fetch($query)) {
213
        // Caching record in row cache
214
        SnCache::cache_set($location_type, $row);
215
        // Making ref to cached record in query cache
216
        SnCache::queryCacheSetByFilter($location_type, $filter, $row[$id_field]);
217
      }
218
    }
219
220
    if ($no_return) {
221
      return true;
222
    } else {
223
      $result = false;
224
      foreach (SnCache::getQueriesByLocationAndFilter($location_type, $filter) as $key => $value) {
225
        $result[$key] = $value;
226
        if ($fetch) {
227
          break;
228
        }
229
      }
230
231
      return $fetch ? (is_array($result) ? reset($result) : false) : $result;
232
    }
233
  }
234
235
  /**
236
   * @param int    $location_type
237
   * @param int    $record_id
238
   * @param string $set - SQL SET structure
239
   *
240
   * @return array|bool|mysqli_result|null
241
   */
242
  public static function db_upd_record_by_id($location_type, $record_id, $set) {
243
    if (!($record_id = idval($record_id)) || !($set = trim($set))) {
244
      return false;
245
    }
246
247
    $id_field = static::$location_info[$location_type][P_ID];
248
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
249
    // TODO Как-то вернуть может быть LIMIT 1 ?
250 View Code Duplication
    if ($result = classSupernova::$db->doUpdate("UPDATE {{{$table_name}}} SET {$set} WHERE `{$id_field}` = {$record_id}")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
251
      if (classSupernova::$db->db_affected_rows()) {
252
        // Обновляем данные только если ряд был затронут
253
        // TODO - переделать под работу со структурированными $set
254
255
        // Тут именно так, а не cache_unset - что бы в кэшах автоматически обновилась запись. Будет нужно на будущее
256
        //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...
257
        SnCache::cacheUnsetElement($location_type, $record_id);
258
        // Вытаскиваем обновленную запись
259
        static::db_get_record_by_id($location_type, $record_id);
260
        SnCache::cache_clear($location_type, false); // Мягкий сброс - только $queries
261
      }
262
    }
263
264
    return $result;
265
  }
266
267
  /**
268
   * @param int    $location_type
269
   * @param string $set
270
   * @param string $condition
271
   *
272
   * @return array|bool|mysqli_result|null
273
   */
274
  public static function db_upd_record_list($location_type, $set, $condition) {
275
    if (!($set = trim($set))) {
276
      return false;
277
    }
278
279
    $condition = trim($condition);
280
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
281
282
    if ($result = classSupernova::$db->doUpdate("UPDATE {{{$table_name}}} SET " . $set . ($condition ? ' WHERE ' . $condition : ''))) {
283
284
      if (classSupernova::$db->db_affected_rows()) { // Обновляем данные только если ряд был затронут
285
        // Поскольку нам неизвестно, что и как обновилось - сбрасываем кэш этого типа полностью
286
        // TODO - когда будет структурированный $condition и $set - перепаковывать данные
287
        SnCache::cache_clear($location_type, true);
288
      }
289
    }
290
291
    return $result;
292
  }
293
  /**
294
   * @param int    $location_type
295
   * @param string $set
296
   *
297
   * @return array|bool|false|mysqli_result|null
298
   */
299
  public static function db_ins_record($location_type, $set) {
300
    $set = trim($set);
301
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
302 View Code Duplication
    if ($result = classSupernova::$db->doInsert("INSERT INTO `{{{$table_name}}}` SET {$set}")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
303
      if (classSupernova::$db->db_affected_rows()) // Обновляем данные только если ряд был затронут
304
      {
305
        $record_id = classSupernova::$db->db_insert_id();
306
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
307
        $result = static::db_get_record_by_id($location_type, $record_id);
308
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
309
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
310
        SnCache::cache_clear($location_type, false); // Мягкий сброс - только $queries
311
      }
312
    }
313
314
    return $result;
315
  }
316
317
  public static function db_ins_field_set($location_type, $field_set, $serialize = false) {
318
    // TODO multiinsert
319
    !sn_db_field_set_is_safe($field_set) ? $field_set = sn_db_field_set_make_safe($field_set, $serialize) : false;
320
    sn_db_field_set_safe_flag_clear($field_set);
321
    $values = implode(',', $field_set);
322
    $fields = implode(',', array_keys($field_set));
323
324
    $table_name = static::$location_info[$location_type][P_TABLE_NAME];
325 View Code Duplication
    if ($result = classSupernova::$db->doInsert("INSERT INTO `{{{$table_name}}}` ({$fields}) VALUES ({$values});")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
326
      if (classSupernova::$db->db_affected_rows()) {
327
        // Обновляем данные только если ряд был затронут
328
        $record_id = classSupernova::$db->db_insert_id();
329
        // Вытаскиваем запись целиком, потому что в $set могли быть "данные по умолчанию"
330
        $result = static::db_get_record_by_id($location_type, $record_id);
331
        // Очищаем второстепенные кэши - потому что вставленная запись могла повлиять на результаты запросов или локация или еще чего
332
        // TODO - когда будет поддержка изменения индексов и локаций - можно будет вызывать её
333
        SnCache::cache_clear($location_type, false); // Мягкий сброс - только $queries
334
      }
335
    }
336
337
    return $result;
338
  }
339
340
341
342
343
344
345
346
347
348
349
350
  /**
351
   * Блокирует указанные таблицу/список таблиц
352
   *
353
   * @param string|array $tables Таблица/список таблиц для блокировки. Названия таблиц - без префиксов
354
   * <p>string - название таблицы для блокировки</p>
355
   * <p>array - массив, где ключ - имя таблицы, а значение - условия блокировки элементов</p>
356
   */
357
  public static function db_lock_tables($tables) {
358
    $tables = is_array($tables) ? $tables : array($tables => '');
359
    foreach ($tables as $table_name => $condition) {
360
      classSupernova::$db->doSelect("SELECT 1 FROM {{{$table_name}}}" . ($condition ? ' WHERE ' . $condition : ''));
361
    }
362
  }
363
364
365
}