Completed
Push — master ( 6eb0ef...06bfdf )
by Lars
03:53
created

Helper::copyTableRow()   C

Complexity

Conditions 10
Paths 50

Size

Total Lines 74

Duplication

Lines 6
Ratio 8.11 %

Code Coverage

Tests 32
CRAP Score 10.2465

Importance

Changes 0
Metric Value
dl 6
loc 74
ccs 32
cts 37
cp 0.8649
rs 6.7006
c 0
b 0
f 0
cc 10
nc 50
nop 6
crap 10.2465

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
class Helper
14
{
15
    /**
16
     * @param DB $dbConnection
17
     *
18
     * @return string
19
     */
20 8
    private static function generateCacheKey(DB $dbConnection): string
21
    {
22
        // init
23 8
        $configTmp = [];
24
25 8
        $configOrig = $dbConnection->getConfig();
26 8
        \array_walk_recursive(
27
            $configOrig,
28 8
            static function ($k, $v) use (&$configTmp) {
29 8
                $configTmp[] = $v;
30 8
                $configTmp[] = $k;
31 8
            }
32
        );
33
34 8
        return \implode('--', $configTmp);
35
    }
36
37
    /**
38
     * Optimize tables
39
     *
40
     * @param array   $tables       database table names
41
     * @param DB|null $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
42
     *
43
     * @return int
44
     */
45 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...
46
    {
47 1
        if ($dbConnection === null) {
48 1
            $dbConnection = DB::getInstance();
49
        }
50
51 1
        $optimized = 0;
52 1
        if (!empty($tables)) {
53 1
            foreach ($tables as $table) {
54 1
                $optimize = 'OPTIMIZE TABLE ' . $dbConnection->quote_string($table);
55 1
                $result = $dbConnection->query($optimize);
56 1
                if ($result) {
57 1
                    $optimized++;
58
                }
59
            }
60
        }
61
62 1
        return $optimized;
63
    }
64
65
    /**
66
     * Repair tables
67
     *
68
     * @param array   $tables       database table names
69
     * @param DB|null $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
70
     *
71
     * @return int
72
     */
73 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...
74
    {
75 1
        if ($dbConnection === null) {
76 1
            $dbConnection = DB::getInstance();
77
        }
78
79 1
        $optimized = 0;
80 1
        if (!empty($tables)) {
81 1
            foreach ($tables as $table) {
82 1
                $optimize = 'REPAIR TABLE ' . $dbConnection->quote_string($table);
83 1
                $result = $dbConnection->query($optimize);
84 1
                if ($result) {
85 1
                    $optimized++;
86
                }
87
            }
88
        }
89
90 1
        return $optimized;
91
    }
92
93
    /**
94
     * Check if "mysqlnd"-driver is used.
95
     *
96
     * @return bool
97
     */
98 91
    public static function isMysqlndIsUsed(): bool
99
    {
100 91
        static $_mysqlnd_is_used = null;
101
102 91
        if ($_mysqlnd_is_used === null) {
103
            $_mysqlnd_is_used = (
104 1
                \extension_loaded('mysqlnd')
105
                &&
106 1
                \function_exists('mysqli_fetch_all')
107
            );
108
        }
109
110 91
        return $_mysqlnd_is_used;
111
    }
112
113
    /**
114
     * Check if the current environment supports "utf8mb4".
115
     *
116
     * @param DB $dbConnection
117
     *
118
     * @return bool
119
     */
120 8
    public static function isUtf8mb4Supported(DB $dbConnection = null): bool
121
    {
122
        /**
123
         *  https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/
124
         *
125
         * - You’re currently using the utf8 character set.
126
         * - Your MySQL server is version 5.5.3 or higher (including all 10.x versions of MariaDB).
127
         * - Your MySQL client libraries are version 5.5.3 or higher. If you’re using mysqlnd, 5.0.9 or higher.
128
         *
129
         * INFO: utf8mb4 is 100% backwards compatible with utf8.
130
         */
131 8
        if ($dbConnection === null) {
132
            $dbConnection = DB::getInstance();
133
        }
134
135 8
        $server_version = self::get_mysql_server_version($dbConnection);
136 8
        $client_version = self::get_mysql_client_version($dbConnection);
137
138
        if (
139 8
            $server_version >= 50503
140
            &&
141
            (
142
                (
143 8
                    self::isMysqlndIsUsed()
144
                    &&
145 8
                    $client_version >= 50009
146
                )
147
                ||
148
                (
149
                    !self::isMysqlndIsUsed()
150
                    &&
151 8
                    $client_version >= 50503
152
                )
153
            )
154
155
        ) {
156 8
            return true;
157
        }
158
159
        return false;
160
    }
161
162
    /**
163
     * A phonetic search algorithms for different languages.
164
     *
165
     * INFO: if you need better performance, please save the "voku\helper\Phonetic"-output into the DB and search for it
166
     *
167
     * @param string      $searchString
168
     * @param string      $searchFieldName
169
     * @param string      $idFieldName
170
     * @param string      $language     <p>en, de, fr</p>
171
     * @param string      $table
172
     * @param array       $whereArray
173
     * @param DB|null     $dbConnection <p>use <strong>null</strong> if you will use the current database-connection</p>
174
     * @param string|null $databaseName <p>use <strong>null</strong> if you will use the current database</p>
175
     * @param bool        $useCache     use cache?
176
     * @param int         $cacheTTL     cache-ttl in seconds
177
     *
178
     * @return array
179
     */
180 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
181
    {
182
        // init
183 2
        $cacheKey = null;
184
185 2
        if ($dbConnection === null) {
186 2
            $dbConnection = DB::getInstance();
187
        }
188
189 2 View Code Duplication
        if ($table === '') {
190
            $debug = new Debug($dbConnection);
191
            $debug->displayError('Invalid table name, table name in empty.', false);
192
193
            return [];
194
        }
195
196 2
        if ($idFieldName === null) {
197
            $idFieldName = 'id';
198
        }
199
200 2
        $whereSQL = $dbConnection->_parseArrayPair($whereArray, 'AND');
0 ignored issues
show
Bug introduced by
It seems like $whereArray defined by parameter $whereArray on line 180 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...
201 2
        if ($whereSQL) {
202 2
            $whereSQL = 'AND ' . $whereSQL;
203
        }
204
205 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...
206
            $databaseName = $dbConnection->quote_string(\trim($databaseName)) . '.';
207
        }
208
209
        // get the row
210 2
        $query = 'SELECT ' . $dbConnection->quote_string($searchFieldName) . ', ' . $dbConnection->quote_string($idFieldName) . ' 
211 2
          FROM ' . $databaseName . $dbConnection->quote_string($table) . '
212
          WHERE 1 = 1
213 2
          ' . $whereSQL . '
214
        ';
215
216 2 View Code Duplication
        if ($useCache) {
217 1
            $cache = new Cache(null, null, false, $useCache);
218 1
            $cacheKey = 'sql-phonetic-search-' . \md5($query);
219
220
            if (
221 1
                $cache->getCacheIsReady()
222
                &&
223 1
                $cache->existsItem($cacheKey)
224
            ) {
225 1
                return $cache->getItem($cacheKey);
226
            }
227
        } else {
228 1
            $cache = false;
229
        }
230
231 2
        $result = $dbConnection->query($query);
232
233
        // make sure the row exists
234 2
        if ($result->num_rows <= 0) {
235
            return [];
236
        }
237
238 2
        $dataToSearchIn = [];
239
        /** @noinspection LoopWhichDoesNotLoopInspection */
240
        /** @noinspection PhpAssignmentInConditionInspection */
241 2
        while ($tmpArray = $result->fetchArray()) {
242 2
            $dataToSearchIn[$tmpArray[$idFieldName]] = $tmpArray[$searchFieldName];
243
        }
244
245 2
        $phonetic = new Phonetic($language);
246 2
        $return = $phonetic->phonetic_matches($searchString, $dataToSearchIn);
247
248
        // save into the cache
249 View Code Duplication
        if (
250 2
            $cacheKey !== null
251
            &&
252 2
            $useCache
253
            &&
254 2
            $cache instanceof Cache
255
            &&
256 2
            $cache->getCacheIsReady()
257
        ) {
258 1
            $cache->setItem($cacheKey, $return, $cacheTTL);
259
        }
260
261 2
        return $return;
262
    }
263
264
    /**
265
     * A string that represents the MySQL client library version.
266
     *
267
     * @param DB $dbConnection
268
     *
269
     * @return string
270
     */
271 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...
272
    {
273 8
        static $MYSQL_CLIENT_VERSION_CACHE = [];
274
275 8
        if ($dbConnection === null) {
276
            $dbConnection = DB::getInstance();
277
        }
278
279 8
        $cacheKey = self::generateCacheKey($dbConnection);
280
281 8
        if (isset($MYSQL_CLIENT_VERSION_CACHE[$cacheKey])) {
282 8
            return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey];
283
        }
284
285 2
        $doctrineConnection = $dbConnection->getDoctrineConnection();
286 2
        if ($doctrineConnection) {
287
            $doctrineWrappedConnection = $doctrineConnection->getWrappedConnection();
288
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
289
                return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey] = '';
290
            }
291
        }
292
293 2
        return $MYSQL_CLIENT_VERSION_CACHE[$cacheKey] = (string) \mysqli_get_client_version($dbConnection->getLink());
294
    }
295
296
    /**
297
     * Returns a string representing the version of the MySQL server that the MySQLi extension is connected to.
298
     *
299
     * @param DB $dbConnection
300
     *
301
     * @return string
302
     */
303 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...
304
    {
305 8
        static $MYSQL_SERVER_VERSION_CACHE = [];
306
307 8
        if ($dbConnection === null) {
308
            $dbConnection = DB::getInstance();
309
        }
310
311 8
        $cacheKey = self::generateCacheKey($dbConnection);
312
313 8
        if (isset($MYSQL_SERVER_VERSION_CACHE[$cacheKey])) {
314 8
            return $MYSQL_SERVER_VERSION_CACHE[$cacheKey];
315
        }
316
317 2
        $doctrineConnection = $dbConnection->getDoctrineConnection();
318 2
        if ($doctrineConnection) {
319
            $doctrineWrappedConnection = $doctrineConnection->getWrappedConnection();
320
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
321
                return $MYSQL_SERVER_VERSION_CACHE[$cacheKey] = (string) $doctrineWrappedConnection->getServerVersion();
322
            }
323
        }
324
325 2
        return $MYSQL_SERVER_VERSION_CACHE[$cacheKey] = (string) \mysqli_get_server_version($dbConnection->getLink());
326
    }
327
328
    /**
329
     * Return all db-fields from a table.
330
     *
331
     * @param string      $table
332
     * @param bool        $useStaticCache
333
     * @param DB|null     $dbConnection <p>use <strong>null</strong> if you will use the current database-connection</p>
334
     * @param string|null $databaseName <p>use <strong>null</strong> if you will use the current database</p>
335
     *
336
     * @return array
337
     */
338 1
    public static function getDbFields(string $table, bool $useStaticCache = true, DB $dbConnection = null, string $databaseName = null): array
339
    {
340 1
        static $DB_FIELDS_CACHE = [];
341
342
        // use the static cache
343
        if (
344 1
            $useStaticCache
345
            &&
346 1
            isset($DB_FIELDS_CACHE[$table])
347
        ) {
348 1
            return $DB_FIELDS_CACHE[$table];
349
        }
350
351
        // init
352 1
        $dbFields = [];
353
354 1
        if ($dbConnection === null) {
355 1
            $dbConnection = DB::getInstance();
356
        }
357
358 1 View Code Duplication
        if ($table === '') {
359
            $debug = new Debug($dbConnection);
360
            $debug->displayError('Invalid table name, table name in empty.', false);
361
362
            return [];
363
        }
364
365 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...
366
            $databaseName = $dbConnection->quote_string(\trim($databaseName)) . '.';
367
        }
368
369 1
        $sql = 'SHOW COLUMNS FROM ' . $databaseName . $dbConnection->escape($table);
370 1
        $result = $dbConnection->query($sql);
371
372 1
        if ($result && $result->num_rows > 0) {
373 1
            foreach ($result->fetchAllArray() as $tmpResult) {
374 1
                $dbFields[] = $tmpResult['Field'];
375
            }
376
        }
377
378
        // add to static cache
379 1
        $DB_FIELDS_CACHE[$table] = $dbFields;
380
381 1
        return $dbFields;
382
    }
383
384
    /**
385
     * Copy row within a DB table and making updates to the columns.
386
     *
387
     * @param string      $table
388
     * @param array       $whereArray
389
     * @param array       $updateArray
390
     * @param array       $ignoreArray
391
     * @param DB|null     $dbConnection <p>Use <strong>null</strong> to get your first singleton instance.</p>
392
     * @param string|null $databaseName <p>use <strong>null</strong> if you will use the current database</p>
393
     *
394
     * @return bool|int "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
395
     *                   "false" on error
396
     */
397 1
    public static function copyTableRow(string $table, array $whereArray, array $updateArray = [], array $ignoreArray = [], DB $dbConnection = null, string $databaseName = null)
398
    {
399
        // init
400 1
        $table = \trim($table);
401
402 1
        if ($dbConnection === null) {
403 1
            $dbConnection = DB::getInstance();
404
        }
405
406 1 View Code Duplication
        if ($table === '') {
407
            $debug = new Debug($dbConnection);
408
            $debug->displayError('Invalid table name, table name in empty.', false);
409
410
            return false;
411
        }
412
413 1
        $whereSQL = $dbConnection->_parseArrayPair($whereArray, 'AND');
414 1
        if ($whereSQL) {
415 1
            $whereSQL = 'AND ' . $whereSQL;
416
        }
417
418 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...
419
            $databaseName = $dbConnection->quote_string(\trim($databaseName)) . '.';
420
        }
421
422
        // get the row
423 1
        $query = 'SELECT * FROM ' . $databaseName . $dbConnection->quote_string($table) . '
424
          WHERE 1 = 1
425 1
          ' . $whereSQL . '
426
        ';
427 1
        $result = $dbConnection->query($query);
428
429
        // make sure the row exists
430 1
        if ($result->num_rows > 0) {
431
432
            /** @noinspection LoopWhichDoesNotLoopInspection */
433
            /** @noinspection PhpAssignmentInConditionInspection */
434 1
            while ($tmpArray = $result->fetchArray()) {
435
436
                // re-build a new DB query and ignore some field-names
437 1
                $bindings = [];
438 1
                $insert_keys = '';
439 1
                $insert_values = '';
440
441 1
                foreach ($tmpArray as $fieldName => $value) {
442 1
                    if (!\in_array($fieldName, $ignoreArray, true)) {
443 1
                        if (\array_key_exists($fieldName, $updateArray)) {
444 1
                            $insert_keys .= ',' . $fieldName;
445 1
                            $insert_values .= ',?';
446 1
                            $bindings[] = $updateArray[$fieldName]; // INFO: do not escape non selected data
447
                        } else {
448 1
                            $insert_keys .= ',' . $fieldName;
449 1
                            $insert_values .= ',?';
450 1
                            $bindings[] = $value; // INFO: do not escape non selected data
451
                        }
452
                    }
453
                }
454
455 1
                $insert_keys = \ltrim($insert_keys, ',');
456 1
                $insert_values = \ltrim($insert_values, ',');
457
458
                // insert the "copied" row
459 1
                $new_query = 'INSERT INTO ' . $databaseName . $dbConnection->quote_string($table) . ' 
460 1
                  (' . $insert_keys . ')
461
                  VALUES 
462 1
                  (' . $insert_values . ')
463
                ';
464
465 1
                return $dbConnection->query($new_query, $bindings);
466
            }
467
        }
468
469
        return false;
470
    }
471
}
472