YDB   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 463
Duplicated Lines 0 %

Test Coverage

Coverage 56.84%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 79
c 1
b 0
f 0
dl 0
loc 463
ccs 54
cts 95
cp 0.5684
rs 9.0399
wmc 42

36 Methods

Rating   Name   Duplication   Size   Complexity  
A set_html_context() 0 2 1
A get_num_queries() 0 2 1
A has_option() 0 2 1
A is_installed() 0 2 1
A delete_infos() 0 2 1
A set_plugin_pages() 0 2 1
A delete_option() 0 2 1
A get_infos() 0 2 1
A get_queries() 0 9 2
A query() 0 4 1
A get_var() 0 4 1
A add_plugin_page() 0 5 1
A remove_plugin_page() 0 2 1
A get_html_context() 0 2 1
A get_results() 0 5 1
A get_emulate_state() 0 2 1
A get_plugins() 0 2 1
A get_col() 0 4 1
A get_plugin_pages() 0 2 2
A set_plugins() 0 2 1
A mysql_version() 0 3 1
A has_infos() 0 2 1
A add_plugin() 0 2 1
A set_option() 0 2 1
A get_option() 0 2 1
A escape() 0 4 1
A set_installed() 0 2 1
A get_row() 0 5 2
A remove_plugin() 0 2 1
A dead_or_error() 0 12 2
A set_infos() 0 2 1
A set_emulate_state() 0 5 2
A connect_to_DB() 0 5 2
A init() 0 6 1
A __construct() 0 2 1
A start_profiler() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like YDB 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.

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 YDB, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Aura SQL wrapper for YOURLS that creates the allmighty YDB object.
5
 *
6
 * A fine example of a "class that knows too much" (see https://en.wikipedia.org/wiki/God_object)
7
 *
8
 * Note to plugin authors: you most likely SHOULD NOT use directly methods and properties of this class. Use instead
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 116 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
9
 * function wrappers (eg don't use $ydb->option, or $ydb->set_option(), use yourls_*_options() functions instead).
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
10
 *
11
 * @since 1.7.3
12
 */
13
14
namespace YOURLS\Database;
15
16
use YOURLS\Admin\Logger;
17
use Aura\Sql\ExtendedPdo;
18
use PDO;
19
20
class YDB extends ExtendedPdo {
21
22
    /**
23
     * Debug mode, default false
24
     * @var bool
0 ignored issues
show
Bug introduced by
Expected "boolean" but found "bool" for @var tag in member variable comment
Loading history...
25
     */
26
    protected $debug = false;
27
28
    /**
29
     * Page context (ie "infos", "bookmark", "plugins"...)
30
     * @var string
31
     */
32
    protected $context = '';
33
34
    /**
35
     * Information related to a short URL keyword (eg timestamp, long URL, ...)
36
     *
37
     * @var array
38
     *
39
     */
40
    protected $infos = [];
41
42
    /**
43
     * Is YOURLS installed and ready to run?
44
     * @var bool
0 ignored issues
show
Bug introduced by
Expected "boolean" but found "bool" for @var tag in member variable comment
Loading history...
45
     */
46
    protected $installed = false;
47
48
    /**
49
     * Options
50
     * @var array
51
     */
52
    protected $option = [];
53
54
    /**
55
     * Plugin admin pages informations
56
     * @var array
57
     */
58
    protected $plugin_pages = [];
59
60
    /**
61
     * Plugin informations
62
     * @var array
63
     */
64
    protected $plugins = [];
65
66
    /**
67
     * Are we emulating prepare statements ?
68
     * @var bool
0 ignored issues
show
Bug introduced by
Expected "boolean" but found "bool" for @var tag in member variable comment
Loading history...
69
     */
70
    protected $is_emulate_prepare;
71
72
    /**
73
     * @since 1.7.3
74
     * @param string $dsn         The data source name
75
     * @param string $user        The username
76
     * @param string $pass        The password
77
     * @param array  $options     Driver-specific options
78
     * @param array  $attributes  Attributes to set after a connection
79
     */
80
    public function __construct($dsn, $user, $pass, $options, $attributes) {
81
        parent::__construct($dsn, $user, $pass, $options, $attributes);
82
    }
83
84
    /**
85
     * Init everything needed
86
     *
87
     * Everything we need to set up is done here in init(), not in the constructor, so even
88
     * when the connection fails (eg config error or DB dead), the constructor has worked
89
     * and we have a $ydb object properly instantiated (and for instance yourls_die() can
90
     * correctly die, even if using $ydb methods)
91
     *
92
     * @since  1.7.3
93
     * @return void
94
     */
95
    public function init() {
96
        $this->connect_to_DB();
97
98
        $this->set_emulate_state();
99
100
        $this->start_profiler();
101
    }
102
103
    /**
104
     * Check if we emulate prepare statements, and set bool flag accordingly
105
     *
106
     * Check if current driver can PDO::getAttribute(PDO::ATTR_EMULATE_PREPARES)
107
     * Some combinations of PHP/MySQL don't support this function. See
108
     * https://travis-ci.org/YOURLS/YOURLS/jobs/271423782#L481
109
     *
110
     * @since  1.7.3
111
     * @return void
112
     */
113
    public function set_emulate_state() {
114
        try {
115
            $this->is_emulate_prepare = $this->getAttribute(PDO::ATTR_EMULATE_PREPARES);
116
        } catch (\PDOException $e) {
117
            $this->is_emulate_prepare = false;
118
        }
119
    }
120
121
    /**
122
     * Get emulate status
123
     *
124
     * @since  1.7.3
125
     * @return bool
126
     */
127 38
    public function get_emulate_state() {
128 38
        return $this->is_emulate_prepare;
129
    }
130
131
    /**
132
     * Initiate real connection to DB server
133
     *
134
     * This is to check that the server is running and/or the config is OK
135
     *
136
     * @since  1.7.3
137
     * @return void
138
     * @throws \PDOException
139
     */
140
    public function connect_to_DB() {
141
        try {
142
            $this->connect();
143
        } catch ( \Exception $e ) {
144
            $this->dead_or_error($e);
145
        }
146
    }
147
148
    /**
149
     * Die with an error message
150
     *
151
     * @since  1.7.3
152
     *
153
     * @param \Exception $exception
154
     *
155
     * @return void
156
     */
157
    public function dead_or_error(\Exception $exception) {
158
        // Use any /user/db_error.php file
159
        if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) {
160
            include_once( YOURLS_USERDIR . '/db_error.php' );
161
            die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
162
        }
163
164
        $message  = yourls__( 'Incorrect DB config, or could not connect to DB' );
165
        $message .= '<br/>' . get_class($exception) .': ' . $exception->getMessage();
166
167
        yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 );
168
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
169
    }
170
171
    /**
172
     * Start a Message Logger
173
     *
174
     * @since  1.7.3
175
     * @see    \YOURLS\Admin\Logger
176
     * @see    \Aura\Sql\Profiler
177
     * @return void
178
     */
179
    public function start_profiler() {
180
        $this->profiler = new Logger($this);
181
    }
182
183
    /**
184
     * @param string $context
185
     */
186 1
    public function set_html_context($context) {
187 1
        $this->context = $context;
188 1
    }
189
190
    /**
191
     * @return string
192
     */
193 1
    public function get_html_context() {
194 1
        return $this->context;
195
    }
196
197
    // Options low level functions, see \YOURLS\Database\Options
198
199
    /**
200
     * @param string $name
201
     * @param mixed  $value
202
     */
203 21
    public function set_option($name, $value) {
204 21
        $this->option[$name] = $value;
205 21
    }
206
207
    /**
208
     * @param  string $name
209
     * @return bool
210
     */
211 24
    public function has_option($name) {
212 24
        return array_key_exists($name, $this->option);
213
    }
214
215
    /**
216
     * @param  string $name
217
     * @return string
218
     */
219 24
    public function get_option($name) {
220 24
        return $this->option[$name];
221
    }
222
223
    /**
224
     * @param string $name
225
     */
226 2
    public function delete_option($name) {
227 2
        unset($this->option[$name]);
228 2
    }
229
230
231
    // Infos (related to keyword) low level functions
232
233
    /**
234
     * @param string $keyword
235
     * @param mixed  $infos
236
     */
237 8
    public function set_infos($keyword, $infos) {
238 8
        $this->infos[$keyword] = $infos;
239 8
    }
240
241
    /**
242
     * @param  string $keyword
243
     * @return bool
244
     */
245 9
    public function has_infos($keyword) {
246 9
        return array_key_exists($keyword, $this->infos);
247
    }
248
249
    /**
250
     * @param  string $keyword
251
     * @return array
252
     */
253 5
    public function get_infos($keyword) {
254 5
        return $this->infos[$keyword];
255
    }
256
257
    /**
258
     * @param string $keyword
259
     */
260
    public function delete_infos($keyword) {
261
        unset($this->infos[$keyword]);
262
    }
263
264
    /**
265
     * @todo: infos & options are working the same way here. Abstract this.
266
     */
267
268
269
    // Plugin low level functions, see functions-plugins.php
270
271
    /**
272
     * @return array
273
     */
274 16
    public function get_plugins() {
275 16
        return $this->plugins;
276
    }
277
278
    /**
279
     * @param array $plugins
280
     */
281 2
    public function set_plugins(array $plugins) {
282 2
        $this->plugins = $plugins;
283 2
    }
284
285
    /**
286
     * @param string $plugin  plugin filename
287
     */
288 2
    public function add_plugin($plugin) {
289 2
        $this->plugins[] = $plugin;
290 2
    }
291
292
    /**
293
     * @param string $plugin  plugin filename
294
     */
295
    public function remove_plugin($plugin) {
296
        unset($this->plugins[$plugin]);
297
    }
298
299
300
    // Plugin Pages low level functions, see functions-plugins.php
301
302
    /**
303
     * @return array
304
     */
305 5
    public function get_plugin_pages() {
306 5
        return is_array( $this->plugin_pages ) ? $this->plugin_pages : [];
0 ignored issues
show
introduced by
The condition is_array($this->plugin_pages) is always true.
Loading history...
307
    }
308
309
    /**
310
     * @param array $pages
311
     */
312 9
    public function set_plugin_pages(array $pages) {
313 9
        $this->plugin_pages = $pages;
314 9
    }
315
316
    /**
317
     * @param string   $slug
318
     * @param string   $title
319
     * @param callable $function
320
     */
321 4
    public function add_plugin_page( $slug, $title, $function ) {
322 4
        $this->plugin_pages[ $slug ] = [
323 4
            'slug'     => $slug,
324 4
            'title'    => $title,
325 4
            'function' => $function,
326
        ];
0 ignored issues
show
Coding Style introduced by
The closing parenthesis does not seem to be aligned correctly; expected 39 space(s), but found 8.
Loading history...
327 4
    }
328
329
    /**
330
     * @param string $slug
331
     */
332
    public function remove_plugin_page( $slug ) {
333
        unset( $this->plugin_pages[ $slug ] );
334
    }
335
336
337
    /**
338
     * Return count of SQL queries performed
339
     *
340
     * @since  1.7.3
341
     * @return int
342
     */
343 2
    public function get_num_queries() {
344 2
        return count( (array) $this->get_queries() );
345
    }
346
347
    /**
348
     * Return SQL queries performed
349
     *
350
     * Aura\Sql\Profiler logs every PDO command issued. But depending on PDO::ATTR_EMULATE_PREPARES, some are
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 109 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
351
     * actually sent to the mysql server or not :
352
     *  - if PDO::ATTR_EMULATE_PREPARES is true, prepare() statements are not sent to the server and are performed
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
353
     *    internally, so they are removed from the logger
354
     *  - if PDO::ATTR_EMULATE_PREPARES is false, prepare() statements are actually performed by the mysql server,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
355
     *    and count as an actual query
356
     *
357
     * Resulting array is something like:
358
     *   array (
359
     *      0 => array (
360
     *           'duration' => 1.0010569095611572265625,
361
     *           'function' => 'connect',
362
     *           'statement' => NULL,
363
     *           'bind_values' => array (),
364
     *           'trace' => ...back trace...,
365
     *       ),
366
     *       // key index might not be sequential if 'prepare' function are filtered out
367
     *       2 => array (
368
     *           'duration' => 0.000999927520751953125,
369
     *           'function' => 'perform',
370
     *           'statement' => 'SELECT option_value FROM yourls_options WHERE option_name = :option_name LIMIT 1',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 115 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
371
     *           'bind_values' => array ( 'option_name' => 'test_option' ),
372
     *           'trace' => ...back trace...,
373
     *       ),
374
     *   );
375
     *
376
     * @since  1.7.3
377
     * @return array
378
     */
379 2
    public function get_queries() {
380 2
        $queries = $this->getProfiler()->getProfiles();
381
382 2
        if ($this->get_emulate_state()) {
383
            // keep queries if $query['function'] != 'prepare'
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
384
            $queries = array_filter($queries, function($query) {return $query['function'] !== 'prepare';});
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 107 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
385
        }
386
387 2
        return $queries;
388
    }
389
390
    /**
391
     * Set YOURLS installed state
392
     *
393
     * @since  1.7.3
394
     * @param  bool $bool
395
     * @return void
396
     */
397
    public function set_installed($bool) {
398
        $this->installed = $bool;
399
    }
400
401
    /**
402
     * Get YOURLS installed state
403
     *
404
     * @since  1.7.3
405
     * @return bool
406
     */
407 2
    public function is_installed() {
408 2
        return $this->installed;
409
    }
410
411
    /**
412
     * Return standardized DB version
413
     *
414
     * The regex removes everything that's not a number at the start of the string, or remove anything that's not a number and what
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
415
     * follows after that.
416
     *   'omgmysql-5.5-ubuntu-4.20' => '5.5'
417
     *   'mysql5.5-ubuntu-4.20'     => '5.5'
418
     *   '5.5-ubuntu-4.20'          => '5.5'
419
     *   '5.5-beta2'                => '5.5'
420
     *   '5.5'                      => '5.5'
421
     *
422
     * @since  1.7.3
423
     * @return string
424
     */
425 10
    public function mysql_version() {
426 10
        $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
427 10
        return $version;
428
    }
429
430
    /**
431
     * Deprecated properties since 1.7.3, unused in 3rd party plugins as far as I know
432
     *
433
     * $ydb->DB_driver
434
     * $ydb->captured_errors
435
     * $ydb->dbh
436
     * $ydb->result
437
     * $ydb->rows_affected
438
     * $ydb->show_errors
439
     */
440
441
    /**
442
     * Deprecated functions since 1.7.3
443
     */
444
445
    // @codeCoverageIgnoreStart
446
447
    public function escape($string) {
448
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
449
        // This will escape using PDO->quote(), but then remove the enclosing quotes
450
        return substr($this->quote($string), 1, -1);
451
    }
452
453
    public function get_col($query) {
454
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
455
        yourls_debug_log('LEGACY SQL: '.$query);
456
        return $this->fetchCol($query);
457
    }
458
459
    public function get_results($query) {
460
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
461
        yourls_debug_log('LEGACY SQL: '.$query);
462
        $stm = parent::query($query);
463
        return($stm->fetchAll(PDO::FETCH_OBJ));
464
    }
465
466
    public function get_row($query) {
467
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
468
        yourls_debug_log('LEGACY SQL: '.$query);
469
        $row = $this->fetchObjects($query);
470
        return isset($row[0]) ? $row[0] : null;
471
    }
472
473
    public function get_var($query) {
474
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
475
        yourls_debug_log('LEGACY SQL: '.$query);
476
        return $this->fetchValue($query);
477
    }
478
479
    public function query($query) {
480
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
481
        yourls_debug_log('LEGACY SQL: '.$query);
482
        return $this->fetchAffected($query);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetchAffected($query) returns the type integer which is incompatible with the return type mandated by Aura\Sql\PdoInterface::query() of PDOStatement.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
483
    }
484
    // @codeCoverageIgnoreEnd
485
}
486