Completed
Push — master ( cd7afc...70524f )
by Lars
04:05
created

Helper::repairTables()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 19

Duplication

Lines 19
Ratio 100 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
dl 19
loc 19
ccs 11
cts 11
cp 1
rs 9.3222
c 0
b 0
f 0
cc 5
nc 4
nop 2
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use voku\cache\Cache;
8
use voku\helper\Phonetic;
9
10
/**
11
 * Helper: This class can handle extra functions that use the "Simple-MySQLi"-classes.
12
 *
13
 * @package   voku\db
14
 */
15
class Helper
16
{
17
  /**
18
   * Optimize tables
19
   *
20
   * @param array   $tables       database table names
21
   * @param DB|null $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
22
   *
23
   * @return int
24
   */
25 1 View Code Duplication
  public static function optimizeTables(array $tables = [], DB $dbConnection = null): int
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...
26
  {
27 1
    if ($dbConnection === null) {
28 1
      $dbConnection = DB::getInstance();
29
    }
30
31 1
    $optimized = 0;
32 1
    if (!empty($tables)) {
33 1
      foreach ($tables as $table) {
34 1
        $optimize = 'OPTIMIZE TABLE ' . $dbConnection->quote_string($table);
35 1
        $result = $dbConnection->query($optimize);
36 1
        if ($result) {
37 1
          $optimized++;
38
        }
39
      }
40
    }
41
42 1
    return $optimized;
43
  }
44
45
  /**
46
   * Repair tables
47
   *
48
   * @param array   $tables       database table names
49
   * @param DB|null $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
50
   *
51
   * @return int
52
   */
53 1 View Code Duplication
  public static function repairTables(array $tables = [], DB $dbConnection = null): int
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...
54
  {
55 1
    if ($dbConnection === null) {
56 1
      $dbConnection = DB::getInstance();
57
    }
58
59 1
    $optimized = 0;
60 1
    if (!empty($tables)) {
61 1
      foreach ($tables as $table) {
62 1
        $optimize = 'REPAIR TABLE ' . $dbConnection->quote_string($table);
63 1
        $result = $dbConnection->query($optimize);
64 1
        if ($result) {
65 1
          $optimized++;
66
        }
67
      }
68
    }
69
70 1
    return $optimized;
71
  }
72
73
  /**
74
   * Check if "mysqlnd"-driver is used.
75
   *
76
   * @return bool
77
   */
78 105
  public static function isMysqlndIsUsed(): bool
79
  {
80 105
    static $_mysqlnd_is_used = null;
81
82 105
    if ($_mysqlnd_is_used === null) {
83
      $_mysqlnd_is_used = (
84 1
          \extension_loaded('mysqlnd')
85
          &&
86 1
          \function_exists('mysqli_fetch_all')
87
      );
88
    }
89
90 105
    return $_mysqlnd_is_used;
91
  }
92
93
  /**
94
   * Check if the current environment supports "utf8mb4".
95
   *
96
   * @param DB $dbConnection
97
   *
98
   * @return bool
99
   */
100 8
  public static function isUtf8mb4Supported(DB $dbConnection = null): bool
101
  {
102
    /**
103
     *  https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/
104
     *
105
     * - You’re currently using the utf8 character set.
106
     * - Your MySQL server is version 5.5.3 or higher (including all 10.x versions of MariaDB).
107
     * - Your MySQL client libraries are version 5.5.3 or higher. If you’re using mysqlnd, 5.0.9 or higher.
108
     *
109
     * INFO: utf8mb4 is 100% backwards compatible with utf8.
110
     */
111
112 8
    if ($dbConnection === null) {
113
      $dbConnection = DB::getInstance();
114
    }
115
116 8
    $server_version = self::get_mysql_server_version($dbConnection);
117 8
    $client_version = self::get_mysql_client_version($dbConnection);
118
119
    if (
120 8
        $server_version >= 50503
121
        &&
122
        (
123
            (
124 8
                self::isMysqlndIsUsed() === true
125
                &&
126 8
                $client_version >= 50009
127
            )
128
            ||
129
            (
130
                self::isMysqlndIsUsed() === false
131
                &&
132 8
                $client_version >= 50503
133
            )
134
        )
135
136
    ) {
137 8
      return true;
138
    }
139
140
    return false;
141
  }
142
143
  /**
144
   * A phonetic search algorithms for different languages.
145
   *
146
   * INFO: if you need better performance, please save the "voku\helper\Phonetic"-output into the DB and search for it
147
   *
148
   * @param string      $searchString
149
   * @param string      $searchFieldName
150
   * @param string      $idFieldName
151
   * @param string      $language     <p>en, de, fr</p>
152
   * @param string      $table
153
   * @param array       $whereArray
154
   * @param DB|null     $dbConnection <p>use <strong>null</strong> if you will use the current database-connection</p>
155
   * @param null|string $databaseName <p>use <strong>null</strong> if you will use the current database</p>
156
   * @param bool        $useCache     use cache?
157
   * @param int         $cacheTTL     cache-ttl in seconds
158
   *
159
   * @return array
160
   */
161 2
  public static function phoneticSearch(string $searchString, string $searchFieldName, string $idFieldName = null, string $language = 'de', string $table = '', array $whereArray = null, DB $dbConnection = null, string $databaseName = null, bool $useCache = false, int $cacheTTL = 3600): array
162
  {
163
    // init
164 2
    $cacheKey = null;
165
166 2
    if ($dbConnection === null) {
167 2
      $dbConnection = DB::getInstance();
168
    }
169
170 2 View Code Duplication
    if ($table === '') {
171
      $debug = new Debug($dbConnection);
172
      $debug->displayError('Invalid table name, table name in empty.', false);
173
174
      return [];
175
    }
176
177 2
    if ($idFieldName === null) {
178
      $idFieldName = 'id';
179
    }
180
181 2
    $whereSQL = $dbConnection->_parseArrayPair($whereArray, 'AND');
0 ignored issues
show
Bug introduced by
It seems like $whereArray defined by parameter $whereArray on line 161 can also be of type null; however, voku\db\DB::_parseArrayPair() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
182 2
    if ($whereSQL) {
183 2
      $whereSQL = 'AND ' . $whereSQL;
184
    }
185
186 2
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
187
      $databaseName = $dbConnection->quote_string(trim($databaseName)) . '.';
188
    }
189
190
    // get the row
191 2
    $query = 'SELECT ' . $dbConnection->quote_string($searchFieldName) . ', ' . $dbConnection->quote_string($idFieldName) . ' 
192 2
      FROM ' . $databaseName . $dbConnection->quote_string($table) . '
193
      WHERE 1 = 1
194 2
      ' . $whereSQL . '
195
    ';
196
197 2 View Code Duplication
    if ($useCache === true) {
198 1
      $cache = new Cache(null, null, false, $useCache);
199 1
      $cacheKey = 'sql-phonetic-search-' . \md5($query);
200
201
      if (
202 1
          $cache->getCacheIsReady() === true
203
          &&
204 1
          $cache->existsItem($cacheKey)
205
      ) {
206 1
        return $cache->getItem($cacheKey);
207
      }
208
209
    } else {
210 1
      $cache = false;
211
    }
212
213 2
    $result = $dbConnection->query($query);
214
215
    // make sure the row exists
216 2
    if ($result->num_rows <= 0) {
217
      return [];
218
    }
219
220 2
    $dataToSearchIn = [];
221
    /** @noinspection LoopWhichDoesNotLoopInspection */
222
    /** @noinspection PhpAssignmentInConditionInspection */
223 2
    while ($tmpArray = $result->fetchArray()) {
224 2
      $dataToSearchIn[$tmpArray[$idFieldName]] = $tmpArray[$searchFieldName];
225
    }
226
227 2
    $phonetic = new Phonetic($language);
228 2
    $return = $phonetic->phonetic_matches($searchString, $dataToSearchIn);
229
230
    // save into the cache
231 View Code Duplication
    if (
232 2
        $cacheKey !== null
233
        &&
234 2
        $useCache === true
235
        &&
236 2
        $cache instanceof Cache
237
        &&
238 2
        $cache->getCacheIsReady() === true
239
    ) {
240 1
      $cache->setItem($cacheKey, $return, $cacheTTL);
241
    }
242
243 2
    return $return;
244
  }
245
246
  /**
247
   * A string that represents the MySQL client library version.
248
   *
249
   * @param DB $dbConnection
250
   *
251
   * @return string
252
   */
253 8 View Code Duplication
  public static function get_mysql_client_version(DB $dbConnection = null): string
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...
254
  {
255 8
    static $MYSQL_CLIENT_VERSION_CACHE = [];
256
257 8
    if ($dbConnection === null) {
258
      $dbConnection = DB::getInstance();
259
    }
260
261 8
    $cacheKey = implode('--', $dbConnection->getConfig());
262
263 8
    if (isset($MYSQL_CLIENT_VERSION_CACHE[$cacheKey])) {
264 7
      return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey];
265
    }
266
267 2
    $doctrineConnection = $dbConnection->getDoctrineConnection();
268 2
    if ($doctrineConnection) {
269
      $doctrineWrappedConnection = $doctrineConnection->getWrappedConnection();
270
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
271
        return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey] = '';
272
      }
273
    }
274
275 2
    return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey] = (string)\mysqli_get_client_version($dbConnection->getLink());
276
  }
277
278
279
  /**
280
   * Returns a string representing the version of the MySQL server that the MySQLi extension is connected to.
281
   *
282
   * @param DB $dbConnection
283
   *
284
   * @return string
285
   */
286 8 View Code Duplication
  public static function get_mysql_server_version(DB $dbConnection = null): string
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...
287
  {
288 8
    static $MYSQL_SERVER_VERSION_CACHE = [];
289
290 8
    if ($dbConnection === null) {
291
      $dbConnection = DB::getInstance();
292
    }
293
294 8
    $cacheKey = implode('--', $dbConnection->getConfig());
295
296 8
    if (isset($MYSQL_SERVER_VERSION_CACHE[$cacheKey])) {
297 7
      return $MYSQL_SERVER_VERSION_CACHE[$cacheKey];
298
    }
299
300 2
    $doctrineConnection = $dbConnection->getDoctrineConnection();
301 2
    if ($doctrineConnection) {
302
      $doctrineWrappedConnection = $doctrineConnection->getWrappedConnection();
303
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
304
        return $MYSQL_SERVER_VERSION_CACHE[$cacheKey] = (string)$doctrineWrappedConnection->getServerVersion();
305
      }
306
    }
307
308 2
    return $MYSQL_SERVER_VERSION_CACHE[$cacheKey] = (string)\mysqli_get_server_version($dbConnection->getLink());
309
  }
310
311
  /**
312
   * Return all db-fields from a table.
313
   *
314
   * @param string      $table
315
   * @param bool        $useStaticCache
316
   * @param DB|null     $dbConnection <p>use <strong>null</strong> if you will use the current database-connection</p>
317
   * @param null|string $databaseName <p>use <strong>null</strong> if you will use the current database</p>
318
   *
319
   * @return array
320
   */
321 1
  public static function getDbFields(string $table, bool $useStaticCache = true, DB $dbConnection = null, string $databaseName = null): array
322
  {
323 1
    static $DB_FIELDS_CACHE = [];
324
325
    // use the static cache
326
    if (
327 1
        $useStaticCache === true
328
        &&
329 1
        isset($DB_FIELDS_CACHE[$table])
330
    ) {
331 1
      return $DB_FIELDS_CACHE[$table];
332
    }
333
334
    // init
335 1
    $dbFields = [];
336
337 1
    if ($dbConnection === null) {
338 1
      $dbConnection = DB::getInstance();
339
    }
340
341 1 View Code Duplication
    if ($table === '') {
342
      $debug = new Debug($dbConnection);
343
      $debug->displayError('Invalid table name, table name in empty.', false);
344
345
      return [];
346
    }
347
348 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
349
      $databaseName = $dbConnection->quote_string(trim($databaseName)) . '.';
350
    }
351
352 1
    $sql = 'SHOW COLUMNS FROM ' . $databaseName . $dbConnection->escape($table);
353 1
    $result = $dbConnection->query($sql);
354
355 1
    if ($result && $result->num_rows > 0) {
356 1
      foreach ($result->fetchAllArray() as $tmpResult) {
357 1
        $dbFields[] = $tmpResult['Field'];
358
      }
359
    }
360
361
    // add to static cache
362 1
    $DB_FIELDS_CACHE[$table] = $dbFields;
363
364 1
    return $dbFields;
365
  }
366
367
  /**
368
   * Copy row within a DB table and making updates to the columns.
369
   *
370
   * @param string      $table
371
   * @param array       $whereArray
372
   * @param array       $updateArray
373
   * @param array       $ignoreArray
374
   * @param DB|null     $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
375
   * @param null|string $databaseName <p>use <strong>null</strong> if you will use the current database</p>
376
   *
377
   * @return bool|int "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
378
   *                   "false" on error
379
   */
380 1
  public static function copyTableRow(string $table, array $whereArray, array $updateArray = [], array $ignoreArray = [], DB $dbConnection = null, string $databaseName = null)
381
  {
382
    // init
383 1
    $table = trim($table);
384
385 1
    if ($dbConnection === null) {
386 1
      $dbConnection = DB::getInstance();
387
    }
388
389 1 View Code Duplication
    if ($table === '') {
390
      $debug = new Debug($dbConnection);
391
      $debug->displayError('Invalid table name, table name in empty.', false);
392
393
      return false;
394
    }
395
396 1
    $whereSQL = $dbConnection->_parseArrayPair($whereArray, 'AND');
397 1
    if ($whereSQL) {
398 1
      $whereSQL = 'AND ' . $whereSQL;
399
    }
400
401 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
402
      $databaseName = $dbConnection->quote_string(trim($databaseName)) . '.';
403
    }
404
405
    // get the row
406 1
    $query = 'SELECT * FROM ' . $databaseName . $dbConnection->quote_string($table) . '
407
      WHERE 1 = 1
408 1
      ' . $whereSQL . '
409
    ';
410 1
    $result = $dbConnection->query($query);
411
412
    // make sure the row exists
413 1
    if ($result->num_rows > 0) {
414
415
      /** @noinspection LoopWhichDoesNotLoopInspection */
416
      /** @noinspection PhpAssignmentInConditionInspection */
417 1
      while ($tmpArray = $result->fetchArray()) {
418
419
        // re-build a new DB query and ignore some field-names
420 1
        $bindings = [];
421 1
        $insert_keys = '';
422 1
        $insert_values = '';
423
424 1
        foreach ($tmpArray as $fieldName => $value) {
425
426 1
          if (!\in_array($fieldName, $ignoreArray, true)) {
427 1
            if (\array_key_exists($fieldName, $updateArray)) {
428 1
              $insert_keys .= ',' . $fieldName;
429 1
              $insert_values .= ',?';
430 1
              $bindings[] = $updateArray[$fieldName]; // INFO: do not escape non selected data
431
            } else {
432 1
              $insert_keys .= ',' . $fieldName;
433 1
              $insert_values .= ',?';
434 1
              $bindings[] = $value; // INFO: do not escape non selected data
435
            }
436
          }
437
        }
438
439 1
        $insert_keys = ltrim($insert_keys, ',');
440 1
        $insert_values = ltrim($insert_values, ',');
441
442
        // insert the "copied" row
443 1
        $new_query = 'INSERT INTO ' . $databaseName . $dbConnection->quote_string($table) . ' 
444 1
          (' . $insert_keys . ')
445
          VALUES 
446 1
          (' . $insert_values . ')
447
        ';
448
449 1
        return $dbConnection->query($new_query, $bindings);
450
      }
451
    }
452
453
    return false;
454
  }
455
}
456